package catalogverificationtx

import (
	"database/sql"

	catalogpublicationv1 "github.com/pingcap-inc/nexuscrm-contract/pkg/catalogpublicationv1"
	layoutbundlekeyv1 "github.com/pingcap-inc/nexuscrm-contract/pkg/layoutbundlekeyv1"
)

type sourceIdentity struct {
	catalogSnapshot             catalogpublicationv1.CatalogSnapshotBinding
	orgID                       string
	writerCommandID             string
	writerReservationGeneration uint64
	sourceRequestDigest         string
	requiredBundleKeySetDigest  string
}

// sourceBinding is a one-shot exact source identity tied to one retained
// production transaction. Its zero value cannot authorize a source lease.
type sourceBinding struct {
	tx       *sql.Tx
	identity sourceIdentity
	bound    bool
}

// BindValidatedSource binds the exact source identity only after
// catalogpublication has locked and validated the org writer, source row,
// required set, active baseline, and live writer reservation. The binding is
// one-shot and cannot be transferred to another transaction.
func (token *Token) BindValidatedSource(
	catalogSnapshot catalogpublicationv1.CatalogSnapshotBinding,
	orgID string,
	writerCommandID string,
	writerReservationGeneration uint64,
	sourceRequestDigest string,
	requiredBundleKeySetDigest string,
) error {
	if token == nil {
		return ErrClosed
	}
	token.mu.Lock()
	defer token.mu.Unlock()
	if !token.activeForLocked(token.db) {
		return ErrClosed
	}
	if !token.owned || token.conn == nil || token.mode == nil {
		return ErrAuthorityMismatch
	}
	if err := catalogpublicationv1.ValidateCatalogSnapshotBinding(
		catalogSnapshot,
	); err != nil {
		return ErrAuthorityMismatch
	}
	if token.source.bound ||
		catalogSnapshot.OrgID != orgID ||
		orgID == "" ||
		writerCommandID == "" ||
		writerReservationGeneration == 0 ||
		sourceRequestDigest == "" ||
		requiredBundleKeySetDigest == "" {
		return ErrAuthorityMismatch
	}
	token.source = sourceBinding{
		tx: token.tx,
		identity: sourceIdentity{
			catalogSnapshot:             catalogSnapshot,
			orgID:                       orgID,
			writerCommandID:             writerCommandID,
			writerReservationGeneration: writerReservationGeneration,
			sourceRequestDigest:         sourceRequestDigest,
			requiredBundleKeySetDigest:  requiredBundleKeySetDigest,
		},
		bound: true,
	}
	return nil
}

func (token *Token) sourceBindingActive() bool {
	if token == nil {
		return false
	}
	token.mu.RLock()
	defer token.mu.RUnlock()
	if !token.activeForLocked(token.db) {
		return false
	}
	if !token.owned {
		// The legacy transaction-only form exists solely for focused verifier
		// tests. Every production owned token must carry an exact source binding.
		return true
	}
	return token.source.activeFor(token.tx)
}

func (token *Token) requireSourceBinding() error {
	if token == nil {
		return ErrClosed
	}
	token.mu.RLock()
	defer token.mu.RUnlock()
	if !token.activeForLocked(token.db) {
		return ErrClosed
	}
	if !token.owned {
		return nil
	}
	if !token.source.activeFor(token.tx) {
		return ErrAuthorityMismatch
	}
	return nil
}

// bindRequiredSet returns the exact validated source identity only when the
// caller supplies the complete required-key set whose digest was bound by
// catalogpublication. The defensive canonical copy is owned by the returned
// source lease; callers cannot later change its coverage.
func (token *Token) bindRequiredSet(
	required layoutbundlekeyv1.RequiredBundleKeySet,
) (sourceIdentity, layoutbundlekeyv1.RequiredBundleKeySet, error) {
	canonical, err := layoutbundlekeyv1.CanonicalizeRequiredBundleKeySet(required)
	if err != nil {
		return sourceIdentity{}, layoutbundlekeyv1.RequiredBundleKeySet{}, ErrAuthorityMismatch
	}
	digest, err := layoutbundlekeyv1.RequiredBundleKeySetDigest(canonical)
	if err != nil {
		return sourceIdentity{}, layoutbundlekeyv1.RequiredBundleKeySet{}, ErrAuthorityMismatch
	}

	if token == nil {
		return sourceIdentity{}, layoutbundlekeyv1.RequiredBundleKeySet{}, ErrClosed
	}
	token.mu.RLock()
	defer token.mu.RUnlock()
	if !token.activeForLocked(token.db) {
		return sourceIdentity{}, layoutbundlekeyv1.RequiredBundleKeySet{}, ErrClosed
	}
	if !token.owned {
		// The transaction-only form is constructible only inside this package
		// and exists solely for focused verifier tests. Production leases are
		// owned and must match the source-bound digest below.
		return sourceIdentity{}, canonical, nil
	}
	if !token.source.activeFor(token.tx) ||
		token.source.identity.requiredBundleKeySetDigest != digest {
		return sourceIdentity{}, layoutbundlekeyv1.RequiredBundleKeySet{}, ErrAuthorityMismatch
	}
	return token.source.identity, canonical, nil
}

func (token *Token) requireSourceIdentity(identity sourceIdentity) error {
	if token == nil {
		return ErrClosed
	}
	token.mu.RLock()
	defer token.mu.RUnlock()
	if !token.activeForLocked(token.db) {
		return ErrClosed
	}
	if !token.owned {
		return nil
	}
	if !token.source.activeFor(token.tx) ||
		token.source.identity != identity {
		return ErrAuthorityMismatch
	}
	return nil
}

func (binding sourceBinding) activeFor(tx *sql.Tx) bool {
	return binding.bound &&
		binding.tx != nil &&
		binding.tx == tx
}

func appSourceIdentity(facts AppSourceFacts) sourceIdentity {
	return sourceIdentity{
		catalogSnapshot:             facts.SourceCatalogSnapshot,
		orgID:                       facts.OrgID,
		writerCommandID:             facts.WriterCommandID,
		writerReservationGeneration: facts.WriterReservationGeneration,
		sourceRequestDigest:         facts.SourceRequestDigest,
		requiredBundleKeySetDigest:  facts.RequiredBundleKeySetDigest,
	}
}

func tenantSourceIdentity(facts TenantSourceFacts) sourceIdentity {
	return sourceIdentity{
		catalogSnapshot:             facts.SourceCatalogSnapshot,
		orgID:                       facts.OrgID,
		writerCommandID:             facts.WriterCommandID,
		writerReservationGeneration: facts.WriterReservationGeneration,
		sourceRequestDigest:         facts.SourceRequestDigest,
		requiredBundleKeySetDigest:  facts.RequiredBundleKeySetDigest,
	}
}

func systemSourceIdentity(facts SystemSourceFacts) sourceIdentity {
	return sourceIdentity{
		catalogSnapshot:             facts.SourceCatalogSnapshot,
		orgID:                       facts.OrgID,
		writerCommandID:             facts.WriterCommandID,
		writerReservationGeneration: facts.WriterReservationGeneration,
		sourceRequestDigest:         facts.SourceRequestDigest,
		requiredBundleKeySetDigest:  facts.RequiredBundleKeySetDigest,
	}
}
