// Package releaseartifact defines the immutable delivery envelope for an
// appauthority AppPackage. It does not own App identity, release identity,
// installation state, publisher trust, or serving selection.
package releaseartifact

import (
	"bytes"
	"crypto/ed25519"
	"crypto/sha256"
	"encoding/hex"
	"encoding/json"
	"errors"
	"fmt"
	"regexp"
	"slices"
	"strings"
	"time"

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

const (
	PackageSchemaVersion     = "nexuscrm.app-package-artifact/v1"
	AttestationSchemaVersion = "nexuscrm.publisher-attestation/v1"
	ProvenanceSchemaVersion  = "nexuscrm.app-package-provenance/v1"
	CanonicalizationProfile  = "rfc8785"
	ArtifactMediaType        = "application/vnd.nexuscrm.app-package.v1+json"
	SignatureAlgorithm       = "ed25519"

	attestationDomain       = "nexuscrm.publisher-attestation.v1\x00"
	attestationDigestDomain = "nexuscrm.publisher-attestation-object.v1\x00"
)

var lowercaseSHA256 = regexp.MustCompile(`^sha256:[0-9a-f]{64}$`)

// PackageArtifact is a transport envelope. AppReleaseDigest remains the root
// identity defined by appauthority; this envelope does not define a second
// package or release identity.
type PackageArtifact struct {
	SchemaVersion     string          `json:"schema_version"`
	AppReleaseDigest  string          `json:"app_release_digest"`
	CanonicalManifest json.RawMessage `json:"canonical_manifest"`
	CanonicalBundle   json.RawMessage `json:"canonical_metadata_bundle"`
	ComponentBlobs    []ComponentBlob `json:"component_blobs"`
}

type ComponentBlob struct {
	ComponentID       string `json:"component_id"`
	CanonicalBehavior []byte `json:"canonical_behavior"`
}

type Provenance struct {
	SchemaVersion         string `json:"schema_version"`
	SourceRepository      string `json:"source_repository"`
	SourceRevision        string `json:"source_revision"`
	SourcePath            string `json:"source_path"`
	SourceTreeDigest      string `json:"source_tree_digest"`
	BuilderID             string `json:"builder_id"`
	ContractModuleVersion string `json:"contract_module_version"`
}

// PublisherAttestation is detached from the package. Its signature binds the
// immutable AppRelease tuple and provenance without becoming release identity.
type PublisherAttestation struct {
	SchemaVersion           string     `json:"schema_version"`
	PublisherID             string     `json:"publisher_id"`
	KeyID                   string     `json:"key_id"`
	AppID                   string     `json:"app_id"`
	SemanticVersion         string     `json:"semantic_version"`
	AppReleaseDigest        string     `json:"app_release_digest"`
	CanonicalizationProfile string     `json:"canonicalization_profile"`
	ArtifactMediaType       string     `json:"artifact_media_type"`
	IssuedAt                string     `json:"issued_at"`
	Provenance              Provenance `json:"provenance"`
	SignatureAlgorithm      string     `json:"signature_algorithm"`
	Signature               []byte     `json:"signature"`
}

type VerifiedAttestation struct {
	PublisherID             string
	KeyID                   string
	AppID                   string
	SemanticVersion         string
	AppReleaseDigest        string
	CanonicalizationProfile string
	ArtifactMediaType       string
	AttestationDigest       string
	IssuedAt                time.Time
}

type VerifiedArtifact struct {
	Package     appauthority.VerifiedPackage
	Attestation VerifiedAttestation
}

func BuildPackage(pkg appauthority.Package) (PackageArtifact, []byte, error) {
	verified, err := appauthority.VerifyPackage(pkg)
	if err != nil {
		return PackageArtifact{}, nil, err
	}
	blobs := make([]ComponentBlob, len(verified.ComponentBlobs))
	for i, blob := range verified.ComponentBlobs {
		blobs[i] = ComponentBlob{ComponentID: blob.ComponentID, CanonicalBehavior: slices.Clone(blob.CanonicalBehavior)}
	}
	artifact := PackageArtifact{
		SchemaVersion: PackageSchemaVersion, AppReleaseDigest: verified.AppReleaseDigest,
		CanonicalManifest: slices.Clone(verified.CanonicalManifest), CanonicalBundle: slices.Clone(verified.CanonicalBundle),
		ComponentBlobs: blobs,
	}
	raw, err := MarshalPackage(artifact)
	return artifact, raw, err
}

func MarshalPackage(artifact PackageArtifact) ([]byte, error) {
	verified, err := verifyPackageCarrier(artifact)
	if err != nil {
		return nil, err
	}
	artifact.CanonicalManifest = slices.Clone(verified.CanonicalManifest)
	artifact.CanonicalBundle = slices.Clone(verified.CanonicalBundle)
	return json.Marshal(artifact)
}

func ParsePackage(raw []byte) (PackageArtifact, appauthority.VerifiedPackage, error) {
	var artifact PackageArtifact
	if len(raw) == 0 || !json.Valid(raw) {
		return artifact, appauthority.VerifiedPackage{}, errors.New("AppPackage artifact must be valid JSON")
	}
	if err := json.Unmarshal(raw, &artifact); err != nil {
		return artifact, appauthority.VerifiedPackage{}, err
	}
	verified, err := verifyPackageCarrier(artifact)
	if err != nil {
		return artifact, appauthority.VerifiedPackage{}, err
	}
	canonical, err := json.Marshal(artifact)
	if err != nil {
		return artifact, appauthority.VerifiedPackage{}, err
	}
	if !bytes.Equal(raw, canonical) {
		return artifact, appauthority.VerifiedPackage{}, errors.New("AppPackage artifact is not in its unique canonical serialization")
	}
	return artifact, verified, nil
}

func verifyPackageCarrier(artifact PackageArtifact) (appauthority.VerifiedPackage, error) {
	if artifact.SchemaVersion != PackageSchemaVersion {
		return appauthority.VerifiedPackage{}, fmt.Errorf("unsupported AppPackage artifact schema %q", artifact.SchemaVersion)
	}
	if !lowercaseSHA256.MatchString(artifact.AppReleaseDigest) {
		return appauthority.VerifiedPackage{}, errors.New("AppPackage artifact has invalid app_release_digest")
	}
	manifest, err := appauthority.ParseAppManifest(artifact.CanonicalManifest)
	if err != nil {
		return appauthority.VerifiedPackage{}, fmt.Errorf("parse canonical AppManifest: %w", err)
	}
	canonicalManifest, err := appauthority.CanonicalAppManifest(manifest)
	if err != nil || !bytes.Equal(canonicalManifest, artifact.CanonicalManifest) {
		return appauthority.VerifiedPackage{}, errors.New("AppManifest bytes are not canonical")
	}
	bundle, err := appauthority.ParseMetadataBundle(artifact.CanonicalBundle, manifest)
	if err != nil {
		return appauthority.VerifiedPackage{}, fmt.Errorf("parse canonical MetadataBundle: %w", err)
	}
	canonicalBundle, err := appauthority.CanonicalMetadataBundle(bundle, manifest)
	if err != nil || !bytes.Equal(canonicalBundle, artifact.CanonicalBundle) {
		return appauthority.VerifiedPackage{}, errors.New("MetadataBundle bytes are not canonical")
	}
	if artifact.ComponentBlobs == nil || len(artifact.ComponentBlobs) != len(bundle.Components) {
		return appauthority.VerifiedPackage{}, errors.New("component_blobs must cover every MetadataBundle component exactly once")
	}
	blobs := make([]appauthority.ComponentBlob, len(artifact.ComponentBlobs))
	for i, blob := range artifact.ComponentBlobs {
		if i > 0 && blob.ComponentID <= artifact.ComponentBlobs[i-1].ComponentID {
			return appauthority.VerifiedPackage{}, errors.New("component_blobs must be strictly sorted by component_id")
		}
		if len(blob.CanonicalBehavior) == 0 {
			return appauthority.VerifiedPackage{}, fmt.Errorf("component_blobs[%d] has empty canonical behavior", i)
		}
		blobs[i] = appauthority.ComponentBlob{ComponentID: blob.ComponentID, CanonicalBehavior: slices.Clone(blob.CanonicalBehavior)}
	}
	verified, err := appauthority.VerifyPackage(appauthority.Package{Manifest: manifest, Bundle: bundle, ComponentBlobs: blobs})
	if err != nil {
		return appauthority.VerifiedPackage{}, err
	}
	if verified.AppReleaseDigest != artifact.AppReleaseDigest {
		return appauthority.VerifiedPackage{}, fmt.Errorf("AppPackage root digest mismatch: computed %s, declared %s", verified.AppReleaseDigest, artifact.AppReleaseDigest)
	}
	return verified, nil
}

func SignAttestation(pkg appauthority.VerifiedPackage, provenance Provenance, keyID string, issuedAt time.Time, privateKey ed25519.PrivateKey) (PublisherAttestation, []byte, error) {
	if len(privateKey) != ed25519.PrivateKeySize {
		return PublisherAttestation{}, nil, errors.New("Ed25519 private key is required")
	}
	attestation := PublisherAttestation{
		SchemaVersion: AttestationSchemaVersion, PublisherID: pkg.Manifest.PublisherID, KeyID: keyID,
		AppID: pkg.Manifest.AppID, SemanticVersion: pkg.Manifest.SemanticVersion, AppReleaseDigest: pkg.AppReleaseDigest,
		CanonicalizationProfile: CanonicalizationProfile, ArtifactMediaType: ArtifactMediaType,
		IssuedAt: issuedAt.UTC().Truncate(time.Microsecond).Format(time.RFC3339Nano), Provenance: provenance,
		SignatureAlgorithm: SignatureAlgorithm,
	}
	statement, err := attestationStatement(attestation)
	if err != nil {
		return PublisherAttestation{}, nil, err
	}
	attestation.Signature = ed25519.Sign(privateKey, statement)
	raw, err := MarshalAttestation(attestation)
	return attestation, raw, err
}

func MarshalAttestation(attestation PublisherAttestation) ([]byte, error) {
	if _, err := validateAttestationShape(attestation); err != nil {
		return nil, err
	}
	return json.Marshal(attestation)
}

func ParseAttestation(raw []byte) (PublisherAttestation, error) {
	var attestation PublisherAttestation
	if len(raw) == 0 || !json.Valid(raw) {
		return attestation, errors.New("publisher attestation must be valid JSON")
	}
	if err := json.Unmarshal(raw, &attestation); err != nil {
		return attestation, err
	}
	if _, err := validateAttestationShape(attestation); err != nil {
		return attestation, err
	}
	canonical, err := json.Marshal(attestation)
	if err != nil {
		return attestation, err
	}
	if !bytes.Equal(raw, canonical) {
		return attestation, errors.New("publisher attestation is not in its unique canonical serialization")
	}
	return attestation, nil
}

func Verify(packageRaw, attestationRaw []byte, publicKey ed25519.PublicKey) (VerifiedArtifact, error) {
	_, pkg, err := ParsePackage(packageRaw)
	if err != nil {
		return VerifiedArtifact{}, err
	}
	attestation, err := ParseAttestation(attestationRaw)
	if err != nil {
		return VerifiedArtifact{}, err
	}
	issuedAt, err := validateAttestationShape(attestation)
	if err != nil {
		return VerifiedArtifact{}, err
	}
	if len(publicKey) != ed25519.PublicKeySize {
		return VerifiedArtifact{}, errors.New("Ed25519 public key is required")
	}
	if attestation.PublisherID != pkg.Manifest.PublisherID || attestation.AppID != pkg.Manifest.AppID ||
		attestation.SemanticVersion != pkg.Manifest.SemanticVersion || attestation.AppReleaseDigest != pkg.AppReleaseDigest {
		return VerifiedArtifact{}, errors.New("publisher attestation does not bind the verified AppRelease tuple")
	}
	statement, err := attestationStatement(attestation)
	if err != nil {
		return VerifiedArtifact{}, err
	}
	if !ed25519.Verify(publicKey, statement, attestation.Signature) {
		return VerifiedArtifact{}, errors.New("publisher attestation signature verification failed")
	}
	digest := digest(attestationDigestDomain, attestationRaw)
	return VerifiedArtifact{Package: pkg, Attestation: VerifiedAttestation{
		PublisherID: attestation.PublisherID, KeyID: attestation.KeyID, AppID: attestation.AppID,
		SemanticVersion: attestation.SemanticVersion, AppReleaseDigest: attestation.AppReleaseDigest,
		CanonicalizationProfile: attestation.CanonicalizationProfile, ArtifactMediaType: attestation.ArtifactMediaType,
		AttestationDigest: digest, IssuedAt: issuedAt,
	}}, nil
}

func validateAttestationShape(attestation PublisherAttestation) (time.Time, error) {
	if attestation.SchemaVersion != AttestationSchemaVersion || attestation.CanonicalizationProfile != CanonicalizationProfile ||
		attestation.ArtifactMediaType != ArtifactMediaType || attestation.SignatureAlgorithm != SignatureAlgorithm {
		return time.Time{}, errors.New("publisher attestation has unsupported schema, canonicalization, media type, or signature algorithm")
	}
	if err := appauthority.ValidatePublisherID(attestation.PublisherID); err != nil {
		return time.Time{}, err
	}
	if err := appauthority.ValidateAppID(attestation.AppID); err != nil {
		return time.Time{}, err
	}
	if strings.TrimSpace(attestation.KeyID) == "" || len(attestation.KeyID) > 128 || strings.TrimSpace(attestation.KeyID) != attestation.KeyID {
		return time.Time{}, errors.New("publisher attestation key_id must be 1..128 canonical bytes")
	}
	if err := appauthority.ValidateDigest(attestation.AppReleaseDigest); err != nil {
		return time.Time{}, err
	}
	issuedAt, err := time.Parse(time.RFC3339Nano, attestation.IssuedAt)
	if err != nil || issuedAt.IsZero() || issuedAt.Location() != time.UTC || issuedAt.Format(time.RFC3339Nano) != attestation.IssuedAt {
		return time.Time{}, errors.New("publisher attestation issued_at must be canonical UTC RFC3339")
	}
	if len(attestation.Signature) != ed25519.SignatureSize {
		return time.Time{}, errors.New("publisher attestation signature has invalid length")
	}
	if err := validateProvenance(attestation.Provenance); err != nil {
		return time.Time{}, err
	}
	return issuedAt.UTC().Truncate(time.Microsecond), nil
}

func validateProvenance(provenance Provenance) error {
	if provenance.SchemaVersion != ProvenanceSchemaVersion {
		return fmt.Errorf("unsupported provenance schema %q", provenance.SchemaVersion)
	}
	for label, value := range map[string]string{
		"source_repository": provenance.SourceRepository, "source_revision": provenance.SourceRevision,
		"source_path": provenance.SourcePath, "builder_id": provenance.BuilderID,
		"contract_module_version": provenance.ContractModuleVersion,
	} {
		if value == "" || strings.TrimSpace(value) != value || strings.ContainsRune(value, '\x00') {
			return fmt.Errorf("provenance %s must be non-empty canonical text", label)
		}
	}
	if !lowercaseSHA256.MatchString(provenance.SourceTreeDigest) {
		return errors.New("provenance source_tree_digest must be a lowercase SHA-256 digest")
	}
	return nil
}

func attestationStatement(attestation PublisherAttestation) ([]byte, error) {
	if _, err := validateAttestationShape(PublisherAttestation{
		SchemaVersion: attestation.SchemaVersion, PublisherID: attestation.PublisherID, KeyID: attestation.KeyID,
		AppID: attestation.AppID, SemanticVersion: attestation.SemanticVersion, AppReleaseDigest: attestation.AppReleaseDigest,
		CanonicalizationProfile: attestation.CanonicalizationProfile, ArtifactMediaType: attestation.ArtifactMediaType,
		IssuedAt: attestation.IssuedAt, Provenance: attestation.Provenance, SignatureAlgorithm: attestation.SignatureAlgorithm,
		Signature: make([]byte, ed25519.SignatureSize),
	}); err != nil {
		return nil, err
	}
	type statement struct {
		SchemaVersion           string     `json:"schema_version"`
		PublisherID             string     `json:"publisher_id"`
		KeyID                   string     `json:"key_id"`
		AppID                   string     `json:"app_id"`
		SemanticVersion         string     `json:"semantic_version"`
		AppReleaseDigest        string     `json:"app_release_digest"`
		CanonicalizationProfile string     `json:"canonicalization_profile"`
		ArtifactMediaType       string     `json:"artifact_media_type"`
		IssuedAt                string     `json:"issued_at"`
		Provenance              Provenance `json:"provenance"`
		SignatureAlgorithm      string     `json:"signature_algorithm"`
	}
	raw, err := json.Marshal(statement{
		SchemaVersion: attestation.SchemaVersion, PublisherID: attestation.PublisherID, KeyID: attestation.KeyID,
		AppID: attestation.AppID, SemanticVersion: attestation.SemanticVersion, AppReleaseDigest: attestation.AppReleaseDigest,
		CanonicalizationProfile: attestation.CanonicalizationProfile, ArtifactMediaType: attestation.ArtifactMediaType,
		IssuedAt: attestation.IssuedAt, Provenance: attestation.Provenance, SignatureAlgorithm: attestation.SignatureAlgorithm,
	})
	if err != nil {
		return nil, err
	}
	return append([]byte(attestationDomain), raw...), nil
}

func digest(domain string, raw []byte) string {
	h := sha256.New()
	_, _ = h.Write([]byte(domain))
	_, _ = h.Write(raw)
	return "sha256:" + hex.EncodeToString(h.Sum(nil))
}
