package automation

import (
	"context"
	"fmt"
	"strings"

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

const defaultHierarchyFieldName = "ManagerId"

type approvalApproverResolutionContext struct {
	submitterID     string
	previousActorID string
}

func (s *ApprovalService) approvalMetadataForDefinition(def models.SObject) *NormalizedApprovalProcess {
	if def == nil {
		return nil
	}
	raw, _ := def["Metadata"].(string)
	parsed, err := ParseApprovalProcessMetadata(raw)
	if err != nil {
		s.logger.Warn(fmt.Sprintf("Failed to parse ProcessDefinition metadata: %v", err))
		return nil
	}
	return parsed
}

func (s *ApprovalService) metadataStepForNode(def models.SObject, node models.SObject) *NormalizedApprovalStep {
	metadata := s.approvalMetadataForDefinition(def)
	if metadata == nil || node == nil {
		return nil
	}
	developerName, _ := node["DeveloperName"].(string)
	label, _ := node["Name"].(string)
	return metadata.StepForNames(developerName, label)
}

func (s *ApprovalService) resolveApproverIDsForNode(ctx context.Context, def models.SObject, node models.SObject, targetRecordID string, user *models.UserSession) []string {
	ids, _ := s.resolveApproverIDsForNodeWithContext(ctx, def, node, targetRecordID, user, approvalApproverResolutionContext{})
	return ids
}

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 := s.approvalMetadataForDefinition(def)
	if step := s.metadataStepForNode(def, 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
		}
	}

	approverType, _ := node["ApproverType"].(string)
	if strings.EqualFold(strings.TrimSpace(approverType), "hierarchy") {
		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
	if multiFallback := s.resolveApproverIDsFromNode(ctx, node, targetRecordID, objectName, user); len(multiFallback) > 0 {
		return multiFallback, nil
	}

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

func (s *ApprovalService) approvalModeForNode(def models.SObject, node models.SObject) string {
	if step := s.metadataStepForNode(def, node); step != nil {
		return step.WhenMultipleApprovers
	}
	return "FirstResponse"
}

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

	metadata := s.approvalMetadataForDefinition(def)
	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 "allInternalUsers":
			return true, nil

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

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

		case "user":
			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 "role":
			roleID, err := loadCurrentRoleID()
			if err != nil {
				return false, err
			}
			if roleID != "" && strings.EqualFold(strings.TrimSpace(allowedSubmitter.Submitter), roleID) {
				return true, nil
			}

		case "roleAndSubordinates":
			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) resolveApproverIDsForStep(ctx context.Context, step *NormalizedApprovalStep, targetRecordID, objectName string, user *models.UserSession) []string {
	ids, _ := s.resolveApproverIDsForStepWithContext(ctx, nil, step, targetRecordID, objectName, user, approvalApproverResolutionContext{})
	return ids
}

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 {
		approverID, err := s.resolveApproverIdentifierWithContext(ctx, metadata, approver.Type, approver.Name, targetRecordID, objectName, user, resolutionContext)
		if err != nil {
			return nil, err
		}
		if approverID == "" {
			continue
		}
		if _, exists := seen[approverID]; exists {
			continue
		}
		seen[approverID] = struct{}{}
		resolved = append(resolved, approverID)
	}
	return resolved, nil
}

func (s *ApprovalService) resolveApproverIdentifier(ctx context.Context, approverType, approverValue, targetRecordID, objectName string, user *models.UserSession) string {
	approverID, _ := s.resolveApproverIdentifierWithContext(ctx, nil, approverType, approverValue, targetRecordID, objectName, user, approvalApproverResolutionContext{})
	return approverID
}

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

	switch normalizedType {
	case "hierarchy":
		return s.resolveHierarchyApproverID(ctx, metadata, resolutionContext, targetRecordID, objectName, user)
	case "user":
		safeValue := strings.ReplaceAll(normalizedValue, "'", "''")
		queries := []string{
			fmt.Sprintf("Id == '%s'", safeValue),
			fmt.Sprintf("Username == '%s'", safeValue),
		}
		for _, filter := range queries {
			userResult, err := s.query.QueryWithFilter(ctx, constants.TableUser, filter, user, "", "", 1)
			if err != nil || userResult == nil || len(userResult.Records) == 0 {
				continue
			}
			uid, _ := userResult.Records[0][constants.FieldID].(string)
			if uid != "" {
				return uid, nil
			}
		}
		return "", nil

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

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

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

	case "adhoc":
		// 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) 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 (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'", strings.ReplaceAll(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'", strings.ReplaceAll(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'", 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)
}
