package packs

import (
	"context"
	"database/sql"
	"fmt"
	"reflect"
	"strings"

	cp "github.com/pingcap-inc/nexuscrm-contract/pkg/controlplane"
)

// ManagedLifecycleEffectRequest identifies one transaction-bound effect call.
// The provider never receives the transaction or this host-owned correlation
// identity.
type ManagedLifecycleEffectRequest struct {
	OperationID []byte
	Attempt     int
	Kind        cp.ComponentKind
	Scope       cp.ComponentManagedScope
	Disposition cp.ComponentManagedMutationDisposition
}

// ManagedLifecycleCallerTransaction is an opaque attestation issued by the
// owner that opened a caller-managed SQL transaction. database/sql does not
// expose the effective mode of an existing *sql.Tx, and reading SESSION
// defaults after Begin does not prove transaction-scoped settings. The owner
// must therefore attest the effective mode from its own transaction markers.
//
// A zero value is invalid. Use AttestManagedLifecycleCallerTransaction only
// after the transaction owner has proved the effective mode represented by
// mode for this exact tx.
type ManagedLifecycleCallerTransaction struct {
	tx   *sql.Tx
	mode cp.ComponentManagedTransactionMode
}

// ManagedLifecyclePlannedTransactionRequest is the transaction owner's
// canonical work envelope. Desired item accounting comes from these keys, not
// from a provider-controlled plan estimate. This mirrors the existing exact-set
// path, where the host counts caller-requested business keys independently of
// provider planning.
type ManagedLifecyclePlannedTransactionRequest struct {
	desiredBusinessKeys []string
}

// NewManagedLifecyclePlannedTransactionRequest freezes a sorted, unique set
// of caller-owned work identities for one planned transaction attempt.
func NewManagedLifecyclePlannedTransactionRequest(
	desiredBusinessKeys []string,
) (ManagedLifecyclePlannedTransactionRequest, error) {
	keys, err := canonicalManagedComponentKeys(desiredBusinessKeys)
	if err != nil {
		return ManagedLifecyclePlannedTransactionRequest{}, fmt.Errorf(
			"managed lifecycle planned transaction request: %w",
			err,
		)
	}
	if len(keys) == 0 {
		return ManagedLifecyclePlannedTransactionRequest{}, fmt.Errorf(
			"managed lifecycle planned transaction request requires at least one desired business key",
		)
	}
	return ManagedLifecyclePlannedTransactionRequest{desiredBusinessKeys: keys}, nil
}

// DesiredBusinessKeys returns a defensive copy of the canonical caller-owned
// work identities.
func (request ManagedLifecyclePlannedTransactionRequest) DesiredBusinessKeys() []string {
	return append([]string(nil), request.desiredBusinessKeys...)
}

func (request ManagedLifecyclePlannedTransactionRequest) desiredItems() (int, error) {
	if len(request.desiredBusinessKeys) == 0 {
		return 0, fmt.Errorf("managed lifecycle planned transaction request is required")
	}
	keys, err := canonicalManagedComponentKeys(request.desiredBusinessKeys)
	if err != nil {
		return 0, fmt.Errorf("managed lifecycle planned transaction request: %w", err)
	}
	if !reflect.DeepEqual(keys, request.desiredBusinessKeys) {
		return 0, fmt.Errorf("managed lifecycle planned transaction request is not canonical")
	}
	return len(keys), nil
}

// AttestManagedLifecycleCallerTransaction binds an owner-proved effective
// transaction mode to the exact SQL transaction used by managed staging.
func AttestManagedLifecycleCallerTransaction(
	tx *sql.Tx,
	mode cp.ComponentManagedTransactionMode,
) (ManagedLifecycleCallerTransaction, error) {
	if tx == nil {
		return ManagedLifecycleCallerTransaction{}, fmt.Errorf(
			"managed lifecycle caller transaction is required",
		)
	}
	if mode != cp.ComponentManagedTransactionPessimisticReadCommitted {
		return ManagedLifecycleCallerTransaction{}, fmt.Errorf(
			"managed lifecycle caller transaction mode %q is unsupported",
			mode,
		)
	}
	return ManagedLifecycleCallerTransaction{tx: tx, mode: mode}, nil
}

func (transaction ManagedLifecycleCallerTransaction) require(
	mode cp.ComponentManagedTransactionMode,
) (*sql.Tx, error) {
	if transaction.tx == nil {
		return nil, fmt.Errorf("managed lifecycle caller transaction attestation is required")
	}
	if transaction.mode != mode {
		return nil, fmt.Errorf(
			"managed lifecycle caller transaction mode %q does not satisfy lifecycle mode %q",
			transaction.mode,
			mode,
		)
	}
	return transaction.tx, nil
}

// ManagedLifecycleEffectLockPolicy declares the lock boundary an effect must
// preserve when it runs in either a host-owned or caller-owned transaction.
type ManagedLifecycleEffectLockPolicy string

const (
	// ManagedLifecycleEffectManagedStateOnly permits an effect to lock only
	// its own managed effect state. It may not acquire lifecycle source locks.
	ManagedLifecycleEffectManagedStateOnly ManagedLifecycleEffectLockPolicy = "managed_state_only"
)

// ManagedLifecycleEffectAdapter is one injected transaction-bound effect.
// Apply must use tx for all writes and must not acquire or wait on locks from
// a caller-owned source domain. Caller-transaction staging locks source rows
// before managed targets; reacquiring those source locks after target
// serialization would create a reverse lock edge. Prove performs a fresh read
// after an uncertain commit and validates the opaque receipt returned by Apply.
type ManagedLifecycleEffectAdapter struct {
	Class      cp.ComponentManagedEffectClass
	LockPolicy ManagedLifecycleEffectLockPolicy
	Apply      func(context.Context, *sql.Tx, ManagedLifecycleEffectRequest) ([]byte, error)
	Prove      func(context.Context, *sql.DB, ManagedLifecycleEffectRequest, []byte) (bool, error)
}

// ManagedLifecycleVerification is the fresh provider projection and its exact
// byte comparison result.
type ManagedLifecycleVerification struct {
	Projection cp.ComponentManagedProjection
	Matches    bool
}

type ManagedLifecycleOperationDisposition string

const (
	ManagedLifecycleOperationNoChange            ManagedLifecycleOperationDisposition = "successful_no_op"
	ManagedLifecycleOperationChanged             ManagedLifecycleOperationDisposition = "successful_semantic_change"
	ManagedLifecycleOperationSafe                ManagedLifecycleOperationDisposition = "applied_safe_desired_invalid"
	ManagedLifecycleOperationVerificationFailure ManagedLifecycleOperationDisposition = "committed_verification_failure"
	ManagedLifecycleOperationNoMutationFailure   ManagedLifecycleOperationDisposition = "no_mutation_failure"
	ManagedLifecycleOperationRolledBackFailure   ManagedLifecycleOperationDisposition = "rolled_back_failure"
	ManagedLifecycleOperationIndeterminate       ManagedLifecycleOperationDisposition = "indeterminate_commit_outcome"
)

type ManagedLifecycleOperationResult struct {
	Disposition  ManagedLifecycleOperationDisposition
	OperationID  []byte
	Attempts     int
	Verification ManagedLifecycleVerification
}

// ValidateManagedLifecycleEffectAdapters validates one distribution-owned
// effect registry before it is frozen into runner provenance.
func ValidateManagedLifecycleEffectAdapters(effects []ManagedLifecycleEffectAdapter) error {
	seen := make(map[cp.ComponentManagedEffectClass]struct{}, len(effects))
	for index, adapter := range effects {
		if strings.TrimSpace(string(adapter.Class)) == "" || strings.TrimSpace(string(adapter.Class)) != string(adapter.Class) {
			return fmt.Errorf("managed lifecycle effect adapter %d class is not canonical", index)
		}
		if adapter.Apply == nil || adapter.Prove == nil {
			return fmt.Errorf("managed lifecycle effect adapter %q requires apply and prove callbacks", adapter.Class)
		}
		if adapter.LockPolicy != ManagedLifecycleEffectManagedStateOnly {
			return fmt.Errorf(
				"managed lifecycle effect adapter %q lock policy %q is unsupported",
				adapter.Class,
				adapter.LockPolicy,
			)
		}
		if _, duplicate := seen[adapter.Class]; duplicate {
			return fmt.Errorf("managed lifecycle effect adapter %q is duplicated", adapter.Class)
		}
		seen[adapter.Class] = struct{}{}
	}
	return nil
}
