package automation

import (
	"context"
	"encoding/json"
	"fmt"
	"strconv"
	"strings"

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

const defaultHierarchyFieldName = "ManagerId"
const approvalApproverResolutionFailClosedReason = "no active approver resolved"

type approvalApproverResolutionContext struct {
	submitterID     string
	previousActorID string
}

func (s *ApprovalService) approvalMetadataForDefinition(def map[string]interface{}) (*NormalizedApprovalProcess, error) {
	if def == nil {
		return nil, nil
	}
	// Normalize the Metadata value defensively at the single owner boundary
	// for definition metadata: the shared row scanner already returns TEXT
	// columns as string, but a swallowed type assertion here would let any
	// future []byte-producing read path silently decode as "no metadata" and
	// downgrade every authored step criterion to an unconditional match
	// (NX#3997).
	raw := approvalStringValue(def["Metadata"])
	metadata, err := s.decodeApprovalMetadata(raw)
	if err != nil {
		return nil, apperrors.NewInternalError("failed to decode ProcessDefinition metadata", err)
	}
	return metadata, nil
}

func (s *ApprovalService) metadataStepForNode(def models.SObject, node models.SObject) (*NormalizedApprovalStep, error) {
	metadata, err := s.approvalMetadataForDefinition(def)
	if err != nil {
		return nil, err
	}
	return metadataStepForProcessNode(metadata, node), nil
}

func metadataStepForProcessNode(metadata *NormalizedApprovalProcess, node models.SObject) *NormalizedApprovalStep {
	if metadata == nil || node == nil {
		return nil
	}
	developerName := approvalStringValue(node["DeveloperName"])
	label := approvalStringValue(node["Name"])
	if step := metadata.StepForNames(developerName, label); step != nil {
		return step
	}
	if stepOrder := processNodeStepOrder(node); stepOrder > 0 && stepOrder <= len(metadata.Steps) {
		return &metadata.Steps[stepOrder-1]
	}
	return nil
}

// describeMetadataStepGap explains why metadataStepForProcessNode found no
// authored step for a runtime ProcessNode, for the stepCriteriaMatches WARN
// (NX#3997): either the definition carries no decodable metadata at all, or
// the decoded metadata does not describe this node.
func describeMetadataStepGap(def models.SObject, node models.SObject, metadata *NormalizedApprovalProcess) string {
	defID := approvalStringValue(def[constants.FieldID])
	nodeName := approvalStringValue(node["Name"])
	developerName := approvalStringValue(node["DeveloperName"])
	if metadata == nil {
		return fmt.Sprintf("ProcessDefinition %s has no decodable approval metadata while evaluating node %q (developerName=%q); authored step criteria cannot be enforced", defID, nodeName, developerName)
	}
	return fmt.Sprintf("ProcessDefinition %s approval metadata (%d steps) does not describe node %q (developerName=%q, stepOrder=%d); authored step criteria cannot be enforced", defID, len(metadata.Steps), nodeName, developerName, processNodeStepOrder(node))
}

func processNodeStepOrder(node models.SObject) int {
	if node == nil {
		return 0
	}
	switch value := node["StepOrder"].(type) {
	case int:
		return value
	case int32:
		return int(value)
	case int64:
		return int(value)
	case float32:
		return int(value)
	case float64:
		return int(value)
	case json.Number:
		if parsed, err := value.Int64(); err == nil {
			return int(parsed)
		}
	case string:
		if parsed, err := strconv.Atoi(strings.TrimSpace(value)); err == nil {
			return parsed
		}
	}
	return 0
}

func (s *ApprovalService) resolveApproverIDsForNodeWithContext(
	ctx context.Context,
	def models.SObject,
	node models.SObject,
	targetRecordID string,
	user *models.UserSession,
	resolutionContext approvalApproverResolutionContext,
) ([]string, error) {
	objectName, _ := def["TableEnumOrId"].(string)
	metadata, metadataErr := s.approvalMetadataForDefinition(def)
	if metadataErr != nil {
		return nil, metadataErr
	}
	if step := metadataStepForProcessNode(metadata, node); step != nil {
		ids, err := s.resolveApproverIDsForStepWithContext(ctx, metadata, step, targetRecordID, objectName, user, resolutionContext)
		if err != nil {
			return nil, err
		}
		if len(ids) > 0 {
			return ids, nil
		}
		if approverType, approverRef, ok := authoredApproverFromStep(step); ok {
			return nil, newApproverResolutionError(approverType, approverRef, approvalStepDisplayName(node))
		}
	}

	approverType, _ := node["ApproverType"].(string)
	if constants.NormalizeApprovalApproverType(approverType) == constants.ApprovalApproverTypeHierarchy {
		hierarchyApproverID, err := s.resolveHierarchyApproverID(ctx, metadata, resolutionContext, targetRecordID, objectName, user)
		if err != nil {
			return nil, err
		}
		if hierarchyApproverID != "" {
			return []string{hierarchyApproverID}, nil
		}
	}

	// Fall back to legacy single-approver fields, but try multiple approvers first
	multiFallback, multiFallbackErr := s.resolveApproverIDsFromNode(ctx, node, targetRecordID, objectName, user)
	if multiFallbackErr != nil {
		return nil, multiFallbackErr
	}
	if len(multiFallback) > 0 {
		return multiFallback, nil
	}

	// If no multiple approvers were found, try single fallback for backwards compatibility
	fallback, fallbackErr := s.resolveApproverFromNode(ctx, node, targetRecordID, objectName, user)
	if fallbackErr != nil {
		return nil, fallbackErr
	}
	if fallback != "" {
		return []string{fallback}, nil
	}
	if approverType, approverRef, ok := authoredApproverFromNode(node); ok {
		return nil, newApproverResolutionError(approverType, approverRef, approvalStepDisplayName(node))
	}
	return nil, nil
}

func authoredApproverFromStep(step *NormalizedApprovalStep) (string, string, bool) {
	if step == nil || len(step.Approvers) == 0 {
		return "", "", false
	}
	approver := step.Approvers[0]
	return constants.NormalizeApprovalApproverType(approver.Type), strings.TrimSpace(approver.Name), true
}

func authoredApproverFromNode(node models.SObject) (string, string, bool) {
	if node == nil {
		return "", "", false
	}
	approverType := constants.NormalizeApprovalApproverType(approvalStringValue(node["ApproverType"]))
	if approverType == "" {
		return "", "", false
	}
	approverRef := firstNonEmptyApprovalValue(node["ApproverRelatedField"], node["ApproverUserId"])
	switch approverType {
	case constants.ApprovalApproverTypeQueue, constants.ApprovalApproverTypeRole:
		return approverType, approverRef, true
	case constants.ApprovalApproverTypeUser, constants.ApprovalApproverTypeRelatedUserField:
		return approverType, approverRef, true
	case constants.ApprovalApproverTypeHierarchy:
		return "", "", false
	default:
		return approverType, approverRef, true
	}
}

func approvalStepDisplayName(node models.SObject) string {
	if node == nil {
		return ""
	}
	return firstNonEmptyApprovalValue(node["DeveloperName"], node["Name"], node[constants.FieldID])
}

func approvalMetadataStepDisplayName(step *NormalizedApprovalStep) string {
	if step == nil {
		return ""
	}
	return firstNonEmptyApprovalValue(step.Name, step.Label)
}

func newApproverResolutionError(approverType, approverRef, stepName string) error {
	detail := "approval step could not resolve an approver"
	if approverType != "" {
		detail = fmt.Sprintf("approval step could not resolve authored approver type %q", approverType)
	}
	if approverRef != "" {
		detail += fmt.Sprintf(" reference %q", approverRef)
	}
	if stepName != "" {
		detail += fmt.Sprintf(" for step %q", stepName)
	}
	detail += fmt.Sprintf("; reason: %s; approval routing fails closed", approvalApproverResolutionFailClosedReason)
	return apperrors.NewValidationError("assignedApprover", detail)
}

func newUnresolvedApprovalStepError(node models.SObject) error {
	approverType, approverRef, _ := authoredApproverFromNode(node)
	return newApproverResolutionError(approverType, approverRef, approvalStepDisplayName(node))
}

// resolveApproverFromNode resolves an approver User ID from ProcessNode fields.
// It handles direct user lookup and related-user-field lookup fallbacks.
func (s *ApprovalService) resolveApproverFromNode(ctx context.Context, node models.SObject, targetRecordId, objectName string, user *models.UserSession) (string, error) {
	approverType, _ := node["ApproverType"].(string)
	normalizedApproverType := constants.NormalizeApprovalApproverType(approverType)
	approverUserId, _ := node["ApproverUserId"].(string)

	// Related-user fields cannot yield multiple users from a single field
	// reference, so that fallback returns the first resolved user.
	switch normalizedApproverType {
	case constants.ApprovalApproverTypeUser:
		if approverUserId == "" {
			return "", nil
		}
		// Split comma-separated values to support multiple approvers
		approverValues := strings.Split(approverUserId, ",")
		for _, val := range approverValues {
			trimmedVal := strings.TrimSpace(val)
			if trimmedVal == "" {
				continue
			}
			uid, err := s.resolveActiveApprovalUserReference(ctx, trimmedVal, user)
			if err != nil {
				return "", err
			}
			if uid != "" {
				return uid, nil
			}
		}
		return "", nil

	case constants.ApprovalApproverTypeRelatedUserField:
		// The field name may be in ApproverRelatedField (new deploy) or ApproverUserId (legacy SF sync)
		approverRelatedField, _ := node["ApproverRelatedField"].(string)
		fieldName := approverRelatedField
		if fieldName == "" {
			fieldName = approverUserId // fallback for legacy data
		}
		if fieldName == "" || targetRecordId == "" || objectName == "" {
			return "", nil
		}
		recFilter := fmt.Sprintf("Id == '%s'", escapeApprovalFilterValue(targetRecordId))
		recResult, err := s.query.QueryWithFilter(ctx, objectName, recFilter, user, "", "", 1)
		if err != nil {
			return "", err
		}
		if recResult == nil || len(recResult.Records) == 0 {
			return "", nil
		}
		relatedUserId, _ := recResult.Records[0][fieldName].(string)
		return relatedUserId, nil

	default:
		return "", nil
	}
}

// resolveApproverIDsFromNode resolves multiple approver User IDs from ProcessNode fields.
// Supports comma-separated values in the ApproverUserId field for multiple approvers.
func (s *ApprovalService) resolveApproverIDsFromNode(ctx context.Context, node models.SObject, targetRecordId, objectName string, user *models.UserSession) ([]string, error) {
	approverType, _ := node["ApproverType"].(string)
	normalizedApproverType := constants.NormalizeApprovalApproverType(approverType)
	approverUserId, _ := node["ApproverUserId"].(string)

	switch normalizedApproverType {
	case constants.ApprovalApproverTypeUser:
		if approverUserId == "" {
			return nil, nil
		}
		// Split comma-separated values to support multiple approvers
		approverValues := strings.Split(approverUserId, ",")
		var approverIDs []string
		seen := make(map[string]struct{})

		for _, val := range approverValues {
			trimmedVal := strings.TrimSpace(val)
			if trimmedVal == "" {
				continue
			}
			resolvedID, err := s.resolveActiveApprovalUserReference(ctx, trimmedVal, user)
			if err != nil {
				return nil, err
			}
			if resolvedID == "" {
				return nil, newApproverResolutionError(normalizedApproverType, trimmedVal, approvalStepDisplayName(node))
			}
			if _, exists := seen[resolvedID]; exists {
				continue
			}
			seen[resolvedID] = struct{}{}
			approverIDs = append(approverIDs, resolvedID)
		}
		return approverIDs, nil

	case constants.ApprovalApproverTypeRelatedUserField:
		// For related user fields, we still only support single user
		// The field name may be in ApproverRelatedField (new deploy) or ApproverUserId (legacy SF sync)
		approverRelatedField, _ := node["ApproverRelatedField"].(string)
		fieldName := approverRelatedField
		if fieldName == "" {
			fieldName = approverUserId // fallback for legacy data
		}
		if fieldName == "" || targetRecordId == "" || objectName == "" {
			return nil, nil
		}
		recFilter := fmt.Sprintf("Id == '%s'", escapeApprovalFilterValue(targetRecordId))
		recResult, err := s.query.QueryWithFilter(ctx, objectName, recFilter, user, "", "", 1)
		if err != nil {
			return nil, err
		}
		if recResult == nil || len(recResult.Records) == 0 {
			return nil, nil
		}
		relatedUserId, _ := recResult.Records[0][fieldName].(string)
		if relatedUserId != "" {
			return []string{relatedUserId}, nil
		}
		return nil, nil

	case constants.ApprovalApproverTypeQueue:
		approverRef := firstNonEmptyApprovalValue(node["ApproverUserId"], node["ApproverRelatedField"])
		if approverRef == "" {
			return nil, nil
		}
		return s.resolveQueueApproverIDs(ctx, approverRef, objectName, user)

	case constants.ApprovalApproverTypeRole:
		approverRef := firstNonEmptyApprovalValue(node["ApproverUserId"], node["ApproverRelatedField"])
		if approverRef == "" {
			return nil, nil
		}
		return s.resolveRoleApproverIDs(ctx, approverRef, user)

	default:
		return nil, nil
	}
}

func (s *ApprovalService) approvalModeForNode(def models.SObject, node models.SObject) (string, error) {
	// ProcessDefinition.Metadata.whenMultipleApprovers is the authoritative source
	// of truth for runtime routing mode. ProcessNode columns may help identify the
	// metadata step, but they must not author a parallel routing policy.
	metadata, err := s.approvalMetadataForDefinition(def)
	if err != nil {
		return "", err
	}
	if metadata == nil {
		return constants.ApprovalModeFirstResponse, nil
	}
	if step := metadataStepForProcessNode(metadata, node); step != nil {
		if strings.TrimSpace(step.WhenMultipleApprovers) != "" {
			return constants.NormalizeApprovalMode(step.WhenMultipleApprovers), nil
		}
		return constants.ApprovalModeFirstResponse, nil
	}
	// If authored metadata exists but the runtime node cannot be mapped back to
	// a step, keep explicit unanimous branches pending instead of deleting sibling
	// work items through the Salesforce default.
	for _, step := range metadata.Steps {
		if constants.NormalizeApprovalMode(step.WhenMultipleApprovers) == constants.ApprovalModeUnanimous {
			return constants.ApprovalModeUnanimous, nil
		}
	}
	return constants.ApprovalModeFirstResponse, nil
}

func (s *ApprovalService) canUserSubmitForProcess(ctx context.Context, def map[string]interface{}, objectName, targetRecordID string, user *models.UserSession) (bool, error) {
	if user == nil || strings.TrimSpace(user.ID) == "" {
		return false, nil
	}

	metadata, metadataErr := s.approvalMetadataForDefinition(def)
	if metadataErr != nil {
		return false, metadataErr
	}
	if metadata == nil || !metadata.HasExplicitAllowedSubmitters {
		return true, nil
	}
	if len(metadata.AllowedSubmitters) == 0 {
		return false, nil
	}

	var (
		targetRecord                 models.SObject
		targetRecordLoaded           bool
		currentUserRecord            models.SObject
		currentUserLoaded            bool
		currentRoleID                string
		currentRoleLoaded            bool
		currentUsername              string
		currentUserLoadedForUsername bool
		roleAncestors                map[string]struct{}
		roleAncestorsLoaded          bool
	)

	loadTargetRecord := func() (models.SObject, error) {
		if targetRecordLoaded {
			return targetRecord, nil
		}
		targetRecordLoaded = true
		record, err := s.querySingleByFilter(ctx, objectName, fmt.Sprintf("Id == '%s'", escapeApprovalFilterValue(targetRecordID)), user)
		if err != nil {
			return nil, err
		}
		targetRecord = record
		return targetRecord, nil
	}

	loadCurrentUserRecord := func() (models.SObject, error) {
		if currentUserLoaded {
			return currentUserRecord, nil
		}
		currentUserLoaded = true
		record, err := s.querySingleByFilter(ctx, constants.TableUser, fmt.Sprintf("Id == '%s'", escapeApprovalFilterValue(user.ID)), user)
		if err != nil {
			return nil, err
		}
		currentUserRecord = record
		return currentUserRecord, nil
	}

	loadCurrentUsername := func() (string, error) {
		if currentUserLoadedForUsername {
			return currentUsername, nil
		}
		currentUserLoadedForUsername = true
		record, err := loadCurrentUserRecord()
		if err != nil {
			return "", err
		}
		currentUsername, _ = record["Username"].(string)
		return currentUsername, nil
	}

	loadCurrentRoleID := func() (string, error) {
		if currentRoleLoaded {
			return currentRoleID, nil
		}
		currentRoleLoaded = true
		if user.RoleID != nil {
			currentRoleID = strings.TrimSpace(*user.RoleID)
		}
		if currentRoleID != "" {
			return currentRoleID, nil
		}
		record, err := loadCurrentUserRecord()
		if err != nil {
			return "", err
		}
		currentRoleID, _ = record[constants.FieldUserRoleID].(string)
		currentRoleID = strings.TrimSpace(currentRoleID)
		return currentRoleID, nil
	}

	loadRoleAncestors := func() (map[string]struct{}, error) {
		if roleAncestorsLoaded {
			return roleAncestors, nil
		}
		roleAncestorsLoaded = true
		roleID, err := loadCurrentRoleID()
		if err != nil {
			return nil, err
		}
		roleAncestors, err = s.userRoleAncestors(ctx, roleID, user)
		if err != nil {
			return nil, err
		}
		return roleAncestors, nil
	}

	for _, allowedSubmitter := range metadata.AllowedSubmitters {
		switch allowedSubmitter.Type {
		case constants.ApprovalSubmitterTypeAllInternalUsers:
			return true, nil

		case constants.ApprovalSubmitterTypeRecordCreator:
			record, err := loadTargetRecord()
			if err != nil {
				return false, err
			}
			if createdByID, _ := record[constants.FieldCreatedByID].(string); createdByID == user.ID {
				return true, nil
			}

		case constants.ApprovalSubmitterTypeRecordOwner:
			record, err := loadTargetRecord()
			if err != nil {
				return false, err
			}
			if ownerID, _ := record[constants.FieldOwnerID].(string); ownerID == user.ID {
				return true, nil
			}

		case constants.ApprovalSubmitterTypeUser:
			submitter := strings.TrimSpace(allowedSubmitter.Submitter)
			if submitter == "" {
				continue
			}
			if strings.EqualFold(submitter, user.ID) {
				return true, nil
			}
			if user.Email != nil && strings.EqualFold(submitter, strings.TrimSpace(*user.Email)) {
				return true, nil
			}
			username, err := loadCurrentUsername()
			if err != nil {
				return false, err
			}
			if username != "" && strings.EqualFold(submitter, username) {
				return true, nil
			}

		case constants.ApprovalSubmitterTypeRole:
			roleID, err := loadCurrentRoleID()
			if err != nil {
				return false, err
			}
			if roleID != "" && strings.EqualFold(strings.TrimSpace(allowedSubmitter.Submitter), roleID) {
				return true, nil
			}

		case constants.ApprovalSubmitterTypeRoleAndSubordinates:
			roleID, err := loadCurrentRoleID()
			if err != nil {
				return false, err
			}
			targetRoleID := strings.TrimSpace(allowedSubmitter.Submitter)
			if roleID == "" || targetRoleID == "" {
				continue
			}
			if strings.EqualFold(targetRoleID, roleID) {
				return true, nil
			}
			ancestors, err := loadRoleAncestors()
			if err != nil {
				return false, err
			}
			if _, ok := ancestors[targetRoleID]; ok {
				return true, nil
			}
		}
	}

	return false, nil
}

func (s *ApprovalService) resolveApproverIDsForStepWithContext(
	ctx context.Context,
	metadata *NormalizedApprovalProcess,
	step *NormalizedApprovalStep,
	targetRecordID, objectName string,
	user *models.UserSession,
	resolutionContext approvalApproverResolutionContext,
) ([]string, error) {
	if step == nil {
		return nil, nil
	}

	seen := make(map[string]struct{})
	var resolved []string
	for _, approver := range step.Approvers {
		approverIDs, err := s.resolveApproverActorIDsWithContext(ctx, metadata, approver.Type, approver.Name, targetRecordID, objectName, user, resolutionContext)
		if err != nil {
			return nil, err
		}
		if len(approverIDs) == 0 {
			return nil, newApproverResolutionError(constants.NormalizeApprovalApproverType(approver.Type), strings.TrimSpace(approver.Name), approvalMetadataStepDisplayName(step))
		}
		for _, approverID := range approverIDs {
			if approverID == "" {
				continue
			}
			if _, exists := seen[approverID]; exists {
				continue
			}
			seen[approverID] = struct{}{}
			resolved = append(resolved, approverID)
		}
	}
	return resolved, nil
}

func (s *ApprovalService) resolveApproverActorIDsWithContext(
	ctx context.Context,
	metadata *NormalizedApprovalProcess,
	approverType, approverValue, targetRecordID, objectName string,
	user *models.UserSession,
	resolutionContext approvalApproverResolutionContext,
) ([]string, error) {
	normalizedType := constants.NormalizeApprovalApproverType(approverType)
	normalizedValue := strings.TrimSpace(approverValue)
	switch normalizedType {
	case constants.ApprovalApproverTypeQueue:
		return s.resolveQueueApproverIDs(ctx, normalizedValue, objectName, user)
	case constants.ApprovalApproverTypeRole:
		return s.resolveRoleApproverIDs(ctx, normalizedValue, user)
	default:
		approverID, err := s.resolveApproverIdentifierWithContext(ctx, metadata, approverType, approverValue, targetRecordID, objectName, user, resolutionContext)
		if err != nil || approverID == "" {
			return nil, err
		}
		return []string{approverID}, nil
	}
}

func (s *ApprovalService) resolveApproverIdentifierWithContext(
	ctx context.Context,
	metadata *NormalizedApprovalProcess,
	approverType, approverValue, targetRecordID, objectName string,
	user *models.UserSession,
	resolutionContext approvalApproverResolutionContext,
) (string, error) {
	normalizedType := constants.NormalizeApprovalApproverType(approverType)
	normalizedValue := strings.TrimSpace(approverValue)
	if normalizedType != constants.ApprovalApproverTypeHierarchy && normalizedValue == "" {
		return "", nil
	}

	switch normalizedType {
	case constants.ApprovalApproverTypeHierarchy:
		return s.resolveHierarchyApproverID(ctx, metadata, resolutionContext, targetRecordID, objectName, user)
	case constants.ApprovalApproverTypeUser:
		return s.resolveActiveApprovalUserReference(ctx, normalizedValue, user)

	case constants.ApprovalApproverTypeRelatedUserField:
		if targetRecordID == "" || objectName == "" {
			return "", nil
		}
		recordFilter := fmt.Sprintf("Id == '%s'", escapeApprovalFilterValue(targetRecordID))
		recordResult, err := s.query.QueryWithFilter(ctx, objectName, recordFilter, user, "", "", 1)
		if err != nil {
			return "", err
		}
		if recordResult == nil || len(recordResult.Records) == 0 {
			return "", nil
		}
		relatedUserID, _ := recordResult.Records[0][normalizedValue].(string)
		return relatedUserID, nil

	case constants.ApprovalApproverTypeUserHierarchyField:
		// Handle userHierarchyField type - similar to hierarchy but using the field from metadata
		return s.resolveHierarchyApproverID(ctx, metadata, resolutionContext, targetRecordID, objectName, user)

	case constants.ApprovalApproverTypePreviousApprover:
		// Handle PreviousApprover type - return the previous actor ID from context
		return resolutionContext.previousActorID, nil

	case constants.ApprovalApproverTypeAdhoc:
		// Handle adhoc type - treat the value as a user ID to be returned directly
		if normalizedValue == "" {
			return "", nil
		}
		return normalizedValue, nil

	default:
		return "", nil
	}
}

func (s *ApprovalService) resolveQueueApproverIDs(ctx context.Context, queueRef, objectName string, user *models.UserSession) ([]string, error) {
	queueID, err := s.resolveQueueGroupIDReference(ctx, queueRef, user)
	if err != nil || queueID == "" {
		return nil, err
	}
	if strings.TrimSpace(objectName) != "" {
		supported, err := s.queueSupportsObject(ctx, queueID, objectName, user)
		if err != nil || !supported {
			return nil, err
		}
	}
	return s.resolveGroupMemberUserIDs(ctx, queueID, user)
}

func (s *ApprovalService) resolveRoleApproverIDs(ctx context.Context, roleRef string, user *models.UserSession) ([]string, error) {
	roleID, err := s.resolveUserRoleIDReference(ctx, roleRef, user)
	if err != nil || roleID == "" {
		return nil, err
	}
	return s.resolveRoleUserIDs(ctx, roleID, user)
}

func (s *ApprovalService) resolveQueueGroupIDReference(ctx context.Context, queueRef string, user *models.UserSession) (string, error) {
	normalizedRef := strings.TrimSpace(queueRef)
	if normalizedRef == "" {
		return "", nil
	}

	safeRef := escapeApprovalFilterValue(normalizedRef)
	groupRecord, err := s.querySingleByFilter(ctx, constants.TableGroup, fmt.Sprintf("%s == '%s'", constants.FieldID, safeRef), user)
	if err != nil || groupRecord != nil {
		return approvalLiveQueueGroupID(groupRecord), err
	}

	for _, fieldName := range []string{constants.FieldGroup_DeveloperName, constants.FieldGroup_Name} {
		groupRecord, err = s.querySingleByFilter(ctx, constants.TableGroup, fmt.Sprintf("%s == '%s'", fieldName, safeRef), user)
		if err != nil || groupRecord != nil {
			return approvalLiveQueueGroupID(groupRecord), err
		}
	}
	return "", nil
}

func approvalLiveQueueGroupID(record models.SObject) string {
	if record == nil || approvalBoolValue(record[constants.FieldGroup_IsDeleted]) {
		return ""
	}
	groupType := strings.TrimSpace(approvalStringValue(record[constants.FieldGroup_Type]))
	if !strings.EqualFold(groupType, constants.GroupTypeQueue) {
		return ""
	}
	return strings.TrimSpace(approvalStringValue(record[constants.FieldID]))
}

func (s *ApprovalService) queueSupportsObject(ctx context.Context, queueID, objectName string, user *models.UserSession) (bool, error) {
	filter := fmt.Sprintf(
		"%s == '%s' AND %s == '%s'",
		constants.FieldQueueSobject_QueueID,
		escapeApprovalFilterValue(queueID),
		constants.FieldQueueSobject_SobjectType,
		escapeApprovalFilterValue(objectName),
	)
	result, err := s.query.QueryWithFilter(ctx, constants.TableQueueSobject, filter, user, "", "", 1)
	if err != nil {
		return false, err
	}
	return result != nil && len(result.Records) > 0, nil
}

func (s *ApprovalService) resolveGroupMemberUserIDs(ctx context.Context, groupID string, user *models.UserSession) ([]string, error) {
	filter := fmt.Sprintf(
		"%s == '%s' AND %s == '%s'",
		constants.FieldSysGroupMembershipClosure_GroupID,
		escapeApprovalFilterValue(groupID),
		constants.FieldSysGroupMembershipClosure_MemberType,
		escapeApprovalFilterValue(constants.GroupMembershipClosureMemberTypeUser),
	)
	records, err := s.queryAllPaged(ctx, constants.TableGroupMembershipClosure, filter, user)
	if err != nil {
		return nil, err
	}
	if len(records) == 0 {
		return nil, nil
	}

	seen := make(map[string]struct{})
	actorIDs := make([]string, 0, len(records))
	for _, member := range records {
		memberID := strings.TrimSpace(approvalStringValue(member[constants.FieldSysGroupMembershipClosure_MemberID]))
		actorID, _, err := s.resolveActiveApprovalUserExactID(ctx, memberID, user)
		if err != nil {
			return nil, err
		}
		if actorID == "" {
			continue
		}
		if _, exists := seen[actorID]; exists {
			continue
		}
		seen[actorID] = struct{}{}
		actorIDs = append(actorIDs, actorID)
	}
	return actorIDs, nil
}

func (s *ApprovalService) resolveUserRoleIDReference(ctx context.Context, roleRef string, user *models.UserSession) (string, error) {
	normalizedRef := strings.TrimSpace(roleRef)
	if normalizedRef == "" {
		return "", nil
	}

	safeRef := escapeApprovalFilterValue(normalizedRef)
	roleRecord, err := s.querySingleByFilter(ctx, constants.TableUserRole, fmt.Sprintf("%s == '%s'", constants.FieldID, safeRef), user)
	if err != nil || roleRecord != nil {
		return approvalLiveUserRoleID(roleRecord), err
	}

	roleRecord, err = s.querySingleByFilter(ctx, constants.TableUserRole, fmt.Sprintf("%s == '%s'", constants.FieldUserRole_Name, safeRef), user)
	if err != nil || roleRecord != nil {
		return approvalLiveUserRoleID(roleRecord), err
	}
	return "", nil
}

func approvalLiveUserRoleID(record models.SObject) string {
	if record == nil || approvalBoolValue(record[constants.FieldUserRole_IsDeleted]) {
		return ""
	}
	return strings.TrimSpace(approvalStringValue(record[constants.FieldID]))
}

func (s *ApprovalService) resolveRoleUserIDs(ctx context.Context, roleID string, user *models.UserSession) ([]string, error) {
	filter := fmt.Sprintf("%s == '%s'", constants.FieldUserRoleID, escapeApprovalFilterValue(roleID))
	records, err := s.queryAllPaged(ctx, constants.TableUser, filter, user)
	if err != nil {
		return nil, err
	}
	if len(records) == 0 {
		return nil, nil
	}

	seen := make(map[string]struct{})
	actorIDs := make([]string, 0, len(records))
	for _, record := range records {
		actorID := approvableApprovalUserID(record)
		if actorID == "" {
			continue
		}
		if _, exists := seen[actorID]; exists {
			continue
		}
		seen[actorID] = struct{}{}
		actorIDs = append(actorIDs, actorID)
	}
	return actorIDs, nil
}

func (s *ApprovalService) querySingleByFilter(ctx context.Context, objectName, filterExpr string, user *models.UserSession) (models.SObject, error) {
	result, err := s.query.QueryWithFilter(ctx, objectName, filterExpr, user, "", "", 1)
	if err != nil {
		return nil, err
	}
	if result == nil || len(result.Records) == 0 {
		return nil, nil
	}
	return result.Records[0], nil
}

func (s *ApprovalService) userRoleAncestors(ctx context.Context, roleID string, user *models.UserSession) (map[string]struct{}, error) {
	ancestors := make(map[string]struct{})
	currentRoleID := strings.TrimSpace(roleID)
	visited := make(map[string]struct{})

	for currentRoleID != "" {
		if _, seen := visited[currentRoleID]; seen {
			break
		}
		visited[currentRoleID] = struct{}{}

		roleRecord, err := s.querySingleByFilter(ctx, constants.TableUserRole, fmt.Sprintf("Id == '%s'", escapeApprovalFilterValue(currentRoleID)), user)
		if err != nil {
			return nil, err
		}
		if roleRecord == nil {
			break
		}

		parentRoleID, _ := roleRecord[constants.FieldUserRole_ParentRoleID].(string)
		parentRoleID = strings.TrimSpace(parentRoleID)
		if parentRoleID == "" {
			break
		}

		ancestors[parentRoleID] = struct{}{}
		currentRoleID = parentRoleID
	}

	return ancestors, nil
}

func escapeApprovalFilterValue(value string) string {
	return strings.ReplaceAll(value, "'", "''")
}

func approvalUserCanApprove(record models.SObject) bool {
	if record == nil {
		return false
	}
	if approvalBoolValue(record["IsDeleted"]) {
		return false
	}
	if _, ok := record["IsActive"]; ok && !approvalBoolValue(record["IsActive"]) {
		return false
	}
	return true
}

func (s *ApprovalService) resolveActiveApprovalUserReference(ctx context.Context, ref string, user *models.UserSession) (string, error) {
	normalizedRef := strings.TrimSpace(ref)
	if normalizedRef == "" {
		return "", nil
	}

	uid, found, err := s.resolveActiveApprovalUserExactID(ctx, normalizedRef, user)
	if err != nil {
		return "", err
	}
	if found {
		return uid, nil
	}

	safeRef := escapeApprovalFilterValue(normalizedRef)
	usernameFilter := fmt.Sprintf("Username == '%s'", safeRef)
	userResult, err := s.query.QueryWithFilter(ctx, constants.TableUser, usernameFilter, user, "", "", 1)
	if err != nil {
		return "", err
	}
	if userResult == nil || len(userResult.Records) == 0 {
		return "", nil
	}
	return approvableApprovalUserID(userResult.Records[0]), nil
}

func (s *ApprovalService) resolveActiveApprovalUserExactID(ctx context.Context, userID string, user *models.UserSession) (string, bool, error) {
	normalizedUserID := strings.TrimSpace(userID)
	if normalizedUserID == "" {
		return "", false, nil
	}
	idFilter := fmt.Sprintf("Id == '%s'", escapeApprovalFilterValue(normalizedUserID))
	userResult, err := s.query.QueryWithFilter(ctx, constants.TableUser, idFilter, user, "", "", 1)
	if err != nil {
		return "", false, err
	}
	if userResult == nil || len(userResult.Records) == 0 {
		return "", false, nil
	}
	return approvableApprovalUserID(userResult.Records[0]), true, nil
}

func approvableApprovalUserID(record models.SObject) string {
	if !approvalUserCanApprove(record) {
		return ""
	}
	uid, _ := record[constants.FieldID].(string)
	return strings.TrimSpace(uid)
}

func (s *ApprovalService) resolveHierarchyApproverID(
	ctx context.Context,
	metadata *NormalizedApprovalProcess,
	resolutionContext approvalApproverResolutionContext,
	targetRecordID, objectName string,
	user *models.UserSession,
) (string, error) {
	seedUserID := strings.TrimSpace(resolutionContext.previousActorID)
	if seedUserID == "" {
		// First step: decide whether to seed from submitter or record owner.
		if metadata != nil && metadata.NextAutomatedApprover != nil && metadata.NextAutomatedApprover.UseApproverFieldOfRecordOwner {
			if targetRecordID != "" && objectName != "" {
				recordFilter := fmt.Sprintf("Id == '%s'", escapeApprovalFilterValue(targetRecordID))
				recordResult, err := s.query.QueryWithFilter(ctx, objectName, recordFilter, user, "", "", 1)
				if err == nil && recordResult != nil && len(recordResult.Records) > 0 {
					ownerID, _ := recordResult.Records[0]["OwnerId"].(string)
					seedUserID = strings.TrimSpace(ownerID)
				}
			}
		}
		if seedUserID == "" {
			seedUserID = strings.TrimSpace(resolutionContext.submitterID)
		}
	}
	if seedUserID == "" {
		return "", newHierarchyResolutionError("could not determine the seed user for manager hierarchy")
	}

	userHierarchyField := defaultHierarchyFieldName
	if metadata != nil && metadata.NextAutomatedApprover != nil && strings.TrimSpace(metadata.NextAutomatedApprover.UserHierarchyField) != "" {
		userHierarchyField = strings.TrimSpace(metadata.NextAutomatedApprover.UserHierarchyField)
	}

	userFilter := fmt.Sprintf("Id == '%s'", escapeApprovalFilterValue(seedUserID))
	userResult, err := s.query.QueryWithFilter(ctx, constants.TableUser, userFilter, user, "", "", 1)
	if err != nil || userResult == nil || len(userResult.Records) == 0 {
		return "", newHierarchyResolutionError(fmt.Sprintf("could not load seed user %s for manager hierarchy", seedUserID))
	}

	managerID, _ := userResult.Records[0][userHierarchyField].(string)
	if strings.TrimSpace(managerID) == "" {
		return "", newHierarchyResolutionError(fmt.Sprintf("user %s does not have %s populated", seedUserID, userHierarchyField))
	}
	return managerID, nil
}

func newHierarchyResolutionError(detail string) error {
	return apperrors.NewValidationError("nextAutomatedApprover", detail)
}

func (s *ApprovalService) previousActorIDForStepIndex(
	ctx context.Context,
	instanceID string,
	orderedNodes []models.SObject,
	stepIndex int,
	user *models.UserSession,
) (string, error) {
	if stepIndex <= 0 {
		return "", nil
	}

	nodeHistoryFilter := fmt.Sprintf("ProcessInstanceId == '%s'", escapeApprovalFilterValue(instanceID))
	nodeHistoryResult, err := s.query.QueryWithFilter(ctx, constants.TableProcessInstanceNode, nodeHistoryFilter, user, "", "", 100)
	if err != nil {
		return "", err
	}

	lastActorByNodeID := make(map[string]string, len(nodeHistoryResult.Records))
	for _, nodeHistory := range nodeHistoryResult.Records {
		processNodeID, _ := nodeHistory["ProcessNodeId"].(string)
		lastActorID, _ := nodeHistory["LastActorId"].(string)
		if processNodeID != "" && lastActorID != "" {
			lastActorByNodeID[processNodeID] = lastActorID
		}
	}

	previousNodeID, _ := orderedNodes[stepIndex-1][constants.FieldID].(string)
	return lastActorByNodeID[previousNodeID], nil
}

func (s *ApprovalService) createWorkitems(ctx context.Context, instanceID string, actorIDs []string, comments, now string, user *models.UserSession) ([]string, error) {
	workItemIDs := make([]string, 0, len(actorIDs))
	for _, actorID := range actorIDs {
		workItemRecord := models.SObject{
			"ProcessInstanceId": instanceID,
			"ActorId":           actorID,
			"OriginalActorId":   actorID,
			"CreatedDate":       now,
		}
		if comments != "" {
			workItemRecord["Comments"] = comments
		}
		createdWorkItem, err := s.persistence.Insert(processTableWriteContext(ctx, "ApprovalService: create ProcessInstanceWorkitem"), constants.TableProcessInstanceWorkitem, workItemRecord, user)
		if err != nil {
			return nil, err
		}
		workItemID, _ := createdWorkItem[constants.FieldID].(string)
		if workItemID != "" {
			workItemIDs = append(workItemIDs, workItemID)
		}
	}
	return workItemIDs, nil
}

func processTableWriteContext(ctx context.Context, reason string) context.Context {
	return constants.WithSecurityContext(ctx, constants.SystemContext, reason)
}
