package automation

import (
	"context"
	"fmt"
	"strings"

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

// executeTransferRecordOwner is the app-blind, transaction-bound mechanism
// used by product-owned Change Owner flows.  Object, field, principal, and
// permission identities are immutable Flow inputs; Runtime owns only the
// authorization and record mutation semantics.  Outbound notification is not
// part of this bounded-atomic mechanism and therefore fails closed.
func executeTransferRecordOwner(
	ctx context.Context,
	executor *FlowExecutor,
	interview *models.FlowInterview,
	_ *models.FlowMetadata,
	action *models.FlowActionCall,
	user *models.UserSession,
) (string, error) {
	fault := func(err error) (string, error) {
		return executor.RouteActionCallFault(ctx, interview, action, err)
	}
	params, err := executor.ResolveActionCallInputParameters(ctx, interview, action, user)
	if err != nil {
		return fault(err)
	}
	objectAPIName := transferRecordOwnerString(params, "objectApiName")
	recordID := transferRecordOwnerString(params, "recordId")
	newOwnerID := transferRecordOwnerString(params, "newOwnerId")
	ownerFieldAPIName := transferRecordOwnerString(params, "ownerFieldApiName")
	principalObjectAPIName := transferRecordOwnerString(params, "principalObjectApiName")
	principalActiveFieldAPIName := transferRecordOwnerString(params, "principalActiveFieldApiName")
	globalTransferCapability := transferRecordOwnerString(params, "globalTransferCapability")
	objectTransferCapability := transferRecordOwnerString(params, "objectTransferCapability")

	for name, value := range map[string]string{
		"objectApiName": objectAPIName, "recordId": recordID, "newOwnerId": newOwnerID,
		"ownerFieldApiName": ownerFieldAPIName, "principalObjectApiName": principalObjectAPIName,
		"principalActiveFieldApiName": principalActiveFieldAPIName,
	} {
		if value == "" || !identifierRegex.MatchString(value) {
			return fault(fmt.Errorf("transferRecordOwner requires a valid %s", name))
		}
	}
	if transferRecordOwnerBool(params["sendNotification"]) {
		return fault(fmt.Errorf("transferRecordOwner notification requires a durable post-commit owner"))
	}

	deps := executor.StandardActionContractDeps()
	policy, _ := deps.Policy.(contract.CapabilityPolicy)
	if deps.Records == nil || policy == nil {
		return fault(fmt.Errorf("transferRecordOwner runtime contracts are unavailable"))
	}
	record, err := deps.Records.Retrieve(ctx, objectAPIName, recordID, user)
	if err != nil || len(record) == 0 {
		return fault(fmt.Errorf("transferRecordOwner cannot read %s/%s", objectAPIName, recordID))
	}
	previousOwnerID, _ := record[ownerFieldAPIName].(string)
	previousOwnerID = strings.TrimSpace(previousOwnerID)
	updateCtx := ctx
	if user == nil || previousOwnerID != strings.TrimSpace(user.ID) {
		allowed := false
		for _, capability := range []string{globalTransferCapability, objectTransferCapability} {
			capability = strings.TrimSpace(capability)
			if capability != "" && policy.HasCapability(ctx, user, contract.Capability(capability)) {
				allowed = true
				break
			}
		}
		if !allowed {
			return fault(fmt.Errorf("permission denied: caller cannot transfer a record it does not own"))
		}
		// Preserve the legacy invariant exactly: owning callers use their normal
		// Edit authority; only a caller proven to hold a transfer capability gets
		// a transaction-local Edit exception for this one authored object.
		actionID := constants.StandardActionExecutionActionID(ctx)
		updateCtx = constants.WithStandardActionInternalAccessScopes(ctx, actionID, []constants.StandardActionInternalAccessScope{{
			StandardActionID: actionID,
			ObjectAPIName:    objectAPIName,
			Operation:        constants.PermEdit,
		}})
	}
	newOwner, err := deps.Records.Retrieve(ctx, principalObjectAPIName, newOwnerID, user)
	resolvedNewOwnerID, _ := newOwner[constants.FieldID].(string)
	if err != nil || len(newOwner) == 0 || strings.TrimSpace(resolvedNewOwnerID) == "" {
		return fault(fmt.Errorf("newOwnerId does not refer to an accessible principal"))
	}
	if !transferRecordOwnerBool(newOwner[principalActiveFieldAPIName]) {
		return fault(fmt.Errorf("newOwnerId refers to an inactive principal"))
	}
	if err := deps.Records.Update(updateCtx, objectAPIName, recordID, contract.Record{ownerFieldAPIName: newOwnerID}, user); err != nil {
		return fault(fmt.Errorf("transferRecordOwner update failed: %w", flowRecordMutationValidation(err)))
	}
	executor.AssignActionCallOutputs(interview, action, map[string]interface{}{
		"recordId": recordID, "previousOwnerId": previousOwnerID, "newOwnerId": newOwnerID,
	})
	if action.Connector != nil {
		return action.Connector.TargetReference, nil
	}
	return "", nil
}

func transferRecordOwnerString(params map[string]interface{}, name string) string {
	value, _ := params[name].(string)
	return strings.TrimSpace(value)
}

func transferRecordOwnerBool(value interface{}) bool {
	switch typed := value.(type) {
	case bool:
		return typed
	case string:
		return strings.EqualFold(strings.TrimSpace(typed), "true") || strings.TrimSpace(typed) == "1"
	case int:
		return typed == 1
	case int64:
		return typed == 1
	case float64:
		return typed == 1
	default:
		return false
	}
}
