package automation

import (
	"context"
	"errors"
	"fmt"
	"strings"

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

type ConnectorPushLegacyMirrorEvidence struct {
	Contract           connector.ConnectorPushDurableMetadataContract
	SourceRecordID     string
	RemoteID           string
	RemoteIDProvenance string
	Mappings           []connector.ConnectorFieldMappingInput
	Profile            *ConnectorPushProfile
	OriginatingUserID  string
}

type connectorPushSuspendedDriftLinkAdopter interface {
	AdoptSuspendedDriftLink(context.Context, ConnectorPushLinkAdoption) (*ConnectorPushRemoteLink, error)
}

// AdoptConnectorPushLegacyMirrorEvidence materializes proven legacy mirror
// occupancy into the durable link SSoT. Historical mirror tuples prove only the
// durable link key and remote id, not current destination/profile continuity, so
// repair-created links are suspended until an explicit revalidation path proves
// they are safe PATCH authority. Runtime create eligibility must keep reading
// durable link/attempt state.
func AdoptConnectorPushLegacyMirrorEvidence(ctx context.Context, store ConnectorPushDurableStore, evidence ConnectorPushLegacyMirrorEvidence) (*ConnectorPushRemoteLink, bool, error) {
	if store == nil {
		return nil, false, fmt.Errorf("connector push legacy mirror adoption: durable store is not wired")
	}
	remoteID := strings.TrimSpace(evidence.RemoteID)
	provenance := strings.TrimSpace(evidence.RemoteIDProvenance)
	if remoteID == "" || provenance == "" {
		return nil, false, nil
	}
	if evidence.Profile == nil {
		return nil, false, fmt.Errorf("connector push legacy mirror adoption: profile is required")
	}
	suspendedAdopter, ok := store.(connectorPushSuspendedDriftLinkAdopter)
	if !ok {
		return nil, false, fmt.Errorf("connector push legacy mirror adoption: durable store cannot atomically adopt suspended links")
	}
	originatingUserID := strings.TrimSpace(evidence.OriginatingUserID)
	if originatingUserID == "" {
		originatingUserID = constants.SystemUserID
	}

	for _, actionSlotID := range connectorPushLegacyMirrorActionSlotIDs(evidence.Contract) {
		contract := evidence.Contract
		contract.ActionSlotID = actionSlotID
		identity := connectorPushDurableIdentityFromContract(&contract, evidence.SourceRecordID)
		linkKey, err := identity.LinkKey()
		if err != nil {
			return nil, false, fmt.Errorf("connector push legacy mirror adoption: durable link identity: %w", err)
		}
		if provenance != linkKey {
			continue
		}
		existing, protected, err := connectorPushLegacyMirrorProtectedState(ctx, store, linkKey)
		if err != nil {
			return nil, false, fmt.Errorf("connector push legacy mirror adoption: read durable state %s: %w", linkKey, err)
		}
		if protected {
			return existing, false, nil
		}
		snapshot := connectorPushDurableSnapshotFromMetadata(FlowExecutionIdentity{
			APIName:   strings.TrimSpace(contract.FlowDeveloperName),
			OwnerPack: strings.TrimSpace(contract.ConnectorOwnerKey),
		}, &contract, evidence.Mappings, evidence.Profile, originatingUserID)
		link, err := suspendedAdopter.AdoptSuspendedDriftLink(ctx, ConnectorPushLinkAdoption{
			LinkKey:  linkKey,
			RemoteID: remoteID,
			Identity: identity,
			Snapshot: snapshot,
		})
		if err != nil {
			if errors.Is(err, ErrConnectorPushDurableConflict) {
				existing, protected, rereadErr := connectorPushLegacyMirrorProtectedState(ctx, store, linkKey)
				if rereadErr != nil {
					return nil, false, fmt.Errorf("connector push legacy mirror adoption: reread durable state %s after conflict: %w", linkKey, rereadErr)
				}
				if protected {
					return existing, false, nil
				}
			}
			return nil, false, err
		}
		return link, true, nil
	}
	return nil, false, nil
}

func connectorPushLegacyMirrorProtectedState(ctx context.Context, store ConnectorPushDurableStore, linkKey string) (*ConnectorPushRemoteLink, bool, error) {
	existing, err := store.FindLink(ctx, linkKey)
	if err != nil {
		return nil, false, err
	}
	if existing != nil {
		return existing, true, nil
	}
	attempt, err := store.FindBlockingAttempt(ctx, linkKey)
	if err != nil {
		return nil, false, err
	}
	return nil, attempt != nil, nil
}

func (s *SQLConnectorPushDurableStore) AdoptSuspendedDriftLink(ctx context.Context, adoption ConnectorPushLinkAdoption) (*ConnectorPushRemoteLink, error) {
	if s == nil || s.db == nil {
		return nil, fmt.Errorf("connector push durable store is not wired")
	}
	adoption, err := normalizeConnectorPushLinkAdoption(adoption)
	if err != nil {
		return nil, err
	}
	tx, err := s.db.BeginTx(ctx, nil)
	if err != nil {
		return nil, err
	}
	defer func() {
		_ = tx.Rollback()
	}()
	if err := lockConnectorRemoteIdentityLaneTx(ctx, tx, adoption.Identity, adoption.RemoteID); err != nil {
		return nil, err
	}
	// All remote-authority adoption paths take the locator lane before the
	// owner fence. Mixing that order with finalized adoption can deadlock two
	// writers for the same remote identity.
	if err := ensureConnectorRemoteIdentityOwnerCreateAllowedTx(ctx, tx, adoption.Identity.SourceObject, adoption.Identity.SourceRecordID); err != nil {
		return nil, err
	}
	if _, err := checkConnectorRemoteIdentityActiveAuthorityTx(ctx, tx, adoption.Identity, adoption.RemoteID, ConnectorPushLinkStatusSuspendedDrift); err != nil {
		return nil, err
	}

	lockState, err := s.lockConnectorPushLinkThenAttemptOccupancyTx(ctx, tx, adoption.LinkKey)
	if err != nil {
		return nil, err
	}
	if strings.TrimSpace(lockState.AttemptStatus) != "" {
		return nil, fmt.Errorf("%w: create attempt %s already occupies link key", ErrConnectorPushDurableConflict, strings.TrimSpace(lockState.AttemptStatus))
	}

	existingID := lockState.Link.LinkID
	existingStatus := lockState.Link.LinkStatus
	existingRemoteID := lockState.Link.RemoteID
	if strings.TrimSpace(existingID) != "" {
		if strings.TrimSpace(existingStatus) == ConnectorPushLinkStatusSuspendedDrift && strings.TrimSpace(existingRemoteID) == adoption.RemoteID {
			if err := tx.Commit(); err != nil {
				return nil, err
			}
			return s.FindLink(ctx, adoption.LinkKey)
		}
		return nil, fmt.Errorf("%w: remote link %s already owns link key", ErrConnectorPushDurableConflict, strings.TrimSpace(existingStatus))
	}

	linkID := utils.GenerateID()
	remoteLocatorHash := ConnectorPushRemoteLocatorHash(adoption.Identity, adoption.RemoteID)
	_, err = tx.ExecContext(ctx, `
INSERT INTO _System_ConnectorRemoteLink
  (Id, LinkKey, Status, RemoteId, RemoteLocatorHash, RemoteLocatorActiveKey, ConnectorOwnerKey,
   ActionSlotId, ActionType, SourceObject, SourceRecordId, DestinationSlotId, RequesterOwnerPack,
   ProfileOwnerPack, ConnectorNamespace, FlowDeveloperName, FlowActionName, TargetObject, ProfileName,
   CreatedPackInstallId, LastValidatedPackInstallId, CredentialRef, EndpointClass, RetryPolicy, ProfileContractHash, CreatePathTemplate,
   UpdatePathTemplate, ResponseIdPath, RemoteIdField, RemoteIdProvenanceField, MappingFingerprint, BodyFingerprint,
   CompatibilityMirrorStatus)
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`,
		linkID, adoption.LinkKey, ConnectorPushLinkStatusSuspendedDrift, adoption.RemoteID,
		remoteLocatorHash, remoteLocatorHash,
		adoption.Identity.ConnectorOwnerKey, adoption.Identity.ActionSlotID, adoption.Identity.ActionType,
		adoption.Identity.SourceObject, adoption.Identity.SourceRecordID, adoption.Identity.DestinationSlotID,
		adoption.Snapshot.RequesterOwnerPack, adoption.Snapshot.ProfileOwnerPack, adoption.Snapshot.ConnectorNamespace,
		adoption.Snapshot.FlowDeveloperName, adoption.Snapshot.FlowActionName, adoption.Snapshot.TargetObject,
		adoption.Snapshot.ProfileName, adoption.Snapshot.CreatedPackInstallID, adoption.Snapshot.CreatedPackInstallID,
		adoption.Snapshot.CredentialRef, adoption.Snapshot.EndpointClass, mustConnectorPushJSON(adoption.Snapshot.RetryPolicy),
		adoption.Snapshot.ProfileContractHash, adoption.Snapshot.CreatePathTemplate,
		adoption.Snapshot.UpdatePathTemplate, adoption.Snapshot.ResponseIDPath,
		nullableConnectorPushString(adoption.Snapshot.RemoteIDField),
		nullableConnectorPushString(adoption.Snapshot.RemoteIDProvenanceField),
		adoption.Snapshot.MappingFingerprint, adoption.Snapshot.BodyFingerprint,
		ConnectorPushCompatibilityMirrorSynced,
	)
	if err != nil {
		if conflictErr := connectorPushDuplicateKeyConflict(err); conflictErr != nil {
			return nil, conflictErr
		}
		return nil, err
	}
	if err := tx.Commit(); err != nil {
		return nil, err
	}
	return s.FindLink(ctx, adoption.LinkKey)
}

func connectorPushLegacyMirrorActionSlotIDs(contract connector.ConnectorPushDurableMetadataContract) []string {
	out := []string{strings.TrimSpace(contract.ActionSlotID)}
	seen := map[string]struct{}{strings.ToLower(strings.TrimSpace(contract.ActionSlotID)): {}}
	for _, previous := range contract.PreviousActionSlotIDs {
		previous = strings.TrimSpace(previous)
		if previous == "" {
			continue
		}
		key := strings.ToLower(previous)
		if _, ok := seen[key]; ok {
			continue
		}
		seen[key] = struct{}{}
		out = append(out, previous)
	}
	return out
}
