package automation

import (
	"context"
	"fmt"
	"strings"

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

// Clone creates a duplicate of an existing approval process with all its steps and actions.
func (s *ApprovalService) Clone(ctx context.Context, processDefID, newDeveloperName, newLabel string, user *models.UserSession) (*models.SObject, error) {
	ctx = constants.WithSecurityContext(ctx, constants.SystemContext, "ApprovalService: managing approval system tables")

	if user == nil {
		return nil, apperrors.NewUnauthorizedError("authenticated user is required to clone approval processes")
	}
	if processDefID == "" {
		return nil, apperrors.NewValidationError("processDefID", "process definition ID is required")
	}
	if newDeveloperName == "" {
		return nil, apperrors.NewValidationError("newDeveloperName", "new developer name is required")
	}
	if newLabel == "" {
		return nil, apperrors.NewValidationError("newLabel", "new label is required")
	}
	if s.permissions == nil {
		return nil, apperrors.NewInternalError("permission service is required for approval process cloning", nil)
	}
	if !s.permissions.CheckSystemPermission(ctx, user, string(constants.PermModifyMetadata)) {
		return nil, apperrors.NewPermissionError("modify metadata for", "approval processes")
	}

	existingProcessQuery := fmt.Sprintf("DeveloperName == '%s'", escapeApprovalFilterValue(newDeveloperName))
	existingProcessResult, err := s.query.QueryWithFilter(ctx, constants.TableProcessDefinition, existingProcessQuery, user, "", "", 1)
	if err != nil {
		return nil, fmt.Errorf("failed to check for existing process with same DeveloperName: %w", err)
	}
	if len(existingProcessResult.Records) > 0 {
		return nil, apperrors.NewValidationError("newDeveloperName", "a process with this DeveloperName already exists")
	}

	// Load the source process through the query service because approval metadata
	// tables do not implement the generic soft-delete shape expected by GetRaw().
	originalProcessResult, err := s.query.QueryWithFilter(ctx, constants.TableProcessDefinition, fmt.Sprintf("Id == '%s'", escapeApprovalFilterValue(processDefID)), user, "", "", 1)
	if err != nil {
		return nil, fmt.Errorf("failed to read original process definition: %w", err)
	}
	if len(originalProcessResult.Records) == 0 {
		return nil, apperrors.NewNotFoundError(constants.TableProcessDefinition, processDefID)
	}
	originalProcess := originalProcessResult.Records[0]

	objectName, _ := originalProcess["TableEnumOrId"].(string)
	if objectName != "" {
		hasPermission := s.permissions.CheckObjectPermissionWithUser(ctx, objectName, constants.PermRead, user)
		if !hasPermission {
			return nil, apperrors.NewPermissionError("read", objectName)
		}
	}

	actionsQuery := fmt.Sprintf("ProcessDefinitionId == '%s'", escapeApprovalFilterValue(processDefID))
	actionsResult, err := s.query.QueryWithFilter(ctx, constants.TableProcessAction, actionsQuery, user, constants.FieldProcessAction_Name, "ASC", 100)
	if err != nil {
		return nil, fmt.Errorf("failed to query process actions for cloning: %w", err)
	}
	if err := rejectLegacyApprovalAlertCloneActions(actionsResult.Records); err != nil {
		return nil, err
	}

	var createdProcess models.SObject
	err = s.persistence.RunInTransaction(ctx, func(txCtx context.Context) error {
		newProcessDef := cloneApprovalMetadataRecord(originalProcess)
		newProcessDef["Name"] = newLabel
		newProcessDef["DeveloperName"] = newDeveloperName
		newProcessDef["State"] = "Inactive"
		newProcessDef["IsActive"] = false
		// A clone is admin-authored and must not inherit managed ownership from its source.
		newProcessDef[constants.FieldProcessDefinition_OwnerPack] = ""

		var insertErr error
		cloneProcessCtx := contract.WithRecordLifecycleHooksSuppressed(processTableWriteContext(txCtx, "ApprovalService: clone ProcessDefinition"))
		createdProcess, insertErr = s.persistence.Insert(cloneProcessCtx, constants.TableProcessDefinition, newProcessDef, user)
		if insertErr != nil {
			return fmt.Errorf("failed to create cloned process definition: %w", insertErr)
		}

		newProcessDefId, _ := createdProcess[constants.FieldID].(string)

		nodesQuery := fmt.Sprintf("ProcessDefinitionId == '%s'", escapeApprovalFilterValue(processDefID))
		nodesResult, queryErr := s.query.QueryWithFilter(txCtx, constants.TableProcessNode, nodesQuery, user, "StepOrder", "ASC", 100)
		if queryErr != nil {
			return fmt.Errorf("failed to query process nodes for cloning: %w", queryErr)
		}

		nodeIDMap := make(map[string]string, len(nodesResult.Records))
		for _, node := range nodesResult.Records {
			oldNodeID, _ := node[constants.FieldID].(string)
			newNode := cloneApprovalMetadataRecord(node)
			newNode["ProcessDefinitionId"] = newProcessDefId

			createdNode, nodeErr := s.persistence.Insert(processTableWriteContext(txCtx, "ApprovalService: clone ProcessNode"), constants.TableProcessNode, newNode, user)
			if nodeErr != nil {
				return fmt.Errorf("failed to create cloned process node: %w", nodeErr)
			}

			if newNodeID, _ := createdNode[constants.FieldID].(string); oldNodeID != "" && newNodeID != "" {
				nodeIDMap[oldNodeID] = newNodeID
			}
		}

		for _, action := range actionsResult.Records {
			newAction := cloneApprovalMetadataRecord(action)
			newAction["ProcessDefinitionId"] = newProcessDefId

			if oldNodeID, _ := action["ProcessNodeId"].(string); oldNodeID != "" {
				newNodeID, ok := nodeIDMap[oldNodeID]
				if !ok {
					actionName, _ := action["Name"].(string)
					if actionName == "" {
						actionName = oldNodeID
					}
					return fmt.Errorf("failed to remap ProcessNodeId %q for cloned action %q", oldNodeID, actionName)
				}
				newAction["ProcessNodeId"] = newNodeID
			}

			if _, actionErr := s.persistence.Insert(processTableWriteContext(txCtx, "ApprovalService: clone ProcessAction"), constants.TableProcessAction, newAction, user); actionErr != nil {
				return fmt.Errorf("failed to create cloned process action: %w", actionErr)
			}
		}

		return nil
	})
	if err != nil {
		return nil, err
	}

	return &createdProcess, nil
}

func rejectLegacyApprovalAlertCloneActions(actions []models.SObject) error {
	for _, action := range actions {
		actionType := strings.TrimSpace(approvalStringValue(action[constants.FieldProcessAction_ActionType]))
		if !strings.EqualFold(actionType, constants.ProcessActionTypeAlert) {
			continue
		}
		actionName := firstNonEmptyApprovalValue(
			action[constants.FieldProcessAction_Name],
			action[constants.FieldProcessAction_APIName],
			action[constants.FieldID],
		)
		if actionName == "" {
			actionName = "unnamed action"
		}
		return apperrors.NewValidationError(
			constants.FieldProcessAction_ActionType,
			fmt.Sprintf("approval process clone cannot copy retired ProcessAction.Alert action %q; %s", actionName, workflowAlertRuntimeDisabledReason),
		)
	}
	return nil
}

func cloneApprovalMetadataRecord(source models.SObject) models.SObject {
	cloned := make(models.SObject, len(source))
	for k, v := range source {
		if k == constants.FieldID ||
			k == constants.FieldCreatedDate ||
			k == constants.FieldCreatedByID ||
			k == constants.FieldLastModifiedDate ||
			k == constants.FieldLastModifiedByID ||
			strings.HasSuffix(k, "_Name") ||
			k == "attributes" {
			continue
		}

		if _, isNested := v.(map[string]interface{}); isNested {
			continue
		}
		cloned[k] = v
	}
	return cloned
}
