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"
)

// ApprovalProcessNodeReadLimit is the maximum topology the query-only
// approval runtime can load without truncation.
const ApprovalProcessNodeReadLimit = 100

func normalizeRejectBehaviorForProcessNode(rejectBehavior string) string {
	return constants.NormalizeApprovalProcessNodeRejectBehavior(rejectBehavior)
}

func normalizedStepCriteriaHasFilter(criteria *NormalizedCriteria) bool {
	if criteria == nil {
		return false
	}
	if strings.TrimSpace(criteria.Formula) != "" {
		return true
	}
	return len(criteria.CriteriaItems) > 0
}

func buildProcessNodeFromMetadata(step NormalizedApprovalStep, processDefID string, order int) models.SObject {
	record := models.SObject{
		// Match the canonical CRM lifecycle producer and the public
		// ProcessInstanceNode.ProcessNodeName contract: Name is the authored
		// developer name. Presentation reads obtain Label from Metadata.
		"Name":                step.Name,
		"DeveloperName":       step.Name,
		"Description":         step.Description,
		"ProcessDefinitionId": processDefID,
		"AllowComments":       true,
		"RequireComments":     false,
		"StepOrder":           order,
		"RejectBehavior":      normalizeRejectBehaviorForProcessNode(step.RejectBehavior),
	}
	if record["Name"] == "" {
		record["Name"] = step.Label
	}
	if len(step.Approvers) == 0 {
		return record
	}

	firstApprover := step.Approvers[0]
	switch firstApprover.Type {
	case constants.ApprovalApproverTypeRelatedUserField:
		record["ApproverType"] = firstApprover.Type
		record["ApproverRelatedField"] = firstApprover.Name
	case constants.ApprovalApproverTypeHierarchy, constants.ApprovalApproverTypeQueue, constants.ApprovalApproverTypeRole:
		record["ApproverType"] = firstApprover.Type
	case constants.ApprovalApproverTypeUser:
		record["ApproverType"] = firstApprover.Type
		userRefs := make([]string, 0, len(step.Approvers))
		for _, approver := range step.Approvers {
			if approver.Type != constants.ApprovalApproverTypeUser {
				continue
			}
			ref := strings.TrimSpace(approver.Name)
			if ref != "" {
				userRefs = append(userRefs, ref)
			}
		}
		if len(userRefs) > 0 {
			record["ApproverUserId"] = strings.Join(userRefs, ",")
		}
	}

	return record
}

func (s *ApprovalService) approvalProcessTopologyIsQueryOnly(processDef models.SObject) bool {
	if s == nil || len(s.approvalQueryOnlyOwners) == 0 {
		return false
	}
	owner, _ := processDef[constants.FieldProcessDefinition_OwnerPack].(string)
	trimmed := strings.TrimSpace(owner)
	if trimmed == "" || owner != trimmed {
		return false
	}
	_, queryOnly := s.approvalQueryOnlyOwners[trimmed]
	return queryOnly
}

func (s *ApprovalService) ensureProcessNodesForDefinition(
	ctx context.Context,
	processDef models.SObject,
	user *models.UserSession,
) ([]models.SObject, error) {
	processDefID, _ := processDef[constants.FieldID].(string)
	if processDefID == "" {
		return nil, nil
	}

	nodeFilter := fmt.Sprintf("ProcessDefinitionId == '%s'", escapeApprovalFilterValue(processDefID))
	nodeResult, err := s.query.QueryWithFilter(ctx, constants.TableProcessNode, nodeFilter, user, "StepOrder", "ASC", ApprovalProcessNodeReadLimit)
	if err == nil && nodeResult != nil && len(nodeResult.Records) > 0 {
		return nodeResult.Records, nil
	}
	if err != nil {
		s.logger.Error("Failed to query ProcessNode for "+processDefID, err)
		return nil, apperrors.NewInternalError("failed to load approval process steps", err)
	}

	rawMetadata := approvalStringValue(processDef[constants.FieldProcessDefinition_Metadata])
	if strings.TrimSpace(rawMetadata) == "" {
		return nil, nil
	}
	metadata, parseErr := s.approvalMetadataForDefinition(processDef)
	if parseErr != nil {
		return nil, parseErr
	}
	if metadata == nil || len(metadata.Steps) == 0 {
		return nil, nil
	}

	if s.approvalProcessTopologyIsQueryOnly(processDef) {
		return nil, apperrors.NewInternalError("active approval process definition has no materialized steps", nil)
	}

	createdNodes := make([]models.SObject, 0, len(metadata.Steps))
	for idx, step := range metadata.Steps {
		record := buildProcessNodeFromMetadata(step, processDefID, idx+1)
		inserted, insertErr := s.persistence.Insert(processTableWriteContext(ctx, "ApprovalService: rebuild ProcessNode"), constants.TableProcessNode, record, user)
		if insertErr != nil {
			return nil, apperrors.NewInternalError("failed to rebuild approval process steps from metadata", insertErr)
		}
		createdNodes = append(createdNodes, inserted)
	}

	return createdNodes, nil
}

func (s *ApprovalService) ifCriteriaNotMetBehaviorForNode(processDef models.SObject, node models.SObject) (string, error) {
	behavior := ""
	metaStep, metadataErr := s.metadataStepForNode(processDef, node)
	if metadataErr != nil {
		return "", metadataErr
	}
	if metaStep != nil {
		behavior = normalizeIfCriteriaNotMet(metaStep.IfCriteriaNotMet)
	}
	if behavior == "GotoNextStep" {
		return behavior, nil
	}
	if behavior == "RejectRecord" {
		return behavior, nil
	}
	return "ApproveRecord", nil
}

func (s *ApprovalService) selectInitialApprovalStep(
	ctx context.Context,
	processDef models.SObject,
	nodeRecords []models.SObject,
	objectName string,
	targetObjectID string,
	submittedByID string,
	user *models.UserSession,
) (models.SObject, []string, string, error) {
	if len(nodeRecords) == 0 {
		return nil, []string{submittedByID}, constants.ApprovalStatusPending, nil
	}

	for idx, node := range nodeRecords {
		matches, criteriaErr := s.stepCriteriaMatches(ctx, processDef, node, objectName, targetObjectID, user)
		if criteriaErr != nil {
			return nil, nil, "", criteriaErr
		}
		if matches {
			actorIDs, resolveErr := s.resolveApproverIDsForNodeWithContext(ctx, processDef, node, targetObjectID, user, approvalApproverResolutionContext{
				submitterID: submittedByID,
			})
			if resolveErr != nil {
				return nil, nil, "", resolveErr
			}
			if len(actorIDs) == 0 {
				return nil, nil, "", newUnresolvedApprovalStepError(node)
			}
			return node, actorIDs, constants.ApprovalStatusPending, nil
		}

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

	return nil, nil, constants.ApprovalStatusApproved, nil
}
