package layoutbundleownerverificationv1

import (
	"encoding/hex"
	"fmt"
	"strings"
	"unicode/utf8"

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

// ValidateReceipt validates the complete stateless singular receipt value.
func ValidateReceipt(receipt Receipt) error {
	if receipt.ReceiptVersion != ReceiptVersion {
		return fmt.Errorf("Receipt.ReceiptVersion must equal %q", ReceiptVersion)
	}
	if err := validateOrgID(receipt.OrgID); err != nil {
		return fmt.Errorf("Receipt.OrgID: %w", err)
	}
	if !isLowercaseUUIDVersion7(receipt.WriterCommandID) {
		return fmt.Errorf("Receipt.WriterCommandID must be a lowercase UUIDv7")
	}
	if receipt.WriterReservationGeneration == 0 {
		return fmt.Errorf("Receipt.WriterReservationGeneration must be positive")
	}
	if err := validateDigest(receipt.SourceRequestDigest); err != nil {
		return fmt.Errorf("Receipt.SourceRequestDigest: %w", err)
	}
	if err := validateDigest(receipt.RequiredBundleKeySetDigest); err != nil {
		return fmt.Errorf("Receipt.RequiredBundleKeySetDigest: %w", err)
	}
	if err := validateEvidenceBinding(
		receipt.BundleKey,
		receipt.EvidenceVersion,
		receipt.EvidenceDigest,
		receipt.VerificationState,
	); err != nil {
		return fmt.Errorf("Receipt: %w", err)
	}
	return nil
}

// ValidateReceiptSet validates one complete source-bound receipt set against
// the exact recomputed RequiredBundleKeySet value. It rejects missing, extra,
// duplicate, or foreign-owner entries and recomputes every singular receipt
// digest before accepting the set.
func ValidateReceiptSet(
	set ReceiptSet,
	required layoutbundlekeyv1.RequiredBundleKeySet,
) error {
	if set.ReceiptSetVersion != ReceiptSetVersion {
		return fmt.Errorf("ReceiptSet.ReceiptSetVersion must equal %q", ReceiptSetVersion)
	}
	if set.ReceiptVersion != ReceiptVersion {
		return fmt.Errorf("ReceiptSet.ReceiptVersion must equal %q", ReceiptVersion)
	}
	if err := validateOrgID(set.OrgID); err != nil {
		return fmt.Errorf("ReceiptSet.OrgID: %w", err)
	}
	if !isLowercaseUUIDVersion7(set.WriterCommandID) {
		return fmt.Errorf("ReceiptSet.WriterCommandID must be a lowercase UUIDv7")
	}
	if set.WriterReservationGeneration == 0 {
		return fmt.Errorf("ReceiptSet.WriterReservationGeneration must be positive")
	}
	if err := validateDigest(set.SourceRequestDigest); err != nil {
		return fmt.Errorf("ReceiptSet.SourceRequestDigest: %w", err)
	}
	if err := validateDigest(set.RequiredBundleKeySetDigest); err != nil {
		return fmt.Errorf("ReceiptSet.RequiredBundleKeySetDigest: %w", err)
	}
	if set.Receipts == nil {
		return fmt.Errorf("ReceiptSet.Receipts must be present")
	}
	if len(set.Receipts) > MaxReceipts {
		return fmt.Errorf("ReceiptSet.Receipts exceeds %d entries", MaxReceipts)
	}
	// The complete-set byte ceiling is intentionally checked before required
	// set canonicalization, singular-receipt digest derivation, sorting, or
	// complete-buffer allocation. canonicalReceiptSetSize performs arithmetic
	// only and does not allocate in proportion to the canonical byte length.
	size, ok := canonicalReceiptSetSize(set)
	if !ok || size > MaxCanonicalReceiptSetBytes {
		return fmt.Errorf("ReceiptSet canonical bytes exceed %d", MaxCanonicalReceiptSetBytes)
	}
	if err := layoutbundlekeyv1.ValidateRequiredBundleKeySet(required); err != nil {
		return fmt.Errorf("RequiredBundleKeySet: %w", err)
	}
	requiredDigest, err := layoutbundlekeyv1.RequiredBundleKeySetDigest(required)
	if err != nil {
		return fmt.Errorf("RequiredBundleKeySet: %w", err)
	}
	if set.RequiredBundleKeySetDigest != requiredDigest {
		return fmt.Errorf("ReceiptSet.RequiredBundleKeySetDigest does not match RequiredBundleKeySet")
	}
	if len(set.Receipts) != len(required.BundleKeys) {
		return fmt.Errorf("ReceiptSet.Receipts must contain exactly one entry per required BundleKey")
	}

	requiredKeys := make(map[layoutbundlekeyv1.BundleKey]struct{}, len(required.BundleKeys))
	for _, key := range required.BundleKeys {
		requiredKeys[key] = struct{}{}
	}
	seen := make(map[layoutbundlekeyv1.BundleKey]struct{}, len(set.Receipts))
	for index, entry := range set.Receipts {
		if _, duplicate := seen[entry.BundleKey]; duplicate {
			return fmt.Errorf("ReceiptSet.Receipts contains a duplicate BundleKey")
		}
		seen[entry.BundleKey] = struct{}{}
		if _, present := requiredKeys[entry.BundleKey]; !present {
			return fmt.Errorf("ReceiptSet.Receipts[%d] is not a required BundleKey", index)
		}
		receipt := ReconstructReceipt(set, entry)
		if err := ValidateReceipt(receipt); err != nil {
			return fmt.Errorf("ReceiptSet.Receipts[%d]: %w", index, err)
		}
		if _, err := ReceiptDigest(receipt); err != nil {
			return fmt.Errorf("ReceiptSet.Receipts[%d]: %w", index, err)
		}
	}
	for _, key := range required.BundleKeys {
		if _, present := seen[key]; !present {
			return fmt.Errorf("ReceiptSet.Receipts is missing a required BundleKey")
		}
	}
	return nil
}

// ReconstructReceipt expands one set entry with its shared source envelope.
func ReconstructReceipt(set ReceiptSet, entry ReceiptEntry) Receipt {
	return Receipt{
		ReceiptVersion:              set.ReceiptVersion,
		OrgID:                       set.OrgID,
		WriterCommandID:             set.WriterCommandID,
		WriterReservationGeneration: set.WriterReservationGeneration,
		SourceRequestDigest:         set.SourceRequestDigest,
		RequiredBundleKeySetDigest:  set.RequiredBundleKeySetDigest,
		BundleKey:                   entry.BundleKey,
		EvidenceVersion:             entry.EvidenceVersion,
		EvidenceDigest:              entry.EvidenceDigest,
		VerificationState:           entry.VerificationState,
	}
}

func validateEvidenceBinding(
	key layoutbundlekeyv1.BundleKey,
	evidenceVersion string,
	evidenceDigest string,
	state VerificationState,
) error {
	if err := layoutbundlekeyv1.ValidateBundleKey(key); err != nil {
		return fmt.Errorf("BundleKey: %w", err)
	}
	var expectedVersion string
	switch key.SlotOwnerKind {
	case layoutbundlekeyv1.SlotOwnerKindApp:
		expectedVersion = AppAuthorityEvidenceVersion
	case layoutbundlekeyv1.SlotOwnerKindTenant:
		expectedVersion = TenantAuthorityEvidenceVersion
	case layoutbundlekeyv1.SlotOwnerKindSystem:
		expectedVersion = SystemAuthorityEvidenceVersion
	default:
		return fmt.Errorf("BundleKey has an unknown owner kind")
	}
	if evidenceVersion != expectedVersion {
		return fmt.Errorf("EvidenceVersion must equal %q for owner kind %q", expectedVersion, key.SlotOwnerKind)
	}
	if err := validateDigest(evidenceDigest); err != nil {
		return fmt.Errorf("EvidenceDigest: %w", err)
	}
	if state != VerificationStateVerified {
		return fmt.Errorf("VerificationState must equal VERIFIED (0x01)")
	}
	return nil
}

func validateOrgID(value string) error {
	if value == "" || len(value) > 255 || !utf8.ValidString(value) {
		return fmt.Errorf("must be well-formed UTF-8 with 1..255 bytes")
	}
	if value[0] == ' ' || value[len(value)-1] == ' ' {
		return fmt.Errorf("must not have surrounding ASCII space")
	}
	for _, character := range []byte(value) {
		if character < 0x20 || character == 0x7f {
			return fmt.Errorf("must not contain ASCII control or DEL bytes")
		}
	}
	return nil
}

func validateDigest(value string) error {
	const prefix = "sha256:"
	if len(value) != len(prefix)+64 || !strings.HasPrefix(value, prefix) {
		return fmt.Errorf("must be sha256:<64 lowercase hex>")
	}
	hexText := value[len(prefix):]
	if strings.ToLower(hexText) != hexText {
		return fmt.Errorf("hex must be lowercase")
	}
	if _, err := hex.DecodeString(hexText); err != nil {
		return fmt.Errorf("hex is invalid")
	}
	return nil
}

func isLowercaseUUIDVersion7(value string) bool {
	if len(value) != 36 ||
		value[8] != '-' || value[13] != '-' ||
		value[18] != '-' || value[23] != '-' ||
		value[14] != '7' {
		return false
	}
	for index, character := range []byte(value) {
		if index == 8 || index == 13 || index == 18 || index == 23 {
			continue
		}
		if (character < '0' || character > '9') &&
			(character < 'a' || character > 'f') {
			return false
		}
	}
	variant := value[19]
	return variant == '8' || variant == '9' || variant == 'a' || variant == 'b'
}
