package catalogapp

import (
	"bytes"
	"context"
	"crypto/ed25519"
	"database/sql"
	"errors"
	"fmt"
	"time"

	"github.com/pingcap-inc/nexuscrm-contract/pkg/appauthority"
	"github.com/pingcap-inc/nexuscrm-contract/pkg/appidentitykeydeclarationv1"
	"github.com/pingcap-inc/nexuscrm-controlplane/releaseartifact"
)

// releaseArtifactReceipt is the immutable signed-artifact evidence retained
// with a release that entered through ImportReleaseArtifact. It deliberately
// stores only the detached attestation and the exact verification-key snapshot:
// the AppRelease tables remain the sole source of the canonical package bytes.
//
// A receipt is not an App namespace claim, a ProfileKey/RoleKey admission, or
// a local-ID mapping. Releases registered before receipt retention remain
// readable through the historical AppRelease path, but an importer cannot
// silently backfill a receipt for them on replay.
type releaseArtifactReceipt struct {
	canonicalAttestation []byte
	signingKey           releaseSigningKey
}

// sealedReleaseEvidence is the immutable AppRelease summary that a retained
// receipt must bind. It is deliberately private: a validated receipt proves a
// signed App release artifact, not namespace ownership or a ProfileKey/RoleKey
// mapping.
type sealedReleaseEvidence struct {
	publisherID             string
	semanticVersion         string
	canonicalizationProfile string
	artifactMediaType       string
	attestationKeyID        string
	attestationDigest       string
	attestationVerifiedAt   time.Time
	receipt                 releaseArtifactReceipt
}

// errReleaseArtifactReceiptAbsent marks a pre-receipt AppRelease. The caller
// may preserve an exact immutable replay, but must never create a receipt for
// that historical row outside the stopped migration that can establish its
// provenance.
var errReleaseArtifactReceiptAbsent = errors.New("App release artifact receipt is absent")

func newReleaseArtifactReceipt(registration verifiedReleaseRegistration) (releaseArtifactReceipt, error) {
	if len(registration.canonicalAttestation) == 0 {
		return releaseArtifactReceipt{}, errors.New("verified release registration has no canonical attestation")
	}
	if len(registration.signingKey.publicKey) != ed25519.PublicKeySize {
		return releaseArtifactReceipt{}, errors.New("verified release registration has no Ed25519 signing key")
	}

	// Rebuild the package envelope from the verified package and reverify the
	// exact retained attestation. The private registration carrier then proves
	// that the bytes persisted below are the bytes whose signature was accepted,
	// rather than a separate caller-supplied receipt.
	_, packageRaw, err := releaseartifact.BuildPackage(registration.artifact.Package.Package)
	if err != nil {
		return releaseArtifactReceipt{}, err
	}
	verified, err := releaseartifact.Verify(packageRaw, registration.canonicalAttestation, ed25519.PublicKey(registration.signingKey.publicKey))
	if err != nil {
		return releaseArtifactReceipt{}, err
	}
	if verified.Package.AppReleaseDigest != registration.artifact.Package.AppReleaseDigest ||
		verified.Attestation.PublisherID != registration.artifact.Attestation.PublisherID ||
		verified.Attestation.KeyID != registration.artifact.Attestation.KeyID ||
		verified.Attestation.AppID != registration.artifact.Attestation.AppID ||
		verified.Attestation.SemanticVersion != registration.artifact.Attestation.SemanticVersion ||
		verified.Attestation.AppReleaseDigest != registration.artifact.Attestation.AppReleaseDigest ||
		verified.Attestation.CanonicalizationProfile != registration.artifact.Attestation.CanonicalizationProfile ||
		verified.Attestation.ArtifactMediaType != registration.artifact.Attestation.ArtifactMediaType ||
		verified.Attestation.AttestationDigest != registration.artifact.Attestation.AttestationDigest ||
		!verified.Attestation.IssuedAt.Equal(registration.artifact.Attestation.IssuedAt) {
		return releaseArtifactReceipt{}, errors.New("retained attestation does not match verified release registration")
	}
	return releaseArtifactReceipt{
		canonicalAttestation: append([]byte(nil), registration.canonicalAttestation...),
		signingKey:           cloneReleaseSigningKey(registration.signingKey),
	}, nil
}

func cloneReleaseSigningKey(key releaseSigningKey) releaseSigningKey {
	return releaseSigningKey{
		algorithm:  key.algorithm,
		publicKey:  append([]byte(nil), key.publicKey...),
		validFrom:  key.validFrom,
		validUntil: key.validUntil,
	}
}

func insertReleaseArtifactReceiptTx(ctx context.Context, tx *sql.Tx, appID, appReleaseDigest string, receipt releaseArtifactReceipt) error {
	if _, err := tx.ExecContext(ctx, `INSERT INTO _System_CatalogAppReleaseArtifactReceipt
(AppId, AppReleaseDigest, CanonicalAttestation, VerificationKeyAlgorithm, VerificationPublicKey,
VerificationKeyValidFrom, VerificationKeyValidUntil, CreatedDate)
VALUES (?, ?, ?, ?, ?, ?, ?, CURRENT_TIMESTAMP(6))`,
		appID, appReleaseDigest, receipt.canonicalAttestation, receipt.signingKey.algorithm,
		receipt.signingKey.publicKey, receipt.signingKey.validFrom, receipt.signingKey.validUntil); err != nil {
		if isDuplicate(err) {
			return ErrConflict
		}
		return err
	}
	return nil
}

func verifyStoredReleaseArtifactReceiptTx(ctx context.Context, tx *sql.Tx, appID, appReleaseDigest string, expected releaseArtifactReceipt) error {
	var canonicalAttestation []byte
	var algorithm string
	var publicKey []byte
	var validFrom, validUntil sql.NullTime
	err := tx.QueryRowContext(ctx, `SELECT CanonicalAttestation, VerificationKeyAlgorithm, VerificationPublicKey,
VerificationKeyValidFrom, VerificationKeyValidUntil
FROM _System_CatalogAppReleaseArtifactReceipt
WHERE AppId = ? AND AppReleaseDigest = ? FOR UPDATE`, appID, appReleaseDigest).Scan(
		&canonicalAttestation, &algorithm, &publicKey, &validFrom, &validUntil,
	)
	if errors.Is(err, sql.ErrNoRows) {
		return errReleaseArtifactReceiptAbsent
	}
	if err != nil {
		return err
	}
	if !validFrom.Valid || !validUntil.Valid {
		return fmt.Errorf("%w: stored App release artifact receipt has NULL key window", ErrCorrupt)
	}
	actual := releaseArtifactReceipt{
		canonicalAttestation: canonicalAttestation,
		signingKey: releaseSigningKey{
			algorithm:  algorithm,
			publicKey:  publicKey,
			validFrom:  validFrom.Time,
			validUntil: validUntil.Time,
		},
	}
	if !bytes.Equal(actual.canonicalAttestation, expected.canonicalAttestation) || !actual.signingKey.equal(expected.signingKey) {
		return ErrConflict
	}
	return nil
}

// ReadSealedRelease returns a registered release only when Catalog can
// revalidate the immutable signed artifact receipt retained at import. It
// deliberately rejects pre-receipt AppRelease rows with ErrUnproven; ordinary
// ReadRelease remains the historical structural reader for those rows.
//
// This validates source artifact evidence only. It does not authorize a
// namespace claim, ProfileKey/RoleKey admission, a local-ID mapping, or any
// runtime selection.
func (a *Authority) ReadSealedRelease(ctx context.Context, appID, appReleaseDigest string) (RegisteredRelease, error) {
	release, err := a.ReadRelease(ctx, appID, appReleaseDigest)
	if err != nil {
		return RegisteredRelease{}, err
	}
	evidence, err := a.readSealedReleaseEvidence(ctx, appID, appReleaseDigest)
	if err != nil {
		return RegisteredRelease{}, err
	}
	if err := verifySealedReleaseEvidence(appID, release, evidence); err != nil {
		return RegisteredRelease{}, fmt.Errorf("%w: sealed App release evidence: %v", ErrCorrupt, err)
	}
	return release, nil
}

// ReadSealedIdentityKeyDeclarations returns the positive ProfileKey and
// RoleKey declarations carried by one sealed App release. It revalidates the
// release receipt before extracting the declarations and rejects legacy
// releases with ErrUnproven.
//
// The result is source content only. It is not a namespace-owner claim,
// issuer capability, organization admission, local-ID mapping, identity
// ledger entry, or routing-security binding.
func (a *Authority) ReadSealedIdentityKeyDeclarations(
	ctx context.Context,
	appID, appReleaseDigest string,
) (appidentitykeydeclarationv1.Declarations, error) {
	release, err := a.ReadSealedRelease(ctx, appID, appReleaseDigest)
	if err != nil {
		return appidentitykeydeclarationv1.Declarations{}, err
	}
	return sealedIdentityKeyDeclarations(appID, appReleaseDigest, release)
}

func sealedIdentityKeyDeclarations(
	appID, appReleaseDigest string,
	release RegisteredRelease,
) (appidentitykeydeclarationv1.Declarations, error) {
	componentSet, err := verifyReleaseComponentSet(release.Package.Package)
	if err != nil {
		return appidentitykeydeclarationv1.Declarations{}, fmt.Errorf(
			"%w: sealed App release identity-key declarations: %v",
			ErrCorrupt,
			err,
		)
	}
	if release.AppID != appID || release.AppReleaseDigest != appReleaseDigest ||
		componentSet.packageFacts.AppReleaseDigest != appReleaseDigest ||
		componentSet.packageFacts.Manifest.AppID != appID {
		return appidentitykeydeclarationv1.Declarations{}, fmt.Errorf(
			"%w: sealed App release identity-key declarations disagree with the release tuple",
			ErrCorrupt,
		)
	}
	return cloneIdentityKeyDeclarations(componentSet.identityKeyFacts), nil
}

func (a *Authority) readSealedReleaseEvidence(ctx context.Context, appID, appReleaseDigest string) (sealedReleaseEvidence, error) {
	var evidence sealedReleaseEvidence
	var rawAdmissionKind string
	var attestationKeyID, attestationDigest sql.NullString
	var attestationVerifiedAt sql.NullTime
	err := a.db.QueryRowContext(ctx, `SELECT PublisherId, SemanticVersion,
CanonicalizationProfile, ArtifactMediaType, AdmissionKind, AttestationKeyId, AttestationDigest, AttestationVerifiedAt
FROM _System_CatalogAppRelease
WHERE AppId = ? AND AppReleaseDigest = ?`, appID, appReleaseDigest).Scan(
		&evidence.publisherID, &evidence.semanticVersion,
		&evidence.canonicalizationProfile, &evidence.artifactMediaType,
		&rawAdmissionKind, &attestationKeyID, &attestationDigest, &attestationVerifiedAt,
	)
	if errors.Is(err, sql.ErrNoRows) {
		return sealedReleaseEvidence{}, ErrNotFound
	}
	if err != nil {
		return sealedReleaseEvidence{}, err
	}
	if ReleaseAdmissionKind(rawAdmissionKind) != ReleaseAdmissionSignedPublisher {
		return sealedReleaseEvidence{}, ErrUnproven
	}
	retained := retainedReleaseAdmission{
		appID: appID, appReleaseDigest: appReleaseDigest,
		kind:             ReleaseAdmissionSignedPublisher,
		attestationKeyID: attestationKeyID, attestationDigest: attestationDigest,
		attestationVerifiedAt: attestationVerifiedAt,
	}
	if err := validateRetainedReleaseAdmission(retained); err != nil {
		return sealedReleaseEvidence{}, err
	}
	evidence.attestationKeyID = attestationKeyID.String
	evidence.attestationDigest = attestationDigest.String
	evidence.attestationVerifiedAt = attestationVerifiedAt.Time

	var validFrom, validUntil sql.NullTime
	err = a.db.QueryRowContext(ctx, `SELECT CanonicalAttestation, VerificationKeyAlgorithm, VerificationPublicKey,
VerificationKeyValidFrom, VerificationKeyValidUntil
FROM _System_CatalogAppReleaseArtifactReceipt
WHERE AppId = ? AND AppReleaseDigest = ?`, appID, appReleaseDigest).Scan(
		&evidence.receipt.canonicalAttestation, &evidence.receipt.signingKey.algorithm,
		&evidence.receipt.signingKey.publicKey, &validFrom, &validUntil,
	)
	if errors.Is(err, sql.ErrNoRows) {
		return sealedReleaseEvidence{}, ErrUnproven
	}
	if err != nil {
		return sealedReleaseEvidence{}, err
	}
	if !validFrom.Valid || !validUntil.Valid {
		return sealedReleaseEvidence{}, fmt.Errorf("%w: stored App release artifact receipt has NULL key window", ErrCorrupt)
	}
	evidence.receipt.signingKey.validFrom = validFrom.Time
	evidence.receipt.signingKey.validUntil = validUntil.Time
	return evidence, nil
}

func verifySealedReleaseEvidence(appID string, release RegisteredRelease, evidence sealedReleaseEvidence) error {
	if appID == "" || release.AppID == "" || release.AppID != appID || release.AppReleaseDigest == "" ||
		evidence.publisherID != release.PublisherID ||
		evidence.semanticVersion != release.SemanticVersion ||
		evidence.attestationVerifiedAt.IsZero() {
		return errors.New("stored release summary disagrees with the canonical release")
	}
	if len(evidence.receipt.canonicalAttestation) == 0 {
		return errors.New("stored App release artifact receipt has no canonical attestation")
	}

	packageRaw, err := packageBytesForVerifiedRelease(release.Package)
	if err != nil {
		return err
	}
	attestation, err := releaseartifact.ParseAttestation(evidence.receipt.canonicalAttestation)
	if err != nil {
		return err
	}
	issuedAt, err := time.Parse(time.RFC3339Nano, attestation.IssuedAt)
	if err != nil {
		return err
	}
	verifiedAt := evidence.attestationVerifiedAt.UTC().Truncate(time.Microsecond)
	if issuedAt.After(verifiedAt) {
		return errors.New("publisher attestation was issued after its stored Catalog verification time")
	}
	signingKey, err := canonicalReleaseSigningKey(
		evidence.receipt.signingKey.algorithm,
		evidence.receipt.signingKey.publicKey,
		evidence.receipt.signingKey.validFrom,
		evidence.receipt.signingKey.validUntil,
		issuedAt,
		verifiedAt,
	)
	if err != nil {
		return err
	}
	if !signingKey.equal(evidence.receipt.signingKey) {
		return errors.New("stored App release artifact receipt signing key is not canonical")
	}
	verified, err := releaseartifact.Verify(packageRaw, evidence.receipt.canonicalAttestation, ed25519.PublicKey(signingKey.publicKey))
	if err != nil {
		return err
	}
	if verified.Package.AppReleaseDigest != release.AppReleaseDigest ||
		verified.Attestation.PublisherID != evidence.publisherID ||
		verified.Attestation.KeyID != evidence.attestationKeyID ||
		verified.Attestation.AppID != release.AppID ||
		verified.Attestation.SemanticVersion != evidence.semanticVersion ||
		verified.Attestation.AppReleaseDigest != release.AppReleaseDigest ||
		verified.Attestation.CanonicalizationProfile != evidence.canonicalizationProfile ||
		verified.Attestation.ArtifactMediaType != evidence.artifactMediaType ||
		verified.Attestation.AttestationDigest != evidence.attestationDigest {
		return errors.New("stored App release receipt does not bind the stored release summary")
	}
	return nil
}

func packageBytesForVerifiedRelease(release appauthority.VerifiedPackage) ([]byte, error) {
	_, packageRaw, err := releaseartifact.BuildPackage(release.Package)
	return packageRaw, err
}
