package catalogpublicationv2

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

// ValidateBinding fails closed unless every member of the complete v2
// publication identity is present and canonical. CatalogRevision and
// SecurityEpoch remain independent positive axes; transition rules belong to
// the Catalog publication authority, not this value package.
func ValidateBinding(binding Binding) error {
	if err := validateOrgID(binding.OrgID); err != nil {
		return fmt.Errorf("catalog publication v2 binding: OrgID: %w", err)
	}
	if binding.CatalogRevision == 0 {
		return fmt.Errorf("catalog publication v2 binding: CatalogRevision must be positive")
	}
	if err := validateDigest(binding.CatalogSnapshotDigest); err != nil {
		return fmt.Errorf("catalog publication v2 binding: CatalogSnapshotDigest: %w", err)
	}
	if binding.SchemaGeneration == 0 {
		return fmt.Errorf("catalog publication v2 binding: SchemaGeneration must be positive")
	}
	if binding.EngineGeneration == 0 {
		return fmt.Errorf("catalog publication v2 binding: EngineGeneration must be positive")
	}
	if err := validateDigest(binding.LayoutValidationViewDigest); err != nil {
		return fmt.Errorf("catalog publication v2 binding: LayoutValidationViewDigest: %w", err)
	}
	if err := validateDigest(binding.LayoutBundleOwnerVerificationReceiptSetDigest); err != nil {
		return fmt.Errorf(
			"catalog publication v2 binding: LayoutBundleOwnerVerificationReceiptSetDigest: %w",
			err,
		)
	}
	if err := validateDigest(binding.LayoutActivationSetDigest); err != nil {
		return fmt.Errorf("catalog publication v2 binding: LayoutActivationSetDigest: %w", err)
	}
	if binding.SecurityEpoch == 0 {
		return fmt.Errorf("catalog publication v2 binding: SecurityEpoch must be positive")
	}
	if err := validateDigest(binding.LayoutRoutingSecurityViewDigest); err != nil {
		return fmt.Errorf("catalog publication v2 binding: LayoutRoutingSecurityViewDigest: %w", err)
	}
	return nil
}

func validateOrgID(value string) error {
	if value == "" || len(value) > MaxOrgIDBytes || !utf8.ValidString(value) {
		return fmt.Errorf("must be well-formed UTF-8 with 1..%d bytes", MaxOrgIDBytes)
	}
	if value[0] == ' ' || value[len(value)-1] == ' ' {
		return fmt.Errorf("must not have surrounding ASCII space")
	}
	for _, b := range []byte(value) {
		if b < 0x20 || b == 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
}
