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-controlplane/releaseartifact"
)

// verifiedReleaseRegistration is an internal handoff from signature
// verification to durable release registration. It is intentionally not an
// exported value: callers must not be able to assert that arbitrary package
// facts or an attestation have been verified.
type verifiedReleaseRegistration struct {
	artifact             releaseartifact.VerifiedArtifact
	canonicalAttestation []byte
	verifiedAt           time.Time
	signingKey           releaseSigningKey
}

// releaseSigningKey records the exact durable publisher-key fact used to
// verify a release artifact. The registration transaction locks and compares
// the same fact before it writes a release, closing the verification/commit
// race without treating a key ID alone as proof.
type releaseSigningKey struct {
	algorithm  string
	publicKey  []byte
	validFrom  time.Time
	validUntil time.Time
}

// VerifyReleaseCandidate verifies operator-supplied release bytes against the
// operator-supplied publisher trust without reading or mutating Catalog state.
// Reconcile uses this preflight before it is allowed to register a publisher or
// reserve a globally authoritative AppID. ImportReleaseArtifact repeats the
// same verification against the durable Catalog trust before registration.
func (a *Authority) VerifyReleaseCandidate(ctx context.Context, packageRaw, attestationRaw []byte, trust PublisherTrust, expectedAppID string, host releaseartifact.HostCompatibility) (VerifiedReleaseCandidate, error) {
	if err := ctx.Err(); err != nil {
		return VerifiedReleaseCandidate{}, err
	}
	trust, err := canonicalPublisherTrust(trust)
	if err != nil {
		return VerifiedReleaseCandidate{}, err
	}
	if !trust.Active {
		return VerifiedReleaseCandidate{}, errors.New("release candidate requires active publisher trust")
	}
	attestation, issuedAt, verifiedAt, err := a.verificationTimes(attestationRaw)
	if err != nil {
		return VerifiedReleaseCandidate{}, err
	}
	if attestation.PublisherID != trust.PublisherID {
		return VerifiedReleaseCandidate{}, errors.New("release candidate publisher does not match operator-authorized publisher")
	}
	var publicKey ed25519.PublicKey
	for _, key := range trust.Keys {
		if key.KeyID != attestation.KeyID {
			continue
		}
		if key.Algorithm != releaseartifact.SignatureAlgorithm || len(key.PublicKey) != ed25519.PublicKeySize {
			return VerifiedReleaseCandidate{}, fmt.Errorf("publisher key %q is not a supported Ed25519 verification key", attestation.KeyID)
		}
		if !attestationWithin(issuedAt, key) || !attestationWithin(verifiedAt, key) {
			return VerifiedReleaseCandidate{}, fmt.Errorf("publisher key %q is not valid at attestation and Catalog verification times", attestation.KeyID)
		}
		publicKey = ed25519.PublicKey(key.PublicKey)
		break
	}
	if len(publicKey) == 0 {
		return VerifiedReleaseCandidate{}, fmt.Errorf("publisher key %q is not present in operator-authorized trust", attestation.KeyID)
	}
	verified, err := verifyReleaseArtifact(packageRaw, attestationRaw, publicKey, host)
	if err != nil {
		return VerifiedReleaseCandidate{}, err
	}
	if err := ctx.Err(); err != nil {
		return VerifiedReleaseCandidate{}, err
	}
	release := verifiedReleaseCandidate(verified.Package)
	if release.AppID != expectedAppID {
		return VerifiedReleaseCandidate{}, fmt.Errorf("verified AppRelease %q does not match operator-authorized App %q", release.AppID, expectedAppID)
	}
	return release, nil
}

func verifiedReleaseCandidate(verified appauthority.VerifiedPackage) VerifiedReleaseCandidate {
	return VerifiedReleaseCandidate{
		AppID: verified.Manifest.AppID, PublisherID: verified.Manifest.PublisherID,
		SemanticVersion: verified.Manifest.SemanticVersion, AppReleaseDigest: verified.AppReleaseDigest,
		MetadataBundleDigest: verified.Manifest.MetadataBundleDigest, Package: verified,
	}
}

// ImportReleaseArtifact verifies the immutable delivery bytes against the
// operator-registered publisher key and then registers that exact release. It
// does not register a publisher, reserve an AppID, or create an AppInstallation.
func (a *Authority) ImportReleaseArtifact(ctx context.Context, packageRaw, attestationRaw []byte, host releaseartifact.HostCompatibility) (RegisteredRelease, error) {
	attestation, issuedAt, verifiedAt, err := a.verificationTimes(attestationRaw)
	if err != nil {
		return RegisteredRelease{}, err
	}
	signingKey, err := a.readReleaseSigningKey(ctx, attestation.PublisherID, attestation.KeyID, issuedAt, verifiedAt)
	if err != nil {
		return RegisteredRelease{}, err
	}
	verified, err := verifyReleaseArtifact(packageRaw, attestationRaw, ed25519.PublicKey(signingKey.publicKey), host)
	if err != nil {
		return RegisteredRelease{}, err
	}
	return a.registerVerifiedRelease(ctx, verifiedReleaseRegistration{
		artifact:             verified,
		canonicalAttestation: append([]byte(nil), attestationRaw...),
		verifiedAt:           verifiedAt,
		signingKey:           signingKey,
	})
}

func (a *Authority) readReleaseSigningKey(ctx context.Context, publisherID, keyID string, issuedAt, verifiedAt time.Time) (releaseSigningKey, error) {
	var publisherState, keyState, algorithm string
	var publicKey []byte
	var validFrom, validUntil time.Time
	err := a.db.QueryRowContext(ctx, `SELECT p.State, k.State, k.Algorithm, k.PublicKey, k.ValidFrom, k.ValidUntil
FROM _System_CatalogPublisher p
JOIN _System_CatalogPublisherKey k ON k.PublisherId = p.PublisherId
	WHERE p.PublisherId = ? AND k.KeyId = ?`, publisherID, keyID).Scan(
		&publisherState, &keyState, &algorithm, &publicKey, &validFrom, &validUntil,
	)
	if err != nil {
		if errors.Is(err, sql.ErrNoRows) {
			return releaseSigningKey{}, ErrConflict
		}
		return releaseSigningKey{}, err
	}
	if publisherState != "ACTIVE" || keyState != "ACTIVE" {
		return releaseSigningKey{}, ErrConflict
	}
	signingKey, err := canonicalReleaseSigningKey(algorithm, publicKey, validFrom, validUntil, issuedAt, verifiedAt)
	if err != nil {
		return releaseSigningKey{}, fmt.Errorf("publisher key %q: %w", keyID, err)
	}
	return signingKey, nil
}

func canonicalReleaseSigningKey(algorithm string, publicKey []byte, validFrom, validUntil, issuedAt, verifiedAt time.Time) (releaseSigningKey, error) {
	if algorithm != releaseartifact.SignatureAlgorithm || len(publicKey) != ed25519.PublicKeySize {
		return releaseSigningKey{}, errors.New("is not a supported Ed25519 verification key")
	}
	validFrom = validFrom.UTC().Truncate(time.Microsecond)
	validUntil = validUntil.UTC().Truncate(time.Microsecond)
	issuedAt = issuedAt.UTC().Truncate(time.Microsecond)
	verifiedAt = verifiedAt.UTC().Truncate(time.Microsecond)
	if validFrom.IsZero() || validUntil.IsZero() || !validUntil.After(validFrom) || issuedAt.IsZero() || verifiedAt.IsZero() {
		return releaseSigningKey{}, ErrConflict
	}
	key := PublisherKey{Algorithm: algorithm, PublicKey: append([]byte(nil), publicKey...), ValidFrom: validFrom, ValidUntil: validUntil}
	if !attestationWithin(issuedAt, key) || !attestationWithin(verifiedAt, key) {
		return releaseSigningKey{}, ErrConflict
	}
	return releaseSigningKey{algorithm: algorithm, publicKey: append([]byte(nil), publicKey...), validFrom: validFrom, validUntil: validUntil}, nil
}

func (key releaseSigningKey) equal(other releaseSigningKey) bool {
	return key.algorithm == other.algorithm && bytes.Equal(key.publicKey, other.publicKey) &&
		key.validFrom.Equal(other.validFrom) && key.validUntil.Equal(other.validUntil)
}

func (a *Authority) lockReleaseSigningKey(ctx context.Context, tx *sql.Tx, registration verifiedReleaseRegistration) error {
	attestation := registration.artifact.Attestation
	var publisherState, keyState, algorithm string
	var publicKey []byte
	var validFrom, validUntil time.Time
	err := tx.QueryRowContext(ctx, `SELECT p.State, k.State, k.Algorithm, k.PublicKey, k.ValidFrom, k.ValidUntil
FROM _System_CatalogPublisher p
JOIN _System_CatalogPublisherKey k ON k.PublisherId = p.PublisherId
WHERE p.PublisherId = ? AND k.KeyId = ? FOR UPDATE`, attestation.PublisherID, attestation.KeyID).Scan(
		&publisherState, &keyState, &algorithm, &publicKey, &validFrom, &validUntil,
	)
	if errors.Is(err, sql.ErrNoRows) {
		return ErrConflict
	}
	if err != nil {
		return err
	}
	if publisherState != "ACTIVE" || keyState != "ACTIVE" {
		return ErrConflict
	}
	current, err := canonicalReleaseSigningKey(algorithm, publicKey, validFrom, validUntil, attestation.IssuedAt, registration.verifiedAt)
	if err != nil {
		return err
	}
	if !current.equal(registration.signingKey) {
		return ErrConflict
	}
	return nil
}

func (a *Authority) verificationTimes(attestationRaw []byte) (releaseartifact.PublisherAttestation, time.Time, time.Time, error) {
	attestation, err := releaseartifact.ParseAttestation(attestationRaw)
	if err != nil {
		return releaseartifact.PublisherAttestation{}, time.Time{}, time.Time{}, err
	}
	issuedAt, err := time.Parse(time.RFC3339Nano, attestation.IssuedAt)
	if err != nil {
		return releaseartifact.PublisherAttestation{}, time.Time{}, time.Time{}, err
	}
	verifiedAt := a.now().UTC().Truncate(time.Microsecond)
	if verifiedAt.IsZero() || issuedAt.After(verifiedAt) {
		return releaseartifact.PublisherAttestation{}, time.Time{}, time.Time{}, errors.New("publisher attestation issued_at must not be later than Catalog verification time")
	}
	return attestation, issuedAt, verifiedAt, nil
}

func verifyReleaseArtifact(packageRaw, attestationRaw []byte, publicKey ed25519.PublicKey, host releaseartifact.HostCompatibility) (releaseartifact.VerifiedArtifact, error) {
	verified, err := releaseartifact.Verify(packageRaw, attestationRaw, publicKey)
	if err != nil {
		return releaseartifact.VerifiedArtifact{}, err
	}
	componentSet, err := verifyReleaseComponentSet(verified.Package.Package)
	if err != nil {
		return releaseartifact.VerifiedArtifact{}, err
	}
	registeredPackage := componentSet.packageFacts
	if registeredPackage.AppReleaseDigest != verified.Package.AppReleaseDigest ||
		string(registeredPackage.CanonicalManifest) != string(verified.Package.CanonicalManifest) ||
		string(registeredPackage.CanonicalBundle) != string(verified.Package.CanonicalBundle) {
		return releaseartifact.VerifiedArtifact{}, fmt.Errorf("registered component verification changed the signed package carrier")
	}
	verified.Package = registeredPackage
	if err := releaseartifact.VerifyCompatibility(verified.Package, host); err != nil {
		return releaseartifact.VerifiedArtifact{}, err
	}
	return verified, nil
}
