package catalogverificationtx

import (
	"errors"
	"fmt"
	"sync"

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

// ErrCapabilityHasNoJSON is returned for every attempt to persist or
// reconstruct a process-scoped source or owner-fence capability.
var ErrCapabilityHasNoJSON = errors.New(
	"catalog verification capability has no JSON form",
)

// AppSourceFacts are the exact immutable source facts bound to one App claim.
// They contain no transaction, SQL, callback, or authority proof.
type AppSourceFacts struct {
	SourceCatalogSnapshot       catalogpublicationv1.CatalogSnapshotBinding
	OrgID                       string
	WriterCommandID             string
	WriterReservationGeneration uint64
	SourceRequestDigest         string
	RequiredBundleKeySetDigest  string
	BundleKey                   layoutbundlekeyv1.BundleKey
	AppID                       string
	AppInstallationID           string
	AppReleaseDigest            string
}

// TenantSourceFacts are the exact immutable source facts bound to one
// tenant-owned key claim. They contain no transaction, SQL, callback, or
// authority proof.
type TenantSourceFacts struct {
	SourceCatalogSnapshot       catalogpublicationv1.CatalogSnapshotBinding
	OrgID                       string
	WriterCommandID             string
	WriterReservationGeneration uint64
	SourceRequestDigest         string
	RequiredBundleKeySetDigest  string
	BundleKey                   layoutbundlekeyv1.BundleKey
}

// SystemSourceFacts are the exact immutable source facts bound to one
// system-owned key claim. They contain no transaction, SQL, callback, durable
// authority state, or detached proof.
type SystemSourceFacts struct {
	SourceCatalogSnapshot       catalogpublicationv1.CatalogSnapshotBinding
	OrgID                       string
	WriterCommandID             string
	WriterReservationGeneration uint64
	SourceRequestDigest         string
	RequiredBundleKeySetDigest  string
	BundleKey                   layoutbundlekeyv1.BundleKey
}

// TenantOwnerRequest is the exact source OrgID and required tenant-owned key.
// Only catalogpublication can obtain it from a locked source claim.
type TenantOwnerRequest struct {
	OrgID     string
	BundleKey layoutbundlekeyv1.BundleKey
}

// Lease serializes canonical owner-fence acquisition over one retained source
// transaction. It is never exposed by a public control-plane API.
type Lease struct {
	mu                  sync.Mutex
	token               *Token
	source              sourceIdentity
	required            layoutbundlekeyv1.RequiredBundleKeySet
	receipts            []layoutbundleownerverificationv1.ReceiptEntry
	receiptDigests      []string
	receiptSet          layoutbundleownerverificationv1.ReceiptSet
	receiptSetHash      string
	requiresSystemFence bool
	lastKeyIndex        int
	activeClaim         *claimLifecycle
	sealed              bool
	closed              bool
}

// AppClaim is an opaque exact-key claim. Its public methods expose only source
// facts and lifecycle; the retained transaction and fixed-purpose request stay
// private to this internal package.
type AppClaim struct {
	lease     *Lease
	token     *Token
	request   AppOwnerRequest
	facts     AppSourceFacts
	lifecycle *claimLifecycle
}

// TenantClaim is an opaque exact-key tenant claim. Its public methods expose
// only source facts and lifecycle; the retained transaction and fixed-purpose
// request stay private to this internal package.
type TenantClaim struct {
	lease     *Lease
	token     *Token
	request   TenantOwnerRequest
	facts     TenantSourceFacts
	lifecycle *claimLifecycle
}

// SystemClaim is an opaque exact-key system claim. Its public methods expose
// only source facts and lifecycle; the retained transaction and fixed-purpose
// request stay private to this internal package.
type SystemClaim struct {
	lease     *Lease
	token     *Token
	request   SystemOwnerRequest
	facts     SystemSourceFacts
	lifecycle *claimLifecycle
}

type claimLifecycle struct {
	index int
	state claimState
}

type claimState byte

const (
	claimPending claimState = iota + 1
	claimVerifying
	claimVerified
	claimCompleted
	claimAborted
)

// NewLease creates canonical owner-fence state over one retained token whose
// production transaction carries an exact validated source binding. The
// complete required set is bound once here so later claims cannot skip, add,
// reorder, or substitute keys.
func NewLease(
	token *Token,
	required layoutbundlekeyv1.RequiredBundleKeySet,
) (*Lease, error) {
	if err := token.requireSourceBinding(); err != nil {
		return nil, err
	}
	source, canonical, err := token.bindRequiredSet(required)
	if err != nil {
		return nil, err
	}
	requiresSystemFence := false
	for _, key := range canonical.BundleKeys {
		if key.SlotOwnerKind == layoutbundlekeyv1.SlotOwnerKindSystem {
			requiresSystemFence = true
			break
		}
	}
	return &Lease{
		token:               token,
		source:              source,
		required:            canonical,
		receipts:            make([]layoutbundleownerverificationv1.ReceiptEntry, 0, len(canonical.BundleKeys)),
		receiptDigests:      make([]string, 0, len(canonical.BundleKeys)),
		requiresSystemFence: requiresSystemFence,
		lastKeyIndex:        -1,
	}, nil
}

// BeginAppClaim creates one pending exact-key App claim. Catalogpublication
// supplies index only after validating complete required-set membership.
func (lease *Lease) BeginAppClaim(
	index int,
	facts AppSourceFacts,
	request AppOwnerRequest,
) (*AppClaim, error) {
	if lease == nil || index < 0 {
		return nil, ErrClosed
	}
	lease.mu.Lock()
	defer lease.mu.Unlock()
	if err := lease.token.requireSourceIdentity(appSourceIdentity(facts)); err != nil {
		return nil, err
	}
	lifecycle, err := lease.beginClaimLocked(index, facts.BundleKey)
	if err != nil {
		return nil, err
	}
	claim := &AppClaim{
		lease:     lease,
		token:     lease.token,
		request:   request,
		facts:     facts,
		lifecycle: lifecycle,
	}
	return claim, nil
}

// BeginTenantClaim creates one pending exact-key tenant claim.
// Catalogpublication supplies index only after validating complete required-set
// membership.
func (lease *Lease) BeginTenantClaim(
	index int,
	facts TenantSourceFacts,
	request TenantOwnerRequest,
) (*TenantClaim, error) {
	if lease == nil || index < 0 {
		return nil, ErrClosed
	}
	lease.mu.Lock()
	defer lease.mu.Unlock()
	if err := lease.token.requireSourceIdentity(
		tenantSourceIdentity(facts),
	); err != nil {
		return nil, err
	}
	lifecycle, err := lease.beginClaimLocked(index, facts.BundleKey)
	if err != nil {
		return nil, err
	}
	return &TenantClaim{
		lease:     lease,
		token:     lease.token,
		request:   request,
		facts:     facts,
		lifecycle: lifecycle,
	}, nil
}

// BeginSystemClaim creates one pending exact-key system claim.
// Catalogpublication supplies index only after validating complete required-set
// membership.
func (lease *Lease) BeginSystemClaim(
	index int,
	facts SystemSourceFacts,
	request SystemOwnerRequest,
) (*SystemClaim, error) {
	if lease == nil || index < 0 {
		return nil, ErrClosed
	}
	lease.mu.Lock()
	defer lease.mu.Unlock()
	if err := lease.token.requireSourceIdentity(
		systemSourceIdentity(facts),
	); err != nil {
		return nil, err
	}
	lifecycle, err := lease.beginClaimLocked(index, facts.BundleKey)
	if err != nil {
		return nil, err
	}
	return &SystemClaim{
		lease:     lease,
		token:     lease.token,
		request:   request,
		facts:     facts,
		lifecycle: lifecycle,
	}, nil
}

func (lease *Lease) beginClaimLocked(
	index int,
	key layoutbundlekeyv1.BundleKey,
) (*claimLifecycle, error) {
	if !lease.liveLocked() || lease.activeClaim != nil || lease.sealed {
		return nil, ErrClosed
	}
	if index != lease.lastKeyIndex+1 ||
		index >= len(lease.required.BundleKeys) ||
		lease.required.BundleKeys[index] != key {
		return nil, ErrClaimOrder
	}
	lifecycle := &claimLifecycle{index: index, state: claimPending}
	lease.activeClaim = lifecycle
	return lifecycle, nil
}

// Facts returns a copy of the exact source facts while the claim's retained
// source lease is live.
func (claim *AppClaim) Facts() (AppSourceFacts, error) {
	if claim == nil || claim.lease == nil || claim.lifecycle == nil {
		return AppSourceFacts{}, ErrClosed
	}
	claim.lease.mu.Lock()
	defer claim.lease.mu.Unlock()
	if claim.lifecycle.state == claimAborted || !claim.lease.liveLocked() {
		return AppSourceFacts{}, ErrClosed
	}
	return claim.facts, nil
}

// Facts returns a copy of the exact source facts while the tenant claim's
// retained source lease is live.
func (claim *TenantClaim) Facts() (TenantSourceFacts, error) {
	if claim == nil || claim.lease == nil || claim.lifecycle == nil {
		return TenantSourceFacts{}, ErrClosed
	}
	claim.lease.mu.Lock()
	defer claim.lease.mu.Unlock()
	if claim.lifecycle.state == claimAborted || !claim.lease.liveLocked() {
		return TenantSourceFacts{}, ErrClosed
	}
	return claim.facts, nil
}

// Facts returns a copy of the exact source facts while the system claim's
// retained source lease is live.
func (claim *SystemClaim) Facts() (SystemSourceFacts, error) {
	if claim == nil || claim.lease == nil || claim.lifecycle == nil {
		return SystemSourceFacts{}, ErrClosed
	}
	claim.lease.mu.Lock()
	defer claim.lease.mu.Unlock()
	if claim.lifecycle.state == claimAborted || !claim.lease.liveLocked() {
		return SystemSourceFacts{}, ErrClosed
	}
	return claim.facts, nil
}

// CompleteAppClaim seals the freshly derived App receipt into the exact source
// ledger. Only the fixed-purpose App verifier calls this internal API.
func CompleteAppClaim(
	claim *AppClaim,
	receipt layoutbundleownerverificationv1.Receipt,
) error {
	if claim == nil || claim.lease == nil || claim.lifecycle == nil {
		return ErrClosed
	}
	err := claim.lease.completeClaim(
		claim.lifecycle,
		appSourceIdentity(claim.facts),
		claim.facts.BundleKey,
		receipt,
	)
	if errors.Is(err, ErrAuthorityMismatch) {
		claim.Abort()
	}
	return err
}

// CompleteTenantClaim seals the freshly derived tenant receipt into the exact
// source ledger. Only the fixed-purpose tenant verifier calls this API.
func CompleteTenantClaim(
	claim *TenantClaim,
	receipt layoutbundleownerverificationv1.Receipt,
) error {
	if claim == nil || claim.lease == nil || claim.lifecycle == nil {
		return ErrClosed
	}
	err := claim.lease.completeClaim(
		claim.lifecycle,
		tenantSourceIdentity(claim.facts),
		claim.facts.BundleKey,
		receipt,
	)
	if errors.Is(err, ErrAuthorityMismatch) {
		claim.Abort()
	}
	return err
}

// CompleteSystemClaim seals the freshly derived system receipt into the exact
// source ledger after re-proving the session-scoped system fence.
func CompleteSystemClaim(
	claim *SystemClaim,
	receipt layoutbundleownerverificationv1.Receipt,
) error {
	if claim == nil || claim.lease == nil || claim.lifecycle == nil {
		return ErrClosed
	}
	if !claim.lease.claimReadyToComplete(claim.lifecycle) {
		return ErrClosed
	}
	if claim.token == nil || !claim.token.systemAuthorityFenceActive() {
		return ErrClosed
	}
	err := claim.lease.completeClaim(
		claim.lifecycle,
		systemSourceIdentity(claim.facts),
		claim.facts.BundleKey,
		receipt,
	)
	if errors.Is(err, ErrAuthorityMismatch) {
		claim.Abort()
	}
	return err
}

func (lease *Lease) claimReadyToComplete(lifecycle *claimLifecycle) bool {
	lease.mu.Lock()
	defer lease.mu.Unlock()
	return lifecycle.state == claimVerified &&
		lease.activeClaim == lifecycle &&
		lease.liveLocked()
}

func (lease *Lease) completeClaim(
	lifecycle *claimLifecycle,
	source sourceIdentity,
	key layoutbundlekeyv1.BundleKey,
	receipt layoutbundleownerverificationv1.Receipt,
) error {
	lease.mu.Lock()
	defer lease.mu.Unlock()
	if lifecycle.state != claimVerified ||
		lease.activeClaim != lifecycle ||
		!lease.liveLocked() ||
		lease.sealed ||
		lifecycle.index != len(lease.receipts) ||
		lifecycle.index >= len(lease.required.BundleKeys) ||
		lease.required.BundleKeys[lifecycle.index] != key {
		return ErrClosed
	}
	if err := validateClaimReceipt(source, key, receipt); err != nil {
		return err
	}
	digest, err := layoutbundleownerverificationv1.ReceiptDigest(receipt)
	if err != nil {
		return fmt.Errorf("%w: receipt digest: %v", ErrAuthorityMismatch, err)
	}
	lease.receipts = append(lease.receipts, layoutbundleownerverificationv1.ReceiptEntry{
		BundleKey:         receipt.BundleKey,
		EvidenceVersion:   receipt.EvidenceVersion,
		EvidenceDigest:    receipt.EvidenceDigest,
		VerificationState: receipt.VerificationState,
	})
	lease.receiptDigests = append(lease.receiptDigests, digest)
	lifecycle.state = claimCompleted
	lease.activeClaim = nil
	lease.lastKeyIndex = lifecycle.index
	return nil
}

func validateClaimReceipt(
	source sourceIdentity,
	key layoutbundlekeyv1.BundleKey,
	receipt layoutbundleownerverificationv1.Receipt,
) error {
	if err := layoutbundleownerverificationv1.ValidateReceipt(receipt); err != nil {
		return fmt.Errorf("%w: owner receipt: %v", ErrAuthorityMismatch, err)
	}
	if receipt.OrgID != source.orgID ||
		receipt.WriterCommandID != source.writerCommandID ||
		receipt.WriterReservationGeneration != source.writerReservationGeneration ||
		receipt.SourceRequestDigest != source.sourceRequestDigest ||
		receipt.RequiredBundleKeySetDigest != source.requiredBundleKeySetDigest ||
		receipt.BundleKey != key {
		return ErrAuthorityMismatch
	}
	return nil
}

// SealReceiptSet closes the complete owner-verification ledger over the exact
// source-bound required set. It neither commits the retained transaction nor
// authorizes publication adoption.
func (lease *Lease) SealReceiptSet() (
	layoutbundleownerverificationv1.ReceiptSet,
	string,
	error,
) {
	if lease == nil {
		return layoutbundleownerverificationv1.ReceiptSet{}, "", ErrClosed
	}
	lease.mu.Lock()
	defer lease.mu.Unlock()
	if !lease.liveLocked() {
		return layoutbundleownerverificationv1.ReceiptSet{}, "", ErrClosed
	}
	if lease.activeClaim != nil {
		return layoutbundleownerverificationv1.ReceiptSet{}, "", ErrClaimOrder
	}
	if lease.sealed {
		if lease.requiresSystemFence && !lease.token.systemAuthorityFenceActive() {
			return layoutbundleownerverificationv1.ReceiptSet{}, "", ErrClosed
		}
		return cloneReceiptSet(lease.receiptSet), lease.receiptSetHash, nil
	}
	if len(lease.receipts) != len(lease.required.BundleKeys) ||
		len(lease.receiptDigests) != len(lease.required.BundleKeys) ||
		lease.lastKeyIndex != len(lease.required.BundleKeys)-1 {
		return layoutbundleownerverificationv1.ReceiptSet{}, "", ErrClaimOrder
	}
	if lease.requiresSystemFence && !lease.token.systemAuthorityFenceActive() {
		return layoutbundleownerverificationv1.ReceiptSet{}, "", ErrClosed
	}
	set := layoutbundleownerverificationv1.ReceiptSet{
		ReceiptSetVersion:           layoutbundleownerverificationv1.ReceiptSetVersion,
		ReceiptVersion:              layoutbundleownerverificationv1.ReceiptVersion,
		OrgID:                       lease.source.orgID,
		WriterCommandID:             lease.source.writerCommandID,
		WriterReservationGeneration: lease.source.writerReservationGeneration,
		SourceRequestDigest:         lease.source.sourceRequestDigest,
		RequiredBundleKeySetDigest:  lease.source.requiredBundleKeySetDigest,
		Receipts:                    append([]layoutbundleownerverificationv1.ReceiptEntry{}, lease.receipts...),
	}
	canonical, err := layoutbundleownerverificationv1.CanonicalizeReceiptSet(set, lease.required)
	if err != nil {
		return layoutbundleownerverificationv1.ReceiptSet{}, "", fmt.Errorf(
			"%w: complete receipt set: %v",
			ErrAuthorityMismatch,
			err,
		)
	}
	digests, err := layoutbundleownerverificationv1.CanonicalReceiptDigests(canonical, lease.required)
	if err != nil || len(digests) != len(lease.receiptDigests) {
		return layoutbundleownerverificationv1.ReceiptSet{}, "", ErrAuthorityMismatch
	}
	for index := range digests {
		if digests[index] != lease.receiptDigests[index] {
			return layoutbundleownerverificationv1.ReceiptSet{}, "", ErrAuthorityMismatch
		}
	}
	hash, err := layoutbundleownerverificationv1.ReceiptSetDigest(canonical, lease.required)
	if err != nil {
		return layoutbundleownerverificationv1.ReceiptSet{}, "", fmt.Errorf(
			"%w: receipt-set digest: %v",
			ErrAuthorityMismatch,
			err,
		)
	}
	lease.receiptSet = cloneReceiptSet(canonical)
	lease.receiptSetHash = hash
	lease.sealed = true
	return cloneReceiptSet(canonical), hash, nil
}

// ValidateSealedReceiptSet proves that an already-sealed closure still owns
// its exact retained source transaction and returns defensive values.
func (lease *Lease) ValidateSealedReceiptSet() (
	layoutbundleownerverificationv1.ReceiptSet,
	string,
	error,
) {
	if lease == nil {
		return layoutbundleownerverificationv1.ReceiptSet{}, "", ErrClosed
	}
	lease.mu.Lock()
	defer lease.mu.Unlock()
	if !lease.liveLocked() || !lease.sealed || lease.activeClaim != nil {
		return layoutbundleownerverificationv1.ReceiptSet{}, "", ErrClosed
	}
	if lease.requiresSystemFence && !lease.token.systemAuthorityFenceActive() {
		return layoutbundleownerverificationv1.ReceiptSet{}, "", ErrClosed
	}
	if err := layoutbundleownerverificationv1.ValidateReceiptSet(
		lease.receiptSet,
		lease.required,
	); err != nil {
		return layoutbundleownerverificationv1.ReceiptSet{}, "", ErrAuthorityMismatch
	}
	hash, err := layoutbundleownerverificationv1.ReceiptSetDigest(
		lease.receiptSet,
		lease.required,
	)
	if err != nil || hash != lease.receiptSetHash {
		return layoutbundleownerverificationv1.ReceiptSet{}, "", ErrAuthorityMismatch
	}
	return cloneReceiptSet(lease.receiptSet), hash, nil
}

func cloneReceiptSet(
	set layoutbundleownerverificationv1.ReceiptSet,
) layoutbundleownerverificationv1.ReceiptSet {
	set.Receipts = append([]layoutbundleownerverificationv1.ReceiptEntry{}, set.Receipts...)
	return set
}

// Abort invalidates the complete retained source lease. The durable PREPARED
// source remains independently recoverable.
func (claim *AppClaim) Abort() {
	if claim == nil || claim.lease == nil || claim.lifecycle == nil {
		return
	}
	claim.lease.abortClaim(claim.lifecycle)
}

// Abort invalidates the complete retained source lease. The durable PREPARED
// source remains independently recoverable.
func (claim *TenantClaim) Abort() {
	if claim == nil || claim.lease == nil || claim.lifecycle == nil {
		return
	}
	claim.lease.abortClaim(claim.lifecycle)
}

// Abort invalidates the complete retained source lease. The durable PREPARED
// source remains independently recoverable.
func (claim *SystemClaim) Abort() {
	if claim == nil || claim.lease == nil || claim.lifecycle == nil {
		return
	}
	claim.lease.abortClaim(claim.lifecycle)
}

func (lease *Lease) abortClaim(lifecycle *claimLifecycle) {
	lease.mu.Lock()
	lease.closed = true
	if lifecycle.state != claimCompleted {
		lifecycle.state = claimAborted
	}
	if lease.activeClaim == lifecycle {
		lease.activeClaim = nil
	}
	token := lease.token
	lease.mu.Unlock()
	_ = token.Close()
}

// Active reports whether this completed claim and its owner read fence remain
// live.
func (claim *AppClaim) Active() bool {
	if claim == nil || claim.lease == nil || claim.lifecycle == nil {
		return false
	}
	return claim.lease.claimActive(claim.lifecycle)
}

// Active reports whether this completed tenant claim and its owner read fence
// remain live.
func (claim *TenantClaim) Active() bool {
	if claim == nil || claim.lease == nil || claim.lifecycle == nil {
		return false
	}
	return claim.lease.claimActive(claim.lifecycle)
}

// Active reports whether this completed system claim and its owner read fence
// remain live.
func (claim *SystemClaim) Active() bool {
	if claim == nil || claim.lease == nil || claim.lifecycle == nil {
		return false
	}
	return claim.lease.claimActive(claim.lifecycle) &&
		claim.token != nil &&
		claim.token.systemAuthorityFenceActive()
}

func (lease *Lease) claimActive(lifecycle *claimLifecycle) bool {
	lease.mu.Lock()
	defer lease.mu.Unlock()
	return lifecycle.state == claimCompleted && lease.liveLocked()
}

// Active reports whether the retained source transaction is live.
func (lease *Lease) Active() bool {
	if lease == nil {
		return false
	}
	lease.mu.Lock()
	defer lease.mu.Unlock()
	return lease.liveLocked()
}

// Close invalidates the lease and rolls its retained transaction back.
func (lease *Lease) Close() error {
	if lease == nil {
		return nil
	}
	lease.mu.Lock()
	if lease.closed {
		lease.mu.Unlock()
		return nil
	}
	lease.closed = true
	if lease.activeClaim != nil {
		lease.activeClaim.state = claimAborted
		lease.activeClaim = nil
	}
	token := lease.token
	lease.mu.Unlock()
	return token.Close()
}

// Commit invalidates the lease and commits its retained transaction. Only the
// source-authority compatibility path receives the Lease itself.
func (lease *Lease) Commit() error {
	if lease == nil {
		return ErrClosed
	}
	lease.mu.Lock()
	if lease.closed || lease.activeClaim != nil || !lease.liveLocked() {
		lease.mu.Unlock()
		return ErrClosed
	}
	lease.closed = true
	token := lease.token
	lease.mu.Unlock()
	return token.Commit()
}

func (lease *Lease) liveLocked() bool {
	return !lease.closed &&
		lease.token != nil &&
		lease.token.sourceBindingActive()
}

func (claim *AppClaim) acquireInput() (*Token, AppOwnerRequest, error) {
	if claim == nil || claim.lease == nil || claim.lifecycle == nil {
		return nil, AppOwnerRequest{}, ErrClosed
	}
	if err := claim.lease.acquireClaim(claim.lifecycle); err != nil {
		return nil, AppOwnerRequest{}, ErrClosed
	}
	return claim.token, claim.request, nil
}

func (claim *TenantClaim) acquireInput() (*Token, TenantOwnerRequest, error) {
	if claim == nil || claim.lease == nil || claim.lifecycle == nil {
		return nil, TenantOwnerRequest{}, ErrClosed
	}
	if err := claim.lease.acquireClaim(claim.lifecycle); err != nil {
		return nil, TenantOwnerRequest{}, ErrClosed
	}
	return claim.token, claim.request, nil
}

func (claim *SystemClaim) acquireInput() (*Token, SystemOwnerRequest, error) {
	if claim == nil || claim.lease == nil || claim.lifecycle == nil {
		return nil, SystemOwnerRequest{}, ErrClosed
	}
	if err := claim.lease.acquireClaim(claim.lifecycle); err != nil {
		return nil, SystemOwnerRequest{}, ErrClosed
	}
	return claim.token, claim.request, nil
}

func (lease *Lease) acquireClaim(lifecycle *claimLifecycle) error {
	lease.mu.Lock()
	defer lease.mu.Unlock()
	if lifecycle.state != claimPending ||
		lease.activeClaim != lifecycle ||
		!lease.liveLocked() {
		return ErrClosed
	}
	lifecycle.state = claimVerifying
	return nil
}

func (lease *Lease) markClaimVerified(lifecycle *claimLifecycle) error {
	lease.mu.Lock()
	defer lease.mu.Unlock()
	if lifecycle.state != claimVerifying ||
		lease.activeClaim != lifecycle ||
		!lease.liveLocked() {
		return ErrClosed
	}
	lifecycle.state = claimVerified
	return nil
}

// MarshalJSON rejects persistence of an owner claim.
func (*AppClaim) MarshalJSON() ([]byte, error) {
	return nil, ErrCapabilityHasNoJSON
}

// UnmarshalJSON rejects caller construction of an owner claim.
func (*AppClaim) UnmarshalJSON([]byte) error {
	return ErrCapabilityHasNoJSON
}

// MarshalJSON rejects persistence of a tenant owner claim.
func (*TenantClaim) MarshalJSON() ([]byte, error) {
	return nil, ErrCapabilityHasNoJSON
}

// UnmarshalJSON rejects caller construction of a tenant owner claim.
func (*TenantClaim) UnmarshalJSON([]byte) error {
	return ErrCapabilityHasNoJSON
}

// MarshalJSON rejects persistence of a system owner claim.
func (*SystemClaim) MarshalJSON() ([]byte, error) {
	return nil, ErrCapabilityHasNoJSON
}

// UnmarshalJSON rejects caller construction of a system owner claim.
func (*SystemClaim) UnmarshalJSON([]byte) error {
	return ErrCapabilityHasNoJSON
}
