package managedlifecycle

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. Commit-outcome proof remains
// the caller's responsibility because this kernel never commits the transaction.
type ManagedLifecycleEffectAdapter struct {
	Class      cp.ComponentManagedEffectClass
	LockPolicy ManagedLifecycleEffectLockPolicy
	Apply      func(context.Context, *sql.Tx, ManagedLifecycleEffectRequest) ([]byte, error)
}

// 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 {
			return fmt.Errorf("managed lifecycle effect adapter %q requires an apply callback", 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
}
