package catalogtenant

import (
	"context"
	"errors"
	"fmt"

	layoutbundlekeyv1 "github.com/pingcap-inc/nexuscrm-contract/pkg/layoutbundlekeyv1"
	layoutbundleownerverificationv1 "github.com/pingcap-inc/nexuscrm-contract/pkg/layoutbundleownerverificationv1"
	tenantauthorityv1 "github.com/pingcap-inc/nexuscrm-contract/pkg/tenantlayoutbundleauthorityv1"
	"github.com/pingcap-inc/nexuscrm-controlplane/catalogpublication"
	"github.com/pingcap-inc/nexuscrm-controlplane/internal/catalogverificationtx"
)

var errVerifiedTenantEvidenceHasNoJSON = errors.New(
	"verified tenant layout-bundle evidence capability has no JSON form",
)

// TenantLayoutBundleOwnerVerification is the complete success output for one
// exact source-required tenant-owned BundleKey. The values alone grant no
// authority; consumers must retain and validate Capability.
type TenantLayoutBundleOwnerVerification struct {
	Evidence       tenantauthorityv1.TenantLayoutBundleAuthorityEvidenceV1
	EvidenceDigest string
	Receipt        layoutbundleownerverificationv1.Receipt
	ReceiptDigest  string
	Capability     *VerifiedTenantLayoutBundleEvidenceCapability
}

// VerifiedTenantLayoutBundleEvidenceCapability is an opaque, process-local
// proof that the tenant authority derived Evidence and Receipt while their
// exact source, namespace, OrgID-binding, and slot read fences were live.
type VerifiedTenantLayoutBundleEvidenceCapability struct {
	authority                   *tenantAuthorityIdentity
	claim                       *catalogpublication.CandidateSourceTenantVerification
	evidence                    tenantauthorityv1.TenantLayoutBundleAuthorityEvidenceV1
	evidenceDigest              string
	receipt                     layoutbundleownerverificationv1.Receipt
	receiptDigest               string
	slotCreationCommandSequence uint64
	namespaceReservationDigest  string
	orgBindingDigest            string
	slotDigest                  string
}

// VerifyLayoutBundleOwner revalidates the exact locked source OrgID, ACTIVE
// namespace reservation, ACTIVE OrgID binding, ACTIVE tenant slot, and their
// immutable creation lineages. It accepts no caller evidence, receipt, SQL,
// query callback, or generic proof adapter.
func (a *Authority) VerifyLayoutBundleOwner(
	ctx context.Context,
	source *catalogpublication.CandidateSourceVerificationLease,
	key layoutbundlekeyv1.BundleKey,
) (TenantLayoutBundleOwnerVerification, error) {
	if a == nil || a.db == nil || a.identity == nil || source == nil {
		return TenantLayoutBundleOwnerVerification{}, ErrConflict
	}
	claim, err := source.BeginTenantBundleKeyVerification(key)
	if err != nil {
		return TenantLayoutBundleOwnerVerification{}, mapTenantOwnerVerificationError(err)
	}
	complete := false
	defer func() {
		if !complete {
			claim.Abort()
		}
	}()

	ownerFact, err := catalogverificationtx.VerifyTenantOwnerClaim(ctx, a.db, claim)
	if err != nil {
		return TenantLayoutBundleOwnerVerification{}, mapTenantOwnerVerificationError(err)
	}
	sourceFacts, err := claim.Facts()
	if err != nil {
		return TenantLayoutBundleOwnerVerification{}, mapTenantOwnerVerificationError(err)
	}
	if err := validateTenantOwnerFactAgainstSource(sourceFacts, ownerFact); err != nil {
		return TenantLayoutBundleOwnerVerification{}, err
	}

	evidence, evidenceDigest, receipt, receiptDigest, err :=
		deriveTenantLayoutBundleOwnerValues(sourceFacts, ownerFact)
	if err != nil {
		return TenantLayoutBundleOwnerVerification{}, err
	}
	if err := catalogverificationtx.CompleteTenantClaim(claim, receipt); err != nil {
		return TenantLayoutBundleOwnerVerification{}, mapTenantOwnerVerificationError(err)
	}

	capability := &VerifiedTenantLayoutBundleEvidenceCapability{
		authority:                   a.identity,
		claim:                       claim,
		evidence:                    evidence,
		evidenceDigest:              evidenceDigest,
		receipt:                     receipt,
		receiptDigest:               receiptDigest,
		slotCreationCommandSequence: ownerFact.SlotCreationCommandSequence,
		namespaceReservationDigest:  ownerFact.NamespaceReservationDigest,
		orgBindingDigest:            ownerFact.OrgBindingDigest,
		slotDigest:                  ownerFact.SlotDigest,
	}
	if err := capability.Validate(); err != nil {
		return TenantLayoutBundleOwnerVerification{}, err
	}
	complete = true
	return TenantLayoutBundleOwnerVerification{
		Evidence:       evidence,
		EvidenceDigest: evidenceDigest,
		Receipt:        receipt,
		ReceiptDigest:  receiptDigest,
		Capability:     capability,
	}, nil
}

func validateTenantOwnerFactAgainstSource(
	sourceFacts catalogverificationtx.TenantSourceFacts,
	ownerFact catalogverificationtx.TenantOwnerFact,
) error {
	namespace := sourceFacts.BundleKey.SlotOwnerNamespace
	if sourceFacts.SourceCatalogSnapshot.OrgID != sourceFacts.OrgID ||
		ownerFact.NamespaceReservation.TenantNamespaceID != namespace ||
		ownerFact.OrgBinding.OrgID != sourceFacts.OrgID ||
		ownerFact.OrgBinding.TenantNamespaceID != namespace ||
		portableTenantBundleKey(ownerFact.Slot.BundleKey) != sourceFacts.BundleKey {
		return fmt.Errorf(
			"%w: tenant owner fact differs from the exact source OrgID or BundleKey",
			ErrConflict,
		)
	}
	if ownerFact.SlotCreationCommandSequence == 0 {
		return fmt.Errorf("%w: tenant slot creation sequence is invalid", ErrCorrupt)
	}
	if err := validateTenantOwnerFactDigests(ownerFact); err != nil {
		return err
	}
	return nil
}

func validateTenantOwnerFactDigests(
	ownerFact catalogverificationtx.TenantOwnerFact,
) error {
	namespaceDigest, err := tenantauthorityv1.NamespaceValueDigest(
		ownerFact.NamespaceReservation,
	)
	if err != nil || namespaceDigest != ownerFact.NamespaceReservationDigest {
		return fmt.Errorf("%w: locked tenant namespace digest differs", ErrCorrupt)
	}
	bindingDigest, err := tenantauthorityv1.OrgBindingValueDigest(ownerFact.OrgBinding)
	if err != nil || bindingDigest != ownerFact.OrgBindingDigest {
		return fmt.Errorf("%w: locked tenant OrgID binding digest differs", ErrCorrupt)
	}
	slotDigest, err := tenantauthorityv1.SlotValueDigest(ownerFact.Slot)
	if err != nil || slotDigest != ownerFact.SlotDigest {
		return fmt.Errorf("%w: locked tenant slot digest differs", ErrCorrupt)
	}
	return nil
}

func deriveTenantLayoutBundleOwnerValues(
	sourceFacts catalogverificationtx.TenantSourceFacts,
	ownerFact catalogverificationtx.TenantOwnerFact,
) (
	tenantauthorityv1.TenantLayoutBundleAuthorityEvidenceV1,
	string,
	layoutbundleownerverificationv1.Receipt,
	string,
	error,
) {
	evidence := tenantauthorityv1.TenantLayoutBundleAuthorityEvidenceV1{
		EvidenceVersion:                  tenantauthorityv1.EvidenceVersion,
		SourceCatalogSnapshot:            sourceFacts.SourceCatalogSnapshot,
		BundleKey:                        ownerFact.Slot.BundleKey,
		NamespaceReservationValueVersion: ownerFact.NamespaceReservation.ValueVersion,
		NamespaceReservationState:        ownerFact.NamespaceReservation.State,
		OrgBindingValueVersion:           ownerFact.OrgBinding.ValueVersion,
		OrgBindingState:                  ownerFact.OrgBinding.State,
		SlotValueVersion:                 ownerFact.Slot.ValueVersion,
		SlotState:                        ownerFact.Slot.State,
	}
	evidenceDigest, err := tenantauthorityv1.EvidenceDigest(evidence)
	if err != nil {
		return tenantauthorityv1.TenantLayoutBundleAuthorityEvidenceV1{}, "", layoutbundleownerverificationv1.Receipt{}, "", fmt.Errorf(
			"%w: derive tenant authority evidence: %v",
			ErrCorrupt,
			err,
		)
	}
	receipt := layoutbundleownerverificationv1.Receipt{
		ReceiptVersion:              layoutbundleownerverificationv1.ReceiptVersion,
		OrgID:                       sourceFacts.OrgID,
		WriterCommandID:             sourceFacts.WriterCommandID,
		WriterReservationGeneration: sourceFacts.WriterReservationGeneration,
		SourceRequestDigest:         sourceFacts.SourceRequestDigest,
		RequiredBundleKeySetDigest:  sourceFacts.RequiredBundleKeySetDigest,
		BundleKey:                   sourceFacts.BundleKey,
		EvidenceVersion:             evidence.EvidenceVersion,
		EvidenceDigest:              evidenceDigest,
		VerificationState:           layoutbundleownerverificationv1.VerificationStateVerified,
	}
	receiptDigest, err := layoutbundleownerverificationv1.ReceiptDigest(receipt)
	if err != nil {
		return tenantauthorityv1.TenantLayoutBundleAuthorityEvidenceV1{}, "", layoutbundleownerverificationv1.Receipt{}, "", fmt.Errorf(
			"%w: derive tenant owner receipt: %v",
			ErrCorrupt,
			err,
		)
	}
	return evidence, evidenceDigest, receipt, receiptDigest, nil
}

// Validate proves that the exact source claim, tenant owner fence, evidence,
// receipt, and private authority value digests bound when this capability was
// minted are still live and coherent.
func (capability *VerifiedTenantLayoutBundleEvidenceCapability) Validate() error {
	if capability == nil ||
		capability.authority == nil ||
		capability.claim == nil ||
		!capability.claim.Active() {
		return ErrConflict
	}
	if capability.slotCreationCommandSequence == 0 {
		return fmt.Errorf("%w: tenant slot creation sequence is invalid", ErrCorrupt)
	}
	if err := tenantauthorityv1.ValidateEvidence(capability.evidence); err != nil {
		return fmt.Errorf("%w: bound tenant evidence: %v", ErrCorrupt, err)
	}
	evidenceDigest, err := tenantauthorityv1.EvidenceDigest(capability.evidence)
	if err != nil || evidenceDigest != capability.evidenceDigest {
		return fmt.Errorf("%w: bound tenant evidence digest differs", ErrCorrupt)
	}
	if err := layoutbundleownerverificationv1.ValidateReceipt(capability.receipt); err != nil {
		return fmt.Errorf("%w: bound tenant owner receipt: %v", ErrCorrupt, err)
	}
	receiptDigest, err := layoutbundleownerverificationv1.ReceiptDigest(capability.receipt)
	if err != nil || receiptDigest != capability.receiptDigest {
		return fmt.Errorf("%w: bound tenant owner receipt digest differs", ErrCorrupt)
	}

	evidenceKey := portableTenantBundleKey(capability.evidence.BundleKey)
	if capability.receipt.BundleKey != evidenceKey ||
		capability.receipt.EvidenceVersion != capability.evidence.EvidenceVersion ||
		capability.receipt.EvidenceDigest != capability.evidenceDigest {
		return fmt.Errorf("%w: bound tenant evidence and receipt differ", ErrCorrupt)
	}
	namespaceValue := tenantauthorityv1.TenantNamespaceReservationV1{
		ValueVersion:      capability.evidence.NamespaceReservationValueVersion,
		TenantNamespaceID: capability.evidence.BundleKey.SlotOwnerNamespace,
		State:             capability.evidence.NamespaceReservationState,
	}
	bindingValue := tenantauthorityv1.TenantOrgNamespaceBindingV1{
		ValueVersion:      capability.evidence.OrgBindingValueVersion,
		OrgID:             capability.receipt.OrgID,
		TenantNamespaceID: capability.evidence.BundleKey.SlotOwnerNamespace,
		State:             capability.evidence.OrgBindingState,
	}
	slotValue := tenantauthorityv1.TenantLayoutBundleSlotV1{
		ValueVersion: capability.evidence.SlotValueVersion,
		BundleKey:    capability.evidence.BundleKey,
		State:        capability.evidence.SlotState,
	}
	namespaceDigest, err := tenantauthorityv1.NamespaceValueDigest(namespaceValue)
	if err != nil || namespaceDigest != capability.namespaceReservationDigest {
		return fmt.Errorf("%w: bound tenant namespace digest differs", ErrCorrupt)
	}
	bindingDigest, err := tenantauthorityv1.OrgBindingValueDigest(bindingValue)
	if err != nil || bindingDigest != capability.orgBindingDigest {
		return fmt.Errorf("%w: bound tenant OrgID binding digest differs", ErrCorrupt)
	}
	slotDigest, err := tenantauthorityv1.SlotValueDigest(slotValue)
	if err != nil || slotDigest != capability.slotDigest {
		return fmt.Errorf("%w: bound tenant slot digest differs", ErrCorrupt)
	}

	sourceFacts, err := capability.claim.Facts()
	if err != nil ||
		sourceFacts.SourceCatalogSnapshot != capability.evidence.SourceCatalogSnapshot ||
		sourceFacts.BundleKey != capability.receipt.BundleKey ||
		sourceFacts.OrgID != capability.receipt.OrgID ||
		sourceFacts.WriterCommandID != capability.receipt.WriterCommandID ||
		sourceFacts.WriterReservationGeneration != capability.receipt.WriterReservationGeneration ||
		sourceFacts.SourceRequestDigest != capability.receipt.SourceRequestDigest ||
		sourceFacts.RequiredBundleKeySetDigest != capability.receipt.RequiredBundleKeySetDigest {
		return ErrConflict
	}
	return nil
}

// Evidence returns the exact immutable evidence only while the capability is
// still valid.
func (capability *VerifiedTenantLayoutBundleEvidenceCapability) Evidence() (
	tenantauthorityv1.TenantLayoutBundleAuthorityEvidenceV1,
	error,
) {
	if err := capability.Validate(); err != nil {
		return tenantauthorityv1.TenantLayoutBundleAuthorityEvidenceV1{}, err
	}
	return capability.evidence, nil
}

// Receipt returns the exact source-bound receipt only while the capability is
// still valid.
func (capability *VerifiedTenantLayoutBundleEvidenceCapability) Receipt() (
	layoutbundleownerverificationv1.Receipt,
	error,
) {
	if err := capability.Validate(); err != nil {
		return layoutbundleownerverificationv1.Receipt{}, err
	}
	return capability.receipt, nil
}

// EvidenceDigest returns the exact evidence digest bound by the capability.
func (capability *VerifiedTenantLayoutBundleEvidenceCapability) EvidenceDigest() (
	string,
	error,
) {
	if err := capability.Validate(); err != nil {
		return "", err
	}
	return capability.evidenceDigest, nil
}

// ReceiptDigest returns the exact singular receipt digest bound by the
// capability.
func (capability *VerifiedTenantLayoutBundleEvidenceCapability) ReceiptDigest() (
	string,
	error,
) {
	if err := capability.Validate(); err != nil {
		return "", err
	}
	return capability.receiptDigest, nil
}

// MarshalJSON rejects persistence of a live tenant owner fence.
func (*VerifiedTenantLayoutBundleEvidenceCapability) MarshalJSON() ([]byte, error) {
	return nil, errVerifiedTenantEvidenceHasNoJSON
}

// UnmarshalJSON rejects caller construction of a live tenant owner fence.
func (*VerifiedTenantLayoutBundleEvidenceCapability) UnmarshalJSON([]byte) error {
	return errVerifiedTenantEvidenceHasNoJSON
}

func portableTenantBundleKey(
	key tenantauthorityv1.BundleKey,
) layoutbundlekeyv1.BundleKey {
	return layoutbundlekeyv1.BundleKey{
		SlotOwnerKind:      key.SlotOwnerKind,
		SlotOwnerNamespace: key.SlotOwnerNamespace,
		LocalComponentKey:  key.LocalComponentKey,
	}
}

func mapTenantOwnerVerificationError(err error) error {
	switch {
	case errors.Is(err, catalogverificationtx.ErrTenantOwnerNotFound):
		return fmt.Errorf("%w: tenant owner fact is absent", ErrNotFound)
	case errors.Is(err, catalogverificationtx.ErrTenantOwnerCorrupt):
		return fmt.Errorf("%w: %v", ErrCorrupt, err)
	case errors.Is(err, catalogverificationtx.ErrTenantOwnerConflict),
		errors.Is(err, catalogverificationtx.ErrClosed),
		errors.Is(err, catalogverificationtx.ErrAuthorityMismatch),
		errors.Is(err, catalogpublication.ErrSourceConflict),
		errors.Is(err, catalogpublication.ErrSourceNotFound),
		errors.Is(err, catalogpublication.ErrSourceNotReady):
		return fmt.Errorf("%w: %v", ErrConflict, err)
	case errors.Is(err, catalogpublication.ErrSourceCorrupt):
		return fmt.Errorf("%w: %v", ErrCorrupt, err)
	default:
		return err
	}
}
