package catalogapp

import (
	"context"
	"errors"
	"fmt"

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

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

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

// VerifiedAppLayoutBundleEvidenceCapability is an opaque, process-local proof
// that the App authority derived Evidence and Receipt while their exact source
// and App owner read fences were live.
type VerifiedAppLayoutBundleEvidenceCapability struct {
	authority       *appAuthorityIdentity
	claim           *catalogpublication.CandidateSourceAppVerification
	evidence        applayoutbundleauthorityv1.Evidence
	evidenceDigest  string
	receipt         layoutbundleownerverificationv1.Receipt
	receiptDigest   string
	catalogRevision uint64
	activationEpoch uint64
}

// VerifyLayoutBundleOwner revalidates the exact locked source-selected App
// tuple, immutable release, slot reservation, and complete slot lineage. 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,
) (AppLayoutBundleOwnerVerification, error) {
	if a == nil || a.db == nil || a.identity == nil {
		return AppLayoutBundleOwnerVerification{}, ErrConflict
	}
	if source == nil {
		return AppLayoutBundleOwnerVerification{}, ErrConflict
	}
	claim, err := source.BeginAppBundleKeyVerification(key)
	if err != nil {
		return AppLayoutBundleOwnerVerification{}, mapAppOwnerVerificationError(err)
	}
	complete := false
	defer func() {
		if !complete {
			claim.Abort()
		}
	}()

	ownerFact, err := catalogverificationtx.VerifyAppOwnerClaim(ctx, a.db, claim)
	if err != nil {
		return AppLayoutBundleOwnerVerification{}, mapAppOwnerVerificationError(err)
	}
	sourceFacts, err := claim.Facts()
	if err != nil {
		return AppLayoutBundleOwnerVerification{}, mapAppOwnerVerificationError(err)
	}
	if ownerFact.AppInstallationID != sourceFacts.AppInstallationID ||
		ownerFact.AppReleaseDigest != sourceFacts.AppReleaseDigest {
		return AppLayoutBundleOwnerVerification{}, fmt.Errorf(
			"%w: App owner fact differs from the exact source selection",
			ErrConflict,
		)
	}

	evidence, evidenceDigest, receipt, receiptDigest, err :=
		deriveAppLayoutBundleOwnerValues(sourceFacts, ownerFact)
	if err != nil {
		return AppLayoutBundleOwnerVerification{}, err
	}
	if err := catalogverificationtx.CompleteAppClaim(claim, receipt); err != nil {
		return AppLayoutBundleOwnerVerification{}, mapAppOwnerVerificationError(err)
	}

	capability := &VerifiedAppLayoutBundleEvidenceCapability{
		authority:       a.identity,
		claim:           claim,
		evidence:        evidence,
		evidenceDigest:  evidenceDigest,
		receipt:         receipt,
		receiptDigest:   receiptDigest,
		catalogRevision: ownerFact.CatalogRevision,
		activationEpoch: ownerFact.ActivationEpoch,
	}
	if err := capability.Validate(); err != nil {
		return AppLayoutBundleOwnerVerification{}, err
	}
	complete = true
	return AppLayoutBundleOwnerVerification{
		Evidence:       evidence,
		EvidenceDigest: evidenceDigest,
		Receipt:        receipt,
		ReceiptDigest:  receiptDigest,
		Capability:     capability,
	}, nil
}

func deriveAppLayoutBundleOwnerValues(
	sourceFacts catalogverificationtx.AppSourceFacts,
	ownerFact catalogverificationtx.AppOwnerFact,
) (
	applayoutbundleauthorityv1.Evidence,
	string,
	layoutbundleownerverificationv1.Receipt,
	string,
	error,
) {
	evidence := applayoutbundleauthorityv1.Evidence{
		EvidenceVersion:       applayoutbundleauthorityv1.EvidenceVersion,
		SourceCatalogSnapshot: sourceFacts.SourceCatalogSnapshot,
		BundleKey: applayoutbundleauthorityv1.BundleKey{
			BundleKeyVersion:   layoutbundlekeyv1.BundleKeyVersion,
			SlotOwnerKind:      sourceFacts.BundleKey.SlotOwnerKind,
			SlotOwnerNamespace: sourceFacts.BundleKey.SlotOwnerNamespace,
			LocalComponentKey:  sourceFacts.BundleKey.LocalComponentKey,
		},
		AppInstallationID:              ownerFact.AppInstallationID,
		AppReleaseDigest:               ownerFact.AppReleaseDigest,
		DeclarationComponentID:         ownerFact.DeclarationComponentID,
		DefinitionVersion:              ownerFact.DefinitionVersion,
		LayoutBundleSlotRevisionDigest: ownerFact.LayoutBundleSlotRevisionDigest,
	}
	evidenceDigest, err := applayoutbundleauthorityv1.EvidenceDigest(evidence)
	if err != nil {
		return applayoutbundleauthorityv1.Evidence{}, "", layoutbundleownerverificationv1.Receipt{}, "", fmt.Errorf(
			"%w: derive App 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 applayoutbundleauthorityv1.Evidence{}, "", layoutbundleownerverificationv1.Receipt{}, "", fmt.Errorf(
			"%w: derive App owner receipt: %v",
			ErrCorrupt,
			err,
		)
	}
	return evidence, evidenceDigest, receipt, receiptDigest, nil
}

// Validate proves that the exact source claim, owner fence, evidence, and
// receipt bound when this capability was minted are still live and coherent.
func (capability *VerifiedAppLayoutBundleEvidenceCapability) Validate() error {
	if capability == nil ||
		capability.authority == nil ||
		capability.claim == nil ||
		!capability.claim.Active() {
		return ErrConflict
	}
	if capability.activationEpoch == 0 {
		return fmt.Errorf("%w: App authority fact version is invalid", ErrCorrupt)
	}
	if err := applayoutbundleauthorityv1.ValidateEvidence(capability.evidence); err != nil {
		return fmt.Errorf("%w: bound App evidence: %v", ErrCorrupt, err)
	}
	evidenceDigest, err := applayoutbundleauthorityv1.EvidenceDigest(capability.evidence)
	if err != nil || evidenceDigest != capability.evidenceDigest {
		return fmt.Errorf("%w: bound App evidence digest differs", ErrCorrupt)
	}
	if err := layoutbundleownerverificationv1.ValidateReceipt(capability.receipt); err != nil {
		return fmt.Errorf("%w: bound App owner receipt: %v", ErrCorrupt, err)
	}
	receiptDigest, err := layoutbundleownerverificationv1.ReceiptDigest(capability.receipt)
	if err != nil || receiptDigest != capability.receiptDigest {
		return fmt.Errorf("%w: bound App owner receipt digest differs", ErrCorrupt)
	}
	evidenceKey := layoutbundlekeyv1.BundleKey{
		SlotOwnerKind:      capability.evidence.BundleKey.SlotOwnerKind,
		SlotOwnerNamespace: capability.evidence.BundleKey.SlotOwnerNamespace,
		LocalComponentKey:  capability.evidence.BundleKey.LocalComponentKey,
	}
	if capability.receipt.BundleKey != evidenceKey ||
		capability.receipt.EvidenceVersion != capability.evidence.EvidenceVersion ||
		capability.receipt.EvidenceDigest != capability.evidenceDigest {
		return fmt.Errorf("%w: bound App evidence and receipt differ", 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 *VerifiedAppLayoutBundleEvidenceCapability) Evidence() (
	applayoutbundleauthorityv1.Evidence,
	error,
) {
	if err := capability.Validate(); err != nil {
		return applayoutbundleauthorityv1.Evidence{}, err
	}
	return capability.evidence, nil
}

// Receipt returns the exact source-bound receipt only while the capability is
// still valid.
func (capability *VerifiedAppLayoutBundleEvidenceCapability) 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 *VerifiedAppLayoutBundleEvidenceCapability) 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 *VerifiedAppLayoutBundleEvidenceCapability) ReceiptDigest() (
	string,
	error,
) {
	if err := capability.Validate(); err != nil {
		return "", err
	}
	return capability.receiptDigest, nil
}

// MarshalJSON rejects persistence of a live App owner fence.
func (*VerifiedAppLayoutBundleEvidenceCapability) MarshalJSON() ([]byte, error) {
	return nil, errVerifiedAppEvidenceHasNoJSON
}

// UnmarshalJSON rejects caller construction of a live App owner fence.
func (*VerifiedAppLayoutBundleEvidenceCapability) UnmarshalJSON([]byte) error {
	return errVerifiedAppEvidenceHasNoJSON
}

func mapAppOwnerVerificationError(err error) error {
	switch {
	case errors.Is(err, catalogverificationtx.ErrAppOwnerNotFound):
		return fmt.Errorf("%w: App owner fact is absent", ErrNotFound)
	case errors.Is(err, catalogverificationtx.ErrAppOwnerCorrupt):
		return fmt.Errorf("%w: %v", ErrCorrupt, err)
	case errors.Is(err, catalogverificationtx.ErrAppOwnerConflict),
		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
	}
}
