package automation

import (
	"context"
	"fmt"
	"strings"

	"github.com/nexuscrm/kernel/pkg/flowactions"
	"github.com/nexuscrm/kernel/pkg/utils"
	"github.com/nexuscrm/shared/pkg/constants"
	"github.com/nexuscrm/shared/pkg/models"
	connector "github.com/pingcap-inc/nexuscrm-contract/pkg/connector"
)

type connectorInboundFinalizeSelectors struct {
	targetFlowDeveloperName string
	targetFlowActionName    string
}

// executeFinalizeConnectorInboundRemoteIdentity closes authenticated inbound
// authorship into durable remote identity without invoking connector HTTP. The
// action accepts only installed-contract selectors; every authority-bearing
// identity and snapshot field is derived by the engine.
func (e *FlowExecutor) executeFinalizeConnectorInboundRemoteIdentity(
	ctx context.Context,
	_ *models.FlowInterview,
	action *models.FlowActionCall,
	user *models.UserSession,
) (string, error) {
	next := ""
	if action != nil && action.Connector != nil {
		next = action.Connector.TargetReference
	}
	selectors, err := parseConnectorInboundFinalizeSelectors(action)
	if err != nil {
		return "", err
	}

	flowIdentity, ok := flowRuntimeContextFromCtx(ctx).CurrentFlow()
	if !ok || strings.TrimSpace(flowIdentity.OwnerPack) == "" {
		return "", fmt.Errorf("finalize inbound remote identity: executing Flow owner pack is required")
	}
	if !flowIdentityIsPackOwnedStandard(flowIdentity) {
		return "", fmt.Errorf("finalize inbound remote identity: Flow %q must be an uncustomized standard FlowVersion", flowIdentity.APIName)
	}
	connectorNamespace := constants.ConnectorSyncOriginFromContext(ctx)
	if connectorNamespace == "" {
		return "", fmt.Errorf("finalize inbound remote identity: connector inbound sync origin is required")
	}
	connectorInboundAuthorized := constants.ConnectorInboundExecutionAuthorizesFlow(ctx, flowIdentity.APIName, connectorNamespace) ||
		constants.ConnectorInboundCompanionExecutionAuthorizesVersion(ctx, flowIdentity.VersionID) ||
		connectorInboundCompanionSubflowExecutionAuthorized(ctx, flowIdentity)
	if !connectorInboundAuthorized {
		return "", fmt.Errorf("finalize inbound remote identity: Flow %q is not the authenticated connector inbound root, exact companion version, or authorized direct companion subflow for namespace %q", flowIdentity.APIName, connectorNamespace)
	}
	if !isWithinAtomicFlowBoundary(ctx) || e.persistence == nil || !e.persistence.IsTransactionActive(ctx) {
		return "", fmt.Errorf("finalize inbound remote identity: a live atomic Flow transaction is required")
	}
	if e.connectorPushDurable == nil {
		return "", fmt.Errorf("finalize inbound remote identity: durable store is not wired")
	}
	finalizer, ok := e.connectorPushDurable.(ConnectorInboundRemoteIdentityFinalizer)
	if !ok {
		return "", fmt.Errorf("finalize inbound remote identity: durable store does not support ambient finalization")
	}
	admission, ok := e.connectorPushDurable.(ConnectorRemoteIdentityRegistrationStore)
	if !ok {
		return "", fmt.Errorf("finalize inbound remote identity: durable store does not support remote identity admission and history guard")
	}
	authorityStore, ok := e.connectorPushDurable.(ConnectorRemoteIdentityAuthorityStore)
	if !ok {
		return "", fmt.Errorf("finalize inbound remote identity: durable store does not support authoritative retry lookup")
	}

	contract, mappings, profile, err := e.resolveConnectorInboundFinalizeAuthority(
		ctx,
		flowIdentity,
		selectors,
	)
	if err != nil {
		return "", err
	}

	sourceRecordID, remoteID, present, err := e.resolveConnectorInboundFinalizeRecord(ctx, contract.SourceObject, contract.RemoteIDField)
	if err != nil {
		return "", err
	}
	if !present {
		return next, nil
	}

	identity := connectorPushDurableIdentityFromContract(contract, sourceRecordID)
	linkKey, err := identity.LinkKey()
	if err != nil {
		return "", fmt.Errorf("finalize inbound remote identity: durable identity: %w", err)
	}
	originatingUserID := ""
	if user != nil {
		originatingUserID = strings.TrimSpace(user.ID)
	}
	snapshot := connectorPushDurableSnapshotFromMetadata(flowIdentity, contract, mappings, profile, originatingUserID)
	if err := admission.LockConnectorRemoteIdentityLaneInAmbientTx(ctx, identity, remoteID); err != nil {
		return "", fmt.Errorf("finalize inbound remote identity: lock remote identity admission: %w", err)
	}
	if _, err := admission.CheckConnectorRemoteIdentityRegistrationInAmbientTx(ctx, identity, remoteID); err != nil {
		return "", fmt.Errorf("finalize inbound remote identity: check remote identity history: %w", err)
	}
	evidence := ConnectorInboundRemoteIDEvidence{
		ID:                 utils.GenerateID(),
		ConnectorOwnerKey:  strings.TrimSpace(flowIdentity.OwnerPack),
		ConnectorNamespace: connectorNamespace,
		SourceObject:       strings.TrimSpace(contract.SourceObject),
		SourceRecordId:     sourceRecordID,
		RemoteIDField:      strings.TrimSpace(contract.RemoteIDField),
		RemoteId:           remoteID,
	}

	existing, err := e.connectorPushDurable.FindActiveInboundRemoteIDEvidenceInTx(
		ctx,
		evidence.ConnectorOwnerKey,
		evidence.SourceObject,
		evidence.SourceRecordId,
		evidence.RemoteIDField,
	)
	if err != nil {
		return "", fmt.Errorf("finalize inbound remote identity: read active evidence: %w", err)
	}
	if existing == nil {
		authority, err := authorityStore.ResolveFinalizedRemoteLocator(ctx, ConnectorRemoteLocatorQuery{
			ConnectorOwnerKey: identity.ConnectorOwnerKey,
			DestinationSlotID: identity.DestinationSlotID,
			RemoteID:          remoteID,
		})
		if err != nil {
			return "", fmt.Errorf("finalize inbound remote identity: resolve existing authority: %w", err)
		}
		if authority != nil {
			if strings.TrimSpace(authority.LinkID) == "" ||
				authority.LinkStatus != ConnectorPushLinkStatusFinalized ||
				authority.LinkKey != linkKey ||
				strings.TrimSpace(authority.RemoteID) != remoteID ||
				authority.Identity != identity {
				return "", fmt.Errorf("%w: remote locator is already owned by a different durable identity", ErrConnectorPushDurableConflict)
			}
			return next, nil
		}
		if err := e.connectorPushDurable.InsertAuthoredEvidence(ctx, evidence); err != nil {
			return "", fmt.Errorf("finalize inbound remote identity: author exact evidence: %w", err)
		}
	} else {
		if !connectorInboundFinalizeEvidenceMatches(*existing, evidence) {
			return "", fmt.Errorf("%w: active inbound authorship evidence does not match the authenticated write", ErrConnectorPushDurableConflict)
		}
		evidence = *existing
	}

	if err := finalizer.FinalizeInboundRemoteIdentityInAmbientTx(ctx, ConnectorInboundRemoteIdentityFinalization{
		Evidence: evidence,
		Adoption: ConnectorPushLinkAdoption{
			LinkKey:  linkKey,
			RemoteID: remoteID,
			Identity: identity,
			Snapshot: snapshot,
		},
	}); err != nil {
		return "", fmt.Errorf("finalize inbound remote identity: publish durable authority: %w", err)
	}
	return next, nil
}

func (e *FlowExecutor) resolveConnectorInboundFinalizeAuthority(
	ctx context.Context,
	flowIdentity FlowExecutionIdentity,
	selectors connectorInboundFinalizeSelectors,
) (
	*connector.ConnectorPushDurableMetadataContract,
	[]connector.ConnectorFieldMappingInput,
	*ConnectorPushProfile,
	error,
) {
	query := ConnectorPushContractQuery{
		ConnectorOwnerKey: strings.TrimSpace(flowIdentity.OwnerPack),
		FlowDeveloperName: selectors.targetFlowDeveloperName,
		FlowActionName:    selectors.targetFlowActionName,
	}
	if e.connectorInboundFinalizer != nil {
		authority, err := e.connectorInboundFinalizer.ResolveConnectorInboundRemoteIdentityFinalizationAuthority(ctx, query)
		if err != nil {
			return nil, nil, nil, fmt.Errorf(
				"finalize inbound remote identity: resolve immutable authority %s/%s/%s: %w",
				query.ConnectorOwnerKey,
				query.FlowDeveloperName,
				query.FlowActionName,
				err,
			)
		}
		if authority == nil {
			return nil, nil, nil, fmt.Errorf(
				"finalize inbound remote identity: immutable authority %s/%s/%s not found",
				query.ConnectorOwnerKey,
				query.FlowDeveloperName,
				query.FlowActionName,
			)
		}
		contract := authority.Contract
		if err := validateConnectorInboundFinalizeContract(query, &contract); err != nil {
			return nil, nil, nil, err
		}
		profileOwnerPack := strings.TrimSpace(contract.ProfileOwnerPack)
		if profileOwnerPack == "" {
			profileOwnerPack = query.ConnectorOwnerKey
		}
		profile := authority.Profile
		if err := validateConnectorInboundFinalizeProfile(
			profileOwnerPack,
			contract.ProfileName,
			&profile,
		); err != nil {
			return nil, nil, nil, err
		}
		mappings := append(
			[]connector.ConnectorFieldMappingInput(nil),
			authority.OutboundMappings...,
		)
		if err := validateResolvedPushToConnectorMappings(mappings); err != nil {
			return nil, nil, nil, fmt.Errorf(
				"finalize inbound remote identity: validate immutable outbound mappings: %w",
				err,
			)
		}
		return &contract, mappings, &profile, nil
	}

	if e.connectorPushContracts == nil {
		return nil, nil, nil, fmt.Errorf(
			"finalize inbound remote identity: connector push contract resolver is not wired",
		)
	}
	if e.connectorPushProfiles == nil {
		return nil, nil, nil, fmt.Errorf(
			"finalize inbound remote identity: connector profile resolver is not wired",
		)
	}
	contract, err := e.resolveConnectorInboundFinalizeContract(ctx, flowIdentity, selectors)
	if err != nil {
		return nil, nil, nil, err
	}
	params := pushToConnectorParamsFromContract(pushToConnectorParams{}, contract)
	mappings, err := e.resolvePushToConnectorMappings(ctx, flowIdentity.OwnerPack, params)
	if err != nil {
		return nil, nil, nil, fmt.Errorf(
			"finalize inbound remote identity: resolve installed outbound mappings: %w",
			err,
		)
	}
	profileOwnerPack := strings.TrimSpace(contract.ProfileOwnerPack)
	if profileOwnerPack == "" {
		profileOwnerPack = strings.TrimSpace(flowIdentity.OwnerPack)
	}
	profile, err := e.connectorPushProfiles.ResolveConnectorPushProfile(
		ctx,
		flowIdentity.OwnerPack,
		profileOwnerPack,
		strings.TrimSpace(contract.ProfileName),
	)
	if err != nil {
		return nil, nil, nil, fmt.Errorf(
			"finalize inbound remote identity: resolve profile %s/%s: %w",
			profileOwnerPack,
			contract.ProfileName,
			err,
		)
	}
	if err := validateConnectorInboundFinalizeProfile(
		profileOwnerPack,
		contract.ProfileName,
		profile,
	); err != nil {
		return nil, nil, nil, err
	}
	return contract, mappings, profile, nil
}

func parseConnectorInboundFinalizeSelectors(action *models.FlowActionCall) (connectorInboundFinalizeSelectors, error) {
	if action == nil || strings.TrimSpace(action.ActionType) != flowactions.ActionTypeFinalizeConnectorInboundRemoteIdentity {
		return connectorInboundFinalizeSelectors{}, fmt.Errorf("finalize inbound remote identity: action type must be %s", flowactions.ActionTypeFinalizeConnectorInboundRemoteIdentity)
	}
	values := map[string]string{}
	for _, param := range action.InputParameters {
		name := strings.TrimSpace(param.Name)
		switch name {
		case "targetFlowDeveloperName", "targetFlowActionName":
		default:
			return connectorInboundFinalizeSelectors{}, fmt.Errorf("finalize inbound remote identity: unknown input %q", name)
		}
		if _, duplicate := values[name]; duplicate {
			return connectorInboundFinalizeSelectors{}, fmt.Errorf("finalize inbound remote identity: duplicate input %q", name)
		}
		typed, ok := param.Value.(map[string]interface{})
		if !ok || len(typed) != 1 {
			return connectorInboundFinalizeSelectors{}, fmt.Errorf("finalize inbound remote identity: %s must be a pack-declared stringValue literal", name)
		}
		literal, ok := typed["stringValue"].(string)
		literal = strings.TrimSpace(literal)
		if !ok || literal == "" || strings.Contains(literal, "{!") || strings.HasPrefix(literal, "$Record") {
			return connectorInboundFinalizeSelectors{}, fmt.Errorf("finalize inbound remote identity: %s must be a nonblank pack-declared stringValue literal", name)
		}
		values[name] = literal
	}
	selectors := connectorInboundFinalizeSelectors{
		targetFlowDeveloperName: values["targetFlowDeveloperName"],
		targetFlowActionName:    values["targetFlowActionName"],
	}
	if selectors.targetFlowDeveloperName == "" || selectors.targetFlowActionName == "" {
		return connectorInboundFinalizeSelectors{}, fmt.Errorf("finalize inbound remote identity: targetFlowDeveloperName and targetFlowActionName are required")
	}
	return selectors, nil
}

func (e *FlowExecutor) resolveConnectorInboundFinalizeContract(ctx context.Context, flowIdentity FlowExecutionIdentity, selectors connectorInboundFinalizeSelectors) (*connector.ConnectorPushDurableMetadataContract, error) {
	query := ConnectorPushContractQuery{
		ConnectorOwnerKey: strings.TrimSpace(flowIdentity.OwnerPack),
		FlowDeveloperName: selectors.targetFlowDeveloperName,
		FlowActionName:    selectors.targetFlowActionName,
	}
	contract, err := e.connectorPushContracts.ResolveConnectorPushContract(ctx, query)
	if err != nil {
		return nil, fmt.Errorf("finalize inbound remote identity: resolve target contract %s/%s/%s: %w", query.ConnectorOwnerKey, query.FlowDeveloperName, query.FlowActionName, err)
	}
	if contract == nil {
		return nil, fmt.Errorf("finalize inbound remote identity: target contract %s/%s/%s not found", query.ConnectorOwnerKey, query.FlowDeveloperName, query.FlowActionName)
	}
	if err := validateConnectorInboundFinalizeContract(query, contract); err != nil {
		return nil, err
	}
	return contract, nil
}

func validateConnectorInboundFinalizeContract(
	query ConnectorPushContractQuery,
	contract *connector.ConnectorPushDurableMetadataContract,
) error {
	if contract == nil {
		return fmt.Errorf(
			"finalize inbound remote identity: target contract %s/%s/%s not found",
			query.ConnectorOwnerKey,
			query.FlowDeveloperName,
			query.FlowActionName,
		)
	}
	if strings.TrimSpace(contract.ConnectorOwnerKey) != query.ConnectorOwnerKey ||
		strings.TrimSpace(contract.FlowDeveloperName) != query.FlowDeveloperName ||
		strings.TrimSpace(contract.FlowActionName) != query.FlowActionName {
		return fmt.Errorf("finalize inbound remote identity: target contract authority drift")
	}
	if strings.TrimSpace(contract.ActionType) != constants.ActionTypePushToConnector ||
		strings.TrimSpace(contract.ActionSlotID) == "" ||
		strings.TrimSpace(contract.SourceObject) == "" ||
		strings.TrimSpace(contract.DestinationSlotID) == "" ||
		strings.TrimSpace(contract.TargetObject) == "" ||
		strings.TrimSpace(contract.ProfileName) == "" ||
		strings.TrimSpace(contract.RemoteIDField) == "" {
		return fmt.Errorf("finalize inbound remote identity: target contract is incomplete or is not %s", constants.ActionTypePushToConnector)
	}
	return nil
}

func validateConnectorInboundFinalizeProfile(ownerPack, profileName string, profile *ConnectorPushProfile) error {
	if profile == nil {
		return fmt.Errorf("finalize inbound remote identity: profile %s/%s not found", ownerPack, profileName)
	}
	if strings.TrimSpace(profile.OwnerPack) != strings.TrimSpace(ownerPack) ||
		strings.TrimSpace(profile.ProfileName) != strings.TrimSpace(profileName) ||
		strings.TrimSpace(profile.CreatedPackInstallID) == "" ||
		strings.TrimSpace(profile.CredentialRef) == "" ||
		strings.TrimSpace(profile.EndpointClass) == "" ||
		profile.RetryPolicy.MaxAttempts < 1 {
		return fmt.Errorf("finalize inbound remote identity: resolved profile %s/%s is incomplete or drifted", ownerPack, profileName)
	}
	return nil
}

func (e *FlowExecutor) resolveConnectorInboundFinalizeRecord(ctx context.Context, sourceObject, remoteIDField string) (string, string, bool, error) {
	sourceObject = strings.TrimSpace(sourceObject)
	remoteIDField = strings.TrimSpace(remoteIDField)
	frc := flowRuntimeContextFromCtx(ctx)
	records := frc.PostUpsertRecords(sourceObject)
	seen := map[string]struct{}{}
	var recordIDs []string
	for _, record := range records {
		recordID, _ := record[constants.FieldID].(string)
		recordID = strings.TrimSpace(recordID)
		if recordID == "" {
			continue
		}
		if _, duplicate := seen[recordID]; !duplicate {
			seen[recordID] = struct{}{}
			recordIDs = append(recordIDs, recordID)
		}
	}
	switch len(recordIDs) {
	case 0:
		if frc.HasPostUpsertWrite(sourceObject) {
			return "", "", false, fmt.Errorf("finalize inbound remote identity: engine wrote %q but no engine-confirmed post-upsert record is available", sourceObject)
		}
		return "", "", false, fmt.Errorf("finalize inbound remote identity: exactly one engine-confirmed post-upsert %q record is required", sourceObject)
	case 1:
	default:
		return "", "", false, ErrAmbiguousPostUpsertf(len(recordIDs), sourceObject)
	}
	recordID := recordIDs[0]
	committed, err := e.persistence.GetLock(ctx, sourceObject, recordID)
	if err != nil {
		return "", "", false, fmt.Errorf("finalize inbound remote identity: GetLock %s/%s: %w", sourceObject, recordID, err)
	}
	if committed == nil {
		return "", "", false, fmt.Errorf("finalize inbound remote identity: committed record %s/%s not found", sourceObject, recordID)
	}
	remoteID := strings.TrimSpace(fmt.Sprintf("%v", committed[remoteIDField]))
	if remoteID == "" || remoteID == "<nil>" {
		return recordID, "", false, nil
	}
	return recordID, remoteID, true, nil
}

func connectorInboundFinalizeEvidenceMatches(existing ConnectorInboundRemoteIDEvidence, expected ConnectorInboundRemoteIDEvidence) bool {
	expectedActiveKey := ConnectorInboundEvidenceActiveKey(expected.ConnectorOwnerKey, expected.SourceObject, expected.SourceRecordId, expected.RemoteIDField)
	return strings.TrimSpace(existing.ID) != "" &&
		strings.TrimSpace(existing.EvidenceStatus) == ConnectorInboundEvidenceStatusAuthored &&
		strings.TrimSpace(existing.ConsumedByLinkKey) == "" &&
		strings.TrimSpace(existing.ActiveKey) == expectedActiveKey &&
		strings.TrimSpace(existing.ConnectorOwnerKey) == expected.ConnectorOwnerKey &&
		strings.EqualFold(strings.TrimSpace(existing.ConnectorNamespace), expected.ConnectorNamespace) &&
		strings.TrimSpace(existing.SourceObject) == expected.SourceObject &&
		strings.TrimSpace(existing.SourceRecordId) == expected.SourceRecordId &&
		strings.TrimSpace(existing.RemoteIDField) == expected.RemoteIDField &&
		strings.TrimSpace(existing.RemoteId) == expected.RemoteId
}
