package layoutbundlekeyv1

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

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

// ValidateBundleKey validates one exact portable slot identity without
// consulting an App, tenant, system, or org registry.
func ValidateBundleKey(key BundleKey) error {
	switch key.SlotOwnerKind {
	case SlotOwnerKindApp:
		if err := appauthority.ValidateAppID(key.SlotOwnerNamespace); err != nil {
			return fmt.Errorf("BundleKey.SlotOwnerNamespace: %w", err)
		}
		if key.SlotOwnerNamespace == SystemSlotOwnerNamespace {
			return fmt.Errorf("BundleKey.SlotOwnerNamespace is reserved for system ownership")
		}
	case SlotOwnerKindTenant:
		if err := ValidateTenantNamespaceID(key.SlotOwnerNamespace); err != nil {
			return fmt.Errorf("BundleKey.SlotOwnerNamespace: %w", err)
		}
		if key.SlotOwnerNamespace == SystemSlotOwnerNamespace {
			return fmt.Errorf("BundleKey.SlotOwnerNamespace is reserved for system ownership")
		}
	case SlotOwnerKindSystem:
		if key.SlotOwnerNamespace != SystemSlotOwnerNamespace {
			return fmt.Errorf("BundleKey.SlotOwnerNamespace must equal %q for system ownership", SystemSlotOwnerNamespace)
		}
	default:
		return fmt.Errorf("BundleKey.SlotOwnerKind must be app, tenant, or system")
	}
	if err := ValidateLocalComponentKey(key.LocalComponentKey); err != nil {
		return fmt.Errorf("BundleKey.LocalComponentKey: %w", err)
	}
	return nil
}

// ValidateTenantNamespaceID validates the exact tenant namespace grammar
// pinned by BundleKeyVersion. It does not prove Catalog reservation, tombstone,
// or environment-binding authority.
func ValidateTenantNamespaceID(value string) error {
	if len(value) == 0 || len(value) > MaxSlotOwnerNamespaceBytes {
		return fmt.Errorf("TenantNamespaceID must be 1..%d ASCII bytes", MaxSlotOwnerNamespaceBytes)
	}
	segments := strings.Split(value, ".")
	if len(segments) < 2 {
		return fmt.Errorf("TenantNamespaceID must contain at least 2 dot-separated segments")
	}
	for segmentIndex, segment := range segments {
		if len(segment) == 0 || len(segment) > MaxSlotOwnerNamespaceSegmentBytes {
			return fmt.Errorf("TenantNamespaceID segment %d must be 1..%d ASCII bytes", segmentIndex, MaxSlotOwnerNamespaceSegmentBytes)
		}
		for index := 0; index < len(segment); index++ {
			character := segment[index]
			if index == 0 {
				if character < 'a' || character > 'z' {
					return fmt.Errorf("TenantNamespaceID segment %d must begin with a lowercase ASCII letter", segmentIndex)
				}
				continue
			}
			if (character < 'a' || character > 'z') &&
				(character < '0' || character > '9') && character != '-' {
				return fmt.Errorf("TenantNamespaceID segment %d contains a non-canonical byte", segmentIndex)
			}
		}
		if segment[len(segment)-1] == '-' {
			return fmt.Errorf("TenantNamespaceID segment %d must not end in '-'", segmentIndex)
		}
	}
	return nil
}

// ValidateRequiredBundleKeySet validates every key, exact tuple uniqueness,
// entry count, and the canonical-byte ceiling. Input order is not semantic.
func ValidateRequiredBundleKeySet(set RequiredBundleKeySet) error {
	if set.Version != RequiredBundleKeySetVersion {
		return fmt.Errorf("RequiredBundleKeySet.Version must equal %q", RequiredBundleKeySetVersion)
	}
	if set.BundleKeys == nil {
		return fmt.Errorf("RequiredBundleKeySet.BundleKeys must be present")
	}
	if len(set.BundleKeys) > MaxRequiredBundleKeys {
		return fmt.Errorf("RequiredBundleKeySet.BundleKeys exceeds %d entries", MaxRequiredBundleKeys)
	}
	seen := make(map[string]struct{}, len(set.BundleKeys))
	for index, key := range set.BundleKeys {
		if err := ValidateBundleKey(key); err != nil {
			return fmt.Errorf("RequiredBundleKeySet.BundleKeys[%d]: %w", index, err)
		}
		identity := bundleKeyIdentity(key)
		if _, duplicate := seen[identity]; duplicate {
			return fmt.Errorf("RequiredBundleKeySet.BundleKeys contains a duplicate BundleKey")
		}
		seen[identity] = struct{}{}
	}
	if size := canonicalRequiredSetSize(set); size > MaxCanonicalRequiredBundleKeySetBytes {
		return fmt.Errorf("RequiredBundleKeySet canonical bytes exceed %d", MaxCanonicalRequiredBundleKeySetBytes)
	}
	return nil
}

// ValidateLocalComponentKey validates only the exact key256 spelling. It does
// not prove randomness, reservation, namespace authority, or registry state.
func ValidateLocalComponentKey(value LocalComponentKey) error {
	raw := string(value)
	if len(raw) != len(LocalComponentKeyPrefix)+64 || !strings.HasPrefix(raw, LocalComponentKeyPrefix) {
		return fmt.Errorf("must be key256:<64 lowercase hex>")
	}
	hexText := raw[len(LocalComponentKeyPrefix):]
	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 bundleKeyIdentity(key BundleKey) string {
	return string([]byte{slotOwnerKindByte(key.SlotOwnerKind)}) + "\x00" +
		key.SlotOwnerNamespace + "\x00" + string(key.LocalComponentKey)
}
