package automation

import (
	"context"
	"fmt"
	"strings"

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

const (
	ConnectorInboundRemoteIdentityRecoveryStateRecovered = "recovered"
	ConnectorInboundRemoteIdentityRecoveryStateNotFound  = "not_found"
)

// ConnectorInboundRemoteIdentityRecoveryLockedRecordInput is the neutral
// source-row projection passed to an installed product lifecycle validator.
// The record was read with GetLock inside the recovery service transaction.
type ConnectorInboundRemoteIdentityRecoveryLockedRecordInput struct {
	ObjectName                 string
	RecordID                   string
	Record                     models.Record
	RemoteIDField              string
	RemoteID                   string
	RequiredFalseBooleanFields []string
}

// ConnectorInboundRemoteIdentityRecoveryLockedRecordValidator keeps product
// lifecycle rules outside the generic recovery engine. Implementations must be
// pure: the action owns the locked read and the recovery service owns mutation.
type ConnectorInboundRemoteIdentityRecoveryLockedRecordValidator interface {
	ValidateConnectorInboundRemoteIdentityRecoveryLockedRecord(
		input ConnectorInboundRemoteIdentityRecoveryLockedRecordInput,
	) (ConnectorPushCompatibilityMirrorStatus, error)
}

// NewConnectorInboundRemoteIdentityRecoveryFlowActionHandler binds an
// app-owned action descriptor to the generic recovery executor. actionType is
// descriptor-owned rather than hard-coded in L0.
func NewConnectorInboundRemoteIdentityRecoveryFlowActionHandler(
	actionType string,
	validator ConnectorInboundRemoteIdentityRecoveryLockedRecordValidator,
) FlowActionCallHandler {
	expectedActionType := normalizeFlowActionType(actionType)
	return func(
		ctx context.Context,
		executor *FlowExecutor,
		interview *models.FlowInterview,
		_ *models.FlowMetadata,
		action *models.FlowActionCall,
		user *models.UserSession,
	) (string, error) {
		return executor.executeRecoverConnectorRemoteIdentity(ctx, interview, action, user, expectedActionType, validator)
	}
}

type connectorInboundRemoteIdentityRecoverer interface {
	Recover(
		ctx context.Context,
		request ConnectorInboundRemoteIdentityRecoveryRequest,
	) (*ConnectorInboundRemoteIdentityRecoveryResult, error)
}

type connectorInboundRemoteIdentityRecovererFactory func() (connectorInboundRemoteIdentityRecoverer, error)

type connectorInboundRemoteIdentityRecoveryValueResolver func(value interface{}) (interface{}, error)

func (e *FlowExecutor) executeRecoverConnectorRemoteIdentity(
	ctx context.Context,
	interview *models.FlowInterview,
	action *models.FlowActionCall,
	user *models.UserSession,
	expectedActionType string,
	validator ConnectorInboundRemoteIdentityRecoveryLockedRecordValidator,
) (string, error) {
	return e.executeRecoverConnectorRemoteIdentityWithFactory(ctx, interview, action, user, expectedActionType, func() (connectorInboundRemoteIdentityRecoverer, error) {
		return e.newConnectorInboundRemoteIdentityRecoverer(validator)
	})
}

func (e *FlowExecutor) executeRecoverConnectorRemoteIdentityWithFactory(
	ctx context.Context,
	interview *models.FlowInterview,
	action *models.FlowActionCall,
	user *models.UserSession,
	expectedActionType string,
	build connectorInboundRemoteIdentityRecovererFactory,
) (string, error) {
	return e.executeRecoverConnectorRemoteIdentityWithDependencies(
		ctx,
		interview,
		action,
		user,
		expectedActionType,
		build,
		func(value interface{}) (interface{}, error) {
			resolved, _, err := e.ResolveFlowResource(ctx, interview, user, value)
			return resolved, err
		},
	)
}

func (e *FlowExecutor) executeRecoverConnectorRemoteIdentityWithDependencies(
	ctx context.Context,
	interview *models.FlowInterview,
	action *models.FlowActionCall,
	user *models.UserSession,
	expectedActionType string,
	build connectorInboundRemoteIdentityRecovererFactory,
	resolve connectorInboundRemoteIdentityRecoveryValueResolver,
) (string, error) {
	next := ""
	if action != nil && action.Connector != nil {
		next = action.Connector.TargetReference
	}
	fault := func(err error) (string, error) {
		if e == nil {
			return "", err
		}
		return e.RouteActionCallFault(ctx, interview, action, err)
	}

	// These gates deliberately precede every dependency lookup and recovery
	// service call. The service owns a fresh short transaction and must never be
	// reached from preview or an inherited Flow transaction.
	if e == nil {
		return "", fmt.Errorf("recover connector remote identity: Flow executor is not wired")
	}
	if isPreviewMode(ctx) {
		return fault(fmt.Errorf("recover connector remote identity: preview execution is not allowed"))
	}
	if isWithinAtomicFlowBoundary(ctx) {
		return fault(fmt.Errorf("recover connector remote identity: atomic Flow execution is not allowed"))
	}
	if e.persistence == nil {
		return fault(fmt.Errorf("recover connector remote identity: persistence is not wired"))
	}
	if e.persistence.IsTransactionActive(ctx) {
		return fault(fmt.Errorf("recover connector remote identity: ambient transaction is not allowed"))
	}

	flowIdentity, ok := flowRuntimeContextFromCtx(ctx).CurrentFlow()
	if !ok || !flowIdentityIsPackOwnedStandard(flowIdentity) || strings.TrimSpace(flowIdentity.OwnerPack) == "" {
		return fault(fmt.Errorf("recover connector remote identity: Flow must be an uncustomized pack-owned standard FlowVersion"))
	}
	connectorNamespace := constants.ConnectorSyncOriginFromContext(ctx)
	if connectorNamespace == "" {
		return fault(fmt.Errorf("recover connector remote identity: authenticated connector inbound namespace is required"))
	}
	connectorRootAuthorized := constants.ConnectorInboundExecutionAuthorizesFlow(ctx, flowIdentity.APIName, connectorNamespace) &&
		strings.EqualFold(strings.TrimSpace(flowIdentity.OwnerPack), connectorNamespace)
	connectorInboundAuthorized := connectorRootAuthorized || constants.ConnectorInboundCompanionExecutionAuthorizesVersion(ctx, flowIdentity.VersionID)
	standardActionAuthorized := constants.HasStandardActionExecutionContext(ctx) &&
		constants.HasStandardActionProtectedFlowExecution(ctx)
	if !connectorInboundAuthorized && !standardActionAuthorized {
		return fault(fmt.Errorf("recover connector remote identity: requires the authenticated connector root, exact companion version, or protected standard action context"))
	}

	if resolve == nil {
		return fault(fmt.Errorf("recover connector remote identity: dynamic value resolver is not wired"))
	}
	params, err := parseConnectorInboundRemoteIdentityRecoveryAction(action, expectedActionType, resolve)
	if err != nil {
		return fault(err)
	}
	if build == nil {
		return fault(fmt.Errorf("recover connector remote identity: recovery service factory is not wired"))
	}
	recoverer, err := build()
	if err != nil {
		return fault(err)
	}
	if recoverer == nil {
		return fault(fmt.Errorf("recover connector remote identity: recovery service is not wired"))
	}

	result, err := recoverer.Recover(ctx, ConnectorInboundRemoteIdentityRecoveryRequest{
		Selector: ConnectorInboundRemoteIdentityRecoverySelector{
			ConnectorOwnerKey:  strings.TrimSpace(flowIdentity.OwnerPack),
			ConnectorNamespace: connectorNamespace,
			DestinationSlotID:  params.destinationSlotID,
			RemoteID:           params.remoteID,
		},
	})
	if err != nil {
		return fault(fmt.Errorf("recover connector remote identity: %w", err))
	}
	state := ConnectorInboundRemoteIdentityRecoveryStateNotFound
	if result != nil {
		if strings.TrimSpace(result.EvidenceID) == "" || strings.TrimSpace(result.LinkKey) == "" ||
			strings.TrimSpace(result.SourceObject) == "" || strings.TrimSpace(result.SourceRecordID) == "" {
			return fault(fmt.Errorf("recover connector remote identity: recovery service returned an incomplete recovered result"))
		}
		state = ConnectorInboundRemoteIdentityRecoveryStateRecovered
	}
	assignActionCallOutputs(interview, action, map[string]interface{}{
		"recoveryState": state,
	})
	return next, nil
}

func (e *FlowExecutor) newConnectorInboundRemoteIdentityRecoverer(
	validator ConnectorInboundRemoteIdentityRecoveryLockedRecordValidator,
) (connectorInboundRemoteIdentityRecoverer, error) {
	if e == nil || e.persistence == nil {
		return nil, fmt.Errorf("recover connector remote identity: persistence is not wired")
	}
	if validator == nil {
		return nil, fmt.Errorf("recover connector remote identity: product lifecycle validator is not wired")
	}
	if e.connectorInboundInstalled == nil {
		return nil, fmt.Errorf("recover connector remote identity: installed contract resolver is not wired")
	}
	if e.connectorPushProfiles == nil {
		return nil, fmt.Errorf("recover connector remote identity: installed profile resolver is not wired")
	}
	store, ok := e.connectorPushDurable.(connectorInboundRemoteIdentityRecoveryStore)
	if !ok || store == nil {
		return nil, fmt.Errorf("recover connector remote identity: strict durable recovery store is not wired")
	}
	return NewConnectorInboundRemoteIdentityRecoveryService(
		e.persistence,
		store,
		e,
		NewConnectorInboundRemoteIdentityRecoverySourceValidator(e.persistence, validator),
	), nil
}

type connectorInboundRemoteIdentityRecoverySourceRecordValidator struct {
	persistence PersistenceProvider
	validator   ConnectorInboundRemoteIdentityRecoveryLockedRecordValidator
}

// NewConnectorInboundRemoteIdentityRecoverySourceValidator composes the
// generic locked-record adapter used by both the protected Flow action and the
// headless maintenance entry point. Product lifecycle policy remains supplied
// through the neutral validator port; callers cannot provide a record or
// snapshot directly.
func NewConnectorInboundRemoteIdentityRecoverySourceValidator(
	persistence PersistenceProvider,
	validator ConnectorInboundRemoteIdentityRecoveryLockedRecordValidator,
) ConnectorInboundRemoteIdentityRecoverySourceValidator {
	return connectorInboundRemoteIdentityRecoverySourceRecordValidator{
		persistence: persistence,
		validator:   validator,
	}
}

func (v connectorInboundRemoteIdentityRecoverySourceRecordValidator) ValidateConnectorInboundRemoteIdentityRecoverySource(
	ctx context.Context,
	evidence ConnectorInboundRemoteIDEvidence,
	binding ConnectorInboundRemoteIdentityRecoveryBinding,
) (ConnectorInboundRemoteIdentityRecoverySourceValidation, error) {
	if v.persistence == nil || v.validator == nil {
		return ConnectorInboundRemoteIdentityRecoverySourceValidation{}, fmt.Errorf("connector recovery source-record validator is not wired")
	}
	objectName := strings.TrimSpace(evidence.SourceObject)
	recordID := strings.TrimSpace(evidence.SourceRecordId)
	locked, err := v.persistence.GetLock(ctx, objectName, recordID)
	if err != nil {
		return ConnectorInboundRemoteIdentityRecoverySourceValidation{}, fmt.Errorf("lock recovery source record %s/%s: %w", objectName, recordID, err)
	}
	if locked == nil {
		return ConnectorInboundRemoteIdentityRecoverySourceValidation{}, fmt.Errorf("recovery source record %s/%s not found", objectName, recordID)
	}
	status, err := v.validator.ValidateConnectorInboundRemoteIdentityRecoveryLockedRecord(
		ConnectorInboundRemoteIdentityRecoveryLockedRecordInput{
			ObjectName:                 objectName,
			RecordID:                   recordID,
			Record:                     locked,
			RemoteIDField:              strings.TrimSpace(evidence.RemoteIDField),
			RemoteID:                   strings.TrimSpace(evidence.RemoteId),
			RequiredFalseBooleanFields: append([]string(nil), binding.RecoverySourceEligibility...),
		},
	)
	if err != nil {
		return ConnectorInboundRemoteIdentityRecoverySourceValidation{}, err
	}
	return ConnectorInboundRemoteIdentityRecoverySourceValidation{
		CompatibilityMirrorStatus: status,
	}, nil
}

// MetadataBoundConnectorInboundRemoteIdentityRecoveryValidator applies only
// the immutable, app-authored boolean predicates carried by the bound Catalog
// view. It contains no product object or field names.
type MetadataBoundConnectorInboundRemoteIdentityRecoveryValidator struct{}

func (MetadataBoundConnectorInboundRemoteIdentityRecoveryValidator) ValidateConnectorInboundRemoteIdentityRecoveryLockedRecord(
	input ConnectorInboundRemoteIdentityRecoveryLockedRecordInput,
) (ConnectorPushCompatibilityMirrorStatus, error) {
	if len(input.RequiredFalseBooleanFields) == 0 {
		return "", fmt.Errorf("connector recovery source eligibility is not published")
	}
	for _, fieldName := range input.RequiredFalseBooleanFields {
		value, ok := input.Record[fieldName]
		if !ok {
			return "", fmt.Errorf("connector recovery source record %s/%s omitted eligibility field %q", input.ObjectName, input.RecordID, fieldName)
		}
		valueIsTrue, valid := strictRecordBoolean(value)
		if !valid {
			return "", fmt.Errorf("connector recovery source record %s/%s field %q is not boolean", input.ObjectName, input.RecordID, fieldName)
		}
		if valueIsTrue {
			return "", fmt.Errorf("connector recovery source record %s/%s is ineligible because %s is true", input.ObjectName, input.RecordID, fieldName)
		}
	}
	mirror, ok := input.Record[input.RemoteIDField]
	if !ok {
		return "", fmt.Errorf("connector recovery source record %s/%s omitted remote id field %q", input.ObjectName, input.RecordID, input.RemoteIDField)
	}
	if flowRecordString(mirror) == strings.TrimSpace(input.RemoteID) {
		return ConnectorPushCompatibilityMirrorSynced, nil
	}
	return ConnectorPushCompatibilityMirrorDrifted, nil
}

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

type connectorInboundRemoteIdentityRecoveryActionParams struct {
	destinationSlotID string
	remoteID          string
}

func parseConnectorInboundRemoteIdentityRecoveryAction(
	action *models.FlowActionCall,
	expectedActionType string,
	resolve connectorInboundRemoteIdentityRecoveryValueResolver,
) (connectorInboundRemoteIdentityRecoveryActionParams, error) {
	expectedActionType = normalizeFlowActionType(expectedActionType)
	if expectedActionType == "" {
		return connectorInboundRemoteIdentityRecoveryActionParams{}, fmt.Errorf("recover connector remote identity: descriptor action type is required")
	}
	if action == nil || normalizeFlowActionType(action.ActionType) != expectedActionType {
		return connectorInboundRemoteIdentityRecoveryActionParams{}, fmt.Errorf("recover connector remote identity: action type must be %s", expectedActionType)
	}
	if len(action.InputParameters) != 2 {
		return connectorInboundRemoteIdentityRecoveryActionParams{}, fmt.Errorf("recover connector remote identity: exactly destinationSlotId and remoteId inputs are required")
	}

	values := make(map[string]interface{}, 2)
	for _, parameter := range action.InputParameters {
		name := strings.TrimSpace(parameter.Name)
		switch name {
		case "destinationSlotId", "remoteId":
		default:
			return connectorInboundRemoteIdentityRecoveryActionParams{}, fmt.Errorf("recover connector remote identity: unknown input %q", name)
		}
		if _, duplicate := values[name]; duplicate {
			return connectorInboundRemoteIdentityRecoveryActionParams{}, fmt.Errorf("recover connector remote identity: duplicate input %q", name)
		}
		values[name] = parameter.Value
	}

	destinationSlotID, err := connectorRecoveryLiteralString(values["destinationSlotId"], "destinationSlotId")
	if err != nil {
		return connectorInboundRemoteIdentityRecoveryActionParams{}, err
	}
	remoteReference, err := connectorRecoveryDynamicReference(values["remoteId"], "remoteId")
	if err != nil {
		return connectorInboundRemoteIdentityRecoveryActionParams{}, err
	}
	resolved, err := resolve(map[string]interface{}{"elementReference": remoteReference})
	if err != nil {
		return connectorInboundRemoteIdentityRecoveryActionParams{}, err
	}
	remoteID, ok := resolved.(string)
	if !ok || remoteID == "" || remoteID != strings.TrimSpace(remoteID) {
		return connectorInboundRemoteIdentityRecoveryActionParams{}, fmt.Errorf("recover connector remote identity: remoteId must resolve to a canonical nonblank string")
	}

	if len(action.OutputParameters) != 1 || strings.TrimSpace(action.OutputParameters[0].Name) != "recoveryState" {
		return connectorInboundRemoteIdentityRecoveryActionParams{}, fmt.Errorf("recover connector remote identity: exactly one recoveryState output is required")
	}
	if _, err := connectorRecoveryDynamicReference(action.OutputParameters[0].Value, "recoveryState output"); err != nil {
		return connectorInboundRemoteIdentityRecoveryActionParams{}, err
	}
	return connectorInboundRemoteIdentityRecoveryActionParams{
		destinationSlotID: destinationSlotID,
		remoteID:          remoteID,
	}, nil
}

func connectorRecoveryLiteralString(value interface{}, name string) (string, error) {
	typed, ok := value.(map[string]interface{})
	if !ok || len(typed) != 1 {
		return "", fmt.Errorf("recover connector remote identity: %s must be a pack-declared stringValue literal", name)
	}
	literal, ok := typed["stringValue"].(string)
	if !ok || literal == "" || literal != strings.TrimSpace(literal) || strings.Contains(literal, "{!") {
		return "", fmt.Errorf("recover connector remote identity: %s must be a canonical nonblank pack-declared stringValue literal", name)
	}
	return literal, nil
}

func connectorRecoveryDynamicReference(value interface{}, name string) (string, error) {
	typed, ok := value.(map[string]interface{})
	if !ok || len(typed) != 1 {
		return "", fmt.Errorf("recover connector remote identity: %s must be a dynamic elementReference", name)
	}
	reference, ok := typed["elementReference"].(string)
	if !ok || reference == "" || reference != strings.TrimSpace(reference) {
		return "", fmt.Errorf("recover connector remote identity: %s must be a canonical nonblank dynamic elementReference", name)
	}
	return reference, nil
}
