package automation

import (
	"context"
	"fmt"
	"strings"

	apperrors "github.com/nexuscrm/kernel/pkg/errors"
	"github.com/nexuscrm/shared/pkg/constants"
	"github.com/nexuscrm/shared/pkg/models"
)

func (s *ApprovalService) loadProcessDefinitionForAction(ctx context.Context, processDefID string, user *models.UserSession) (models.SObject, error) {
	processDefID = strings.TrimSpace(processDefID)
	if processDefID == "" {
		return nil, apperrors.NewInternalError("approval instance missing ProcessDefinitionId", nil)
	}
	defFilter := fmt.Sprintf("Id == '%s'", escapeApprovalFilterValue(processDefID))
	defResult, err := s.query.QueryWithFilter(ctx, constants.TableProcessDefinition, defFilter, user, "", "", 1)
	if err != nil {
		return nil, apperrors.NewInternalError("failed to load approval process definition", err)
	}
	if defResult == nil || len(defResult.Records) == 0 {
		return nil, apperrors.NewInternalError("approval process definition not found for action", nil)
	}
	return defResult.Records[0], nil
}

func approvalNodeComparableNames(node models.SObject) map[string]struct{} {
	names := make(map[string]struct{})
	for _, field := range []string{"Name", "DeveloperName", "ProcessNodeName"} {
		if name := strings.TrimSpace(approvalStringValue(node[field])); name != "" {
			names[name] = struct{}{}
		}
	}
	return names
}

func currentApprovalNodeIndex(currentNode models.SObject, orderedNodes []models.SObject) (int, error) {
	if currentNode == nil {
		return -1, fmt.Errorf("current approval step is missing")
	}
	currentID := strings.TrimSpace(approvalStringValue(currentNode[constants.FieldID]))
	if currentID != "" {
		for i, node := range orderedNodes {
			nodeID := strings.TrimSpace(approvalStringValue(node[constants.FieldID]))
			if nodeID != "" && nodeID == currentID {
				return i, nil
			}
		}
	}

	names := approvalNodeComparableNames(currentNode)
	if len(names) == 0 {
		return -1, fmt.Errorf("current approval step %q was not found in process definition", currentID)
	}

	matches := make([]int, 0, 1)
	for i, node := range orderedNodes {
		for _, field := range []string{"Name", "DeveloperName"} {
			if name := strings.TrimSpace(approvalStringValue(node[field])); name != "" {
				if _, ok := names[name]; ok {
					matches = append(matches, i)
					break
				}
			}
		}
	}
	switch len(matches) {
	case 1:
		return matches[0], nil
	case 0:
		return -1, fmt.Errorf("current approval step %q was not found in process definition", currentID)
	default:
		return -1, fmt.Errorf("current approval step %q matched multiple process definition steps", currentID)
	}
}

type approvalStepTransition struct {
	advanced       bool
	nextNodeID     string
	nextNodeName   string
	actorIDs       []string
	terminalStatus string
}

// determineNextApprovalStep finds the next step from ProcessNode order without
// writing runtime rows. Callers prepare required lifecycle actions before
// creating the returned step records.
func (s *ApprovalService) determineNextApprovalStep(ctx context.Context, processDef models.SObject, currentNode models.SObject, targetObjectId, submittedByID string, user *models.UserSession) (approvalStepTransition, error) {
	if processDef == nil || currentNode == nil {
		return approvalStepTransition{}, apperrors.NewInternalError("approval action missing current process context", nil)
	}
	processDefID, _ := processDef[constants.FieldID].(string)

	// Fetch all ProcessNodes ordered by StepOrder
	nodeFilter := fmt.Sprintf("ProcessDefinitionId == '%s'", escapeApprovalFilterValue(processDefID))
	nodeResult, err := s.query.QueryWithFilter(ctx, "ProcessNode", nodeFilter, user, "StepOrder", "ASC", 100)
	if err != nil {
		return approvalStepTransition{}, apperrors.NewInternalError("failed to load approval process steps", err)
	}
	if nodeResult == nil || len(nodeResult.Records) == 0 {
		return approvalStepTransition{}, apperrors.NewInternalError("approval process definition has no steps", nil)
	}

	// Find current step index
	currentIdx, idxErr := currentApprovalNodeIndex(currentNode, nodeResult.Records)
	if idxErr != nil {
		return approvalStepTransition{}, apperrors.NewInternalError("failed to locate current approval step", idxErr)
	}
	if currentIdx >= len(nodeResult.Records)-1 {
		return approvalStepTransition{}, nil
	}

	objectName, _ := processDef["TableEnumOrId"].(string)

	// Walk downstream steps in order. For each candidate:
	//   - If step criteria match, start it.
	//   - If criteria do NOT match, consult ifCriteriaNotMet:
	//       "GotoNextStep"          -> skip forward to the next candidate.
	//       "ApproveRecord" or ""   -> finalize the process as Approved.
	//       "RejectRecord"           -> finalize the process as Rejected.
	// Running off the end of the chain also finalizes as Approved.
	// Salesforce default (field absent) is ApproveRecord.
	for idx := currentIdx + 1; idx < len(nodeResult.Records); idx++ {
		nextNode := nodeResult.Records[idx]

		matches, criteriaErr := s.stepCriteriaMatches(ctx, processDef, nextNode, objectName, targetObjectId, user)
		if criteriaErr != nil {
			return approvalStepTransition{}, criteriaErr
		}
		if matches {
			nextNodeId, _ := nextNode[constants.FieldID].(string)
			nextNodeName, _ := nextNode["Name"].(string)

			nextActorIDs, resolveErr := s.resolveApproverIDsForNodeWithContext(ctx, processDef, nextNode, targetObjectId, user, approvalApproverResolutionContext{
				submitterID:     submittedByID,
				previousActorID: user.ID,
			})
			if resolveErr != nil {
				return approvalStepTransition{}, resolveErr
			}
			if len(nextActorIDs) == 0 {
				return approvalStepTransition{}, newUnresolvedApprovalStepError(nextNode)
			}

			return approvalStepTransition{
				advanced:     true,
				nextNodeID:   nextNodeId,
				nextNodeName: nextNodeName,
				actorIDs:     nextActorIDs,
			}, nil
		}

		behavior, behaviorErr := s.ifCriteriaNotMetBehaviorForNode(processDef, nextNode)
		if behaviorErr != nil {
			return approvalStepTransition{}, behaviorErr
		}
		nextNodeName, _ := nextNode["Name"].(string)
		s.logger.Info(fmt.Sprintf("stepCriteria: step %q (idx=%d) did not match; ifCriteriaNotMet=%s", nextNodeName, idx, behavior))
		if behavior == "GotoNextStep" {
			continue
		}
		if behavior == "RejectRecord" {
			return approvalStepTransition{terminalStatus: constants.ApprovalStatusRejected}, nil
		}
		return approvalStepTransition{}, nil
	}

	return approvalStepTransition{}, nil
}

// determineRejectBackToPrevious checks whether rejection should roll back to a
// previous step without writing runtime rows. Callers prepare required
// lifecycle actions before creating the returned step records.
func (s *ApprovalService) determineRejectBackToPrevious(ctx context.Context, processDef models.SObject, instanceId string, currentNode models.SObject, targetObjectId, submittedByID string, user *models.UserSession) (approvalStepTransition, error) {
	if processDef == nil || currentNode == nil {
		return approvalStepTransition{}, apperrors.NewInternalError("approval action missing current process context", nil)
	}
	processDefID, _ := processDef[constants.FieldID].(string)

	// Fetch all ProcessNodes ordered by StepOrder
	nodeFilter := fmt.Sprintf("ProcessDefinitionId == '%s'", escapeApprovalFilterValue(processDefID))
	nodeResult, err := s.query.QueryWithFilter(ctx, "ProcessNode", nodeFilter, user, "StepOrder", "ASC", 100)
	if err != nil {
		return approvalStepTransition{}, apperrors.NewInternalError("failed to load approval process steps", err)
	}
	if nodeResult == nil || len(nodeResult.Records) == 0 {
		return approvalStepTransition{}, apperrors.NewInternalError("approval process definition has no steps", nil)
	}

	// Find the current step
	currentIdx, idxErr := currentApprovalNodeIndex(currentNode, nodeResult.Records)
	if idxErr != nil {
		return approvalStepTransition{}, apperrors.NewInternalError("failed to locate current approval step", idxErr)
	}

	// Check RejectBehavior on the current node
	rejectBehavior, _ := nodeResult.Records[currentIdx]["RejectBehavior"].(string)
	metadataStep, metadataErr := s.metadataStepForNode(processDef, nodeResult.Records[currentIdx])
	if metadataErr != nil {
		return approvalStepTransition{}, metadataErr
	}
	if metadataStep != nil && metadataStep.RejectBehavior != "" {
		rejectBehavior = metadataStep.RejectBehavior
	}
	switch constants.NormalizeApprovalRejectBehavior(rejectBehavior) {
	case constants.ApprovalRejectBehaviorBackToSubmitter, constants.ApprovalRejectBehaviorBackToPrevious:
		// Roll back to the previous step (or first step if at step 1)
		prevIdx := currentIdx - 1
		if prevIdx < 0 {
			prevIdx = 0
		}
		prevNode := nodeResult.Records[prevIdx]
		prevNodeId, _ := prevNode[constants.FieldID].(string)
		prevNodeName, _ := prevNode["Name"].(string)

		previousActorID, historyErr := s.previousActorIDForStepIndex(ctx, instanceId, nodeResult.Records, prevIdx, user)
		if historyErr != nil {
			return approvalStepTransition{}, historyErr
		}
		prevActorIDs, resolveErr := s.resolveApproverIDsForNodeWithContext(ctx, processDef, prevNode, targetObjectId, user, approvalApproverResolutionContext{
			submitterID:     submittedByID,
			previousActorID: previousActorID,
		})
		if resolveErr != nil {
			return approvalStepTransition{}, resolveErr
		}
		if len(prevActorIDs) == 0 {
			return approvalStepTransition{}, newUnresolvedApprovalStepError(prevNode)
		}

		return approvalStepTransition{
			advanced:     true,
			nextNodeID:   prevNodeId,
			nextNodeName: prevNodeName,
			actorIDs:     prevActorIDs,
		}, nil
	case constants.ApprovalRejectBehaviorRejectRequest:
		// "RejectRequest" - finalize as rejected (same as FinalRejection)
		return approvalStepTransition{}, nil
	default:
		// "FinalRejection" or empty - finalize as rejected
		return approvalStepTransition{}, nil
	}
}

// createNextStepRecords creates ProcessInstanceNode and one or more ProcessInstanceWorkitems.
func (s *ApprovalService) createNextStepRecords(ctx context.Context, instanceId, targetObjectId, targetObjectType, nextNodeId, nextNodeName string, nextActorIDs []string, now string, user *models.UserSession) (bool, []string, []string, error) {
	nodeInstanceRecord := models.SObject{
		"ProcessInstanceId": instanceId,
		"ProcessNodeId":     nextNodeId,
		"ProcessNodeName":   nextNodeName,
		"NodeStatus":        constants.ApprovalStatusPending,
		"CreatedDate":       now,
	}
	createdNode, insertErr := s.persistence.Insert(processTableWriteContext(ctx, "ApprovalService: create next-step ProcessInstanceNode"), constants.TableProcessInstanceNode, nodeInstanceRecord, user)
	if insertErr != nil {
		return false, nil, nil, fmt.Errorf("failed to create ProcessInstanceNode for next step: %w", insertErr)
	}
	nextStepNodeID, _ := createdNode[constants.FieldID].(string)

	workItemIDs, insertErr := s.createWorkitems(ctx, instanceId, nextActorIDs, "", now, user)
	if insertErr != nil {
		return false, nil, nil, fmt.Errorf("failed to create ProcessInstanceWorkitem for next step: %w", insertErr)
	}
	for i, actorID := range nextActorIDs {
		historyID := ""
		if i < len(workItemIDs) {
			historyID = workItemIDs[i]
		}
		if err := s.appendApprovalHistoryRow(ctx, approvalHistoryRow{
			ID:                historyID,
			ProcessInstanceID: instanceId,
			StepNodeID:        nextStepNodeID,
			ProcessNodeID:     nextNodeId,
			TargetObjectID:    targetObjectId,
			TargetObjectType:  targetObjectType,
			StepStatus:        constants.ApprovalStatusPending,
			ActorID:           actorID,
			OriginalActorID:   actorID,
			Comments:          "",
			IsPending:         true,
			CreatedDate:       now,
		}, user); err != nil {
			return false, nil, nil, err
		}
	}

	return true, nextActorIDs, workItemIDs, nil
}
