package tenantlayoutbundleauthorityv1

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

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

// ValidateTenantNamespaceID validates exact canonical tenant namespace bytes.
// It proves syntax only, not that Catalog has reserved the namespace.
func ValidateTenantNamespaceID(value string) error {
	if err := layoutbundlekeyv1.ValidateTenantNamespaceID(value); err != nil {
		return err
	}
	if value == ReservedSystemNamespace {
		return fmt.Errorf("TenantNamespaceID %q is reserved for system ownership", ReservedSystemNamespace)
	}
	return nil
}

// ValidateCatalogOrgID validates the exact v1 Catalog OrgID identity grammar.
// No trimming, normalization, folding, or alias lookup is performed.
func ValidateCatalogOrgID(value string) error {
	if value == "" || len(value) > MaxCatalogOrgIDBytes || !utf8.ValidString(value) {
		return fmt.Errorf("OrgID must be well-formed UTF-8 with 1..%d bytes", MaxCatalogOrgIDBytes)
	}
	if value[0] == ' ' || value[len(value)-1] == ' ' {
		return fmt.Errorf("OrgID must not have leading or trailing ASCII space")
	}
	for _, character := range []byte(value) {
		if character < 0x20 || character == 0x7f {
			return fmt.Errorf("OrgID must not contain ASCII control or DEL bytes")
		}
	}
	return nil
}

// ValidateBundleKey validates the explicit versioned tenant-owned key. It
// does not prove reservation, binding, slot history, or caller authority.
func ValidateBundleKey(key BundleKey) error {
	if key.BundleKeyVersion != layoutbundlekeyv1.BundleKeyVersion {
		return fmt.Errorf("BundleKey.BundleKeyVersion must equal %q", layoutbundlekeyv1.BundleKeyVersion)
	}
	if key.SlotOwnerKind != layoutbundlekeyv1.SlotOwnerKindTenant {
		return fmt.Errorf("BundleKey.SlotOwnerKind must equal tenant")
	}
	if err := ValidateTenantNamespaceID(key.SlotOwnerNamespace); err != nil {
		return fmt.Errorf("BundleKey.SlotOwnerNamespace: %w", err)
	}
	if err := layoutbundlekeyv1.ValidateLocalComponentKey(key.LocalComponentKey); err != nil {
		return fmt.Errorf("BundleKey.LocalComponentKey: %w", err)
	}
	if err := layoutbundlekeyv1.ValidateBundleKey(portableBundleKey(key)); err != nil {
		return fmt.Errorf("BundleKey: %w", err)
	}
	return nil
}

func ValidateNamespaceValue(value TenantNamespaceReservationV1) error {
	if value.ValueVersion != NamespaceReservationValueVersion {
		return fmt.Errorf(
			"TenantNamespaceReservationV1.ValueVersion must equal %q",
			NamespaceReservationValueVersion,
		)
	}
	if err := ValidateTenantNamespaceID(value.TenantNamespaceID); err != nil {
		return fmt.Errorf("TenantNamespaceReservationV1.TenantNamespaceID: %w", err)
	}
	switch value.State {
	case NamespaceStateActive, NamespaceStateTombstone:
		return nil
	default:
		return fmt.Errorf("TenantNamespaceReservationV1.State must be ACTIVE or TOMBSTONE")
	}
}

func ValidateOrgBindingValue(value TenantOrgNamespaceBindingV1) error {
	if value.ValueVersion != OrgNamespaceBindingValueVersion {
		return fmt.Errorf(
			"TenantOrgNamespaceBindingV1.ValueVersion must equal %q",
			OrgNamespaceBindingValueVersion,
		)
	}
	if err := ValidateCatalogOrgID(value.OrgID); err != nil {
		return fmt.Errorf("TenantOrgNamespaceBindingV1.OrgID: %w", err)
	}
	if err := ValidateTenantNamespaceID(value.TenantNamespaceID); err != nil {
		return fmt.Errorf("TenantOrgNamespaceBindingV1.TenantNamespaceID: %w", err)
	}
	switch value.State {
	case OrgBindingStateActive, OrgBindingStateTombstone:
		return nil
	default:
		return fmt.Errorf("TenantOrgNamespaceBindingV1.State must be ACTIVE or TOMBSTONE")
	}
}

func ValidateSlotValue(value TenantLayoutBundleSlotV1) error {
	if value.ValueVersion != LayoutBundleSlotValueVersion {
		return fmt.Errorf(
			"TenantLayoutBundleSlotV1.ValueVersion must equal %q",
			LayoutBundleSlotValueVersion,
		)
	}
	if err := ValidateBundleKey(value.BundleKey); err != nil {
		return fmt.Errorf("TenantLayoutBundleSlotV1.BundleKey: %w", err)
	}
	switch value.State {
	case SlotStateActive, SlotStateTombstone:
		return nil
	default:
		return fmt.Errorf("TenantLayoutBundleSlotV1.State must be ACTIVE or TOMBSTONE")
	}
}

// ValidateCommandID rejects every spelling except lowercase canonical UUIDv7.
func ValidateCommandID(commandID CommandID) error {
	value := string(commandID)
	if len(value) != 36 || value[8] != '-' || value[13] != '-' || value[18] != '-' || value[23] != '-' {
		return fmt.Errorf("CommandID must be a lowercase canonical UUIDv7")
	}
	for index := range value {
		switch index {
		case 8, 13, 18, 23:
			continue
		}
		if !isLowerHex(value[index]) {
			return fmt.Errorf("CommandID must be a lowercase canonical UUIDv7")
		}
	}
	if value[14] != '7' || !strings.ContainsRune("89ab", rune(value[19])) {
		return fmt.Errorf("CommandID must be a lowercase canonical UUIDv7")
	}
	return nil
}

func ValidateNamespaceCommandRequest(request TenantNamespaceCommandRequest) error {
	if err := ValidateTenantNamespaceID(request.TargetTenantNamespaceID); err != nil {
		return fmt.Errorf("TenantNamespaceCommandRequest.TargetTenantNamespaceID: %w", err)
	}
	if err := ValidateNamespaceValue(request.DesiredNamespaceValue); err != nil {
		return fmt.Errorf("TenantNamespaceCommandRequest.DesiredNamespaceValue: %w", err)
	}
	if request.TargetTenantNamespaceID != request.DesiredNamespaceValue.TenantNamespaceID {
		return fmt.Errorf("TenantNamespaceCommandRequest target must equal desired namespace identity")
	}
	if err := validateNamespaceExpectedPrior(request.ExpectedPriorNamespace); err != nil {
		return fmt.Errorf("TenantNamespaceCommandRequest.ExpectedPriorNamespace: %w", err)
	}

	switch request.Operation {
	case NamespaceOperationReserve:
		if request.DesiredNamespaceValue.State != NamespaceStateActive ||
			request.ExpectedPriorNamespace.Variant != NamespaceExpectedPriorAbsent {
			return fmt.Errorf("RESERVE requires desired ACTIVE and expected ABSENT")
		}
	case NamespaceOperationTombstone:
		if request.DesiredNamespaceValue.State != NamespaceStateTombstone ||
			request.ExpectedPriorNamespace.Variant != NamespaceExpectedPriorExact {
			return fmt.Errorf("namespace TOMBSTONE requires desired TOMBSTONE and expected EXACT active digest")
		}
		active := request.DesiredNamespaceValue
		active.State = NamespaceStateActive
		activeDigest, err := NamespaceValueDigest(active)
		if err != nil {
			return fmt.Errorf("derive active namespace prior: %w", err)
		}
		if request.ExpectedPriorNamespace.NamespaceValueDigest != activeDigest {
			return fmt.Errorf("ExpectedPriorNamespace digest must equal the exact ACTIVE namespace-value digest")
		}
	default:
		return fmt.Errorf("TenantNamespaceCommandRequest.Operation must be RESERVE or TOMBSTONE")
	}
	return nil
}

func ValidateOrgBindingCommandRequest(request TenantOrgNamespaceBindingCommandRequest) error {
	if err := ValidateCatalogOrgID(request.TargetOrgID); err != nil {
		return fmt.Errorf("TenantOrgNamespaceBindingCommandRequest.TargetOrgID: %w", err)
	}
	if err := ValidateOrgBindingValue(request.DesiredBindingValue); err != nil {
		return fmt.Errorf("TenantOrgNamespaceBindingCommandRequest.DesiredBindingValue: %w", err)
	}
	if request.TargetOrgID != request.DesiredBindingValue.OrgID {
		return fmt.Errorf("TenantOrgNamespaceBindingCommandRequest target must equal desired binding identity")
	}
	if err := validateDigest(request.ExpectedNamespaceReservationDigest); err != nil {
		return fmt.Errorf("ExpectedNamespaceReservationDigest: %w", err)
	}
	activeNamespace := TenantNamespaceReservationV1{
		ValueVersion:      NamespaceReservationValueVersion,
		TenantNamespaceID: request.DesiredBindingValue.TenantNamespaceID,
		State:             NamespaceStateActive,
	}
	activeNamespaceDigest, err := NamespaceValueDigest(activeNamespace)
	if err != nil {
		return fmt.Errorf("derive active namespace dependency: %w", err)
	}
	if request.ExpectedNamespaceReservationDigest != activeNamespaceDigest {
		return fmt.Errorf("ExpectedNamespaceReservationDigest must equal the exact ACTIVE namespace-value digest")
	}
	if err := validateBindingExpectedPrior(request.ExpectedPriorBinding); err != nil {
		return fmt.Errorf("TenantOrgNamespaceBindingCommandRequest.ExpectedPriorBinding: %w", err)
	}

	switch request.Operation {
	case BindingOperationBind:
		if request.DesiredBindingValue.State != OrgBindingStateActive ||
			request.ExpectedPriorBinding.Variant != BindingExpectedPriorAbsent {
			return fmt.Errorf("BIND requires desired ACTIVE and expected ABSENT")
		}
	case BindingOperationTombstone:
		if request.DesiredBindingValue.State != OrgBindingStateTombstone ||
			request.ExpectedPriorBinding.Variant != BindingExpectedPriorExact {
			return fmt.Errorf("binding TOMBSTONE requires desired TOMBSTONE and expected EXACT active digest")
		}
		active := request.DesiredBindingValue
		active.State = OrgBindingStateActive
		activeDigest, err := OrgBindingValueDigest(active)
		if err != nil {
			return fmt.Errorf("derive active binding prior: %w", err)
		}
		if request.ExpectedPriorBinding.BindingValueDigest != activeDigest {
			return fmt.Errorf("ExpectedPriorBinding digest must equal the exact ACTIVE binding-value digest")
		}
	default:
		return fmt.Errorf("TenantOrgNamespaceBindingCommandRequest.Operation must be BIND or TOMBSTONE")
	}
	return nil
}

func ValidateSlotCommandRequest(request TenantLayoutBundleSlotCommandRequest) error {
	if err := ValidateCatalogOrgID(request.AuthorizingOrgID); err != nil {
		return fmt.Errorf("TenantLayoutBundleSlotCommandRequest.AuthorizingOrgID: %w", err)
	}
	if err := ValidateBundleKey(request.TargetBundleKey); err != nil {
		return fmt.Errorf("TenantLayoutBundleSlotCommandRequest.TargetBundleKey: %w", err)
	}
	if err := ValidateSlotValue(request.DesiredSlotValue); err != nil {
		return fmt.Errorf("TenantLayoutBundleSlotCommandRequest.DesiredSlotValue: %w", err)
	}
	if request.TargetBundleKey != request.DesiredSlotValue.BundleKey {
		return fmt.Errorf("TenantLayoutBundleSlotCommandRequest TargetBundleKey must equal DesiredSlotValue.BundleKey")
	}
	if err := validateDigest(request.ExpectedNamespaceReservationDigest); err != nil {
		return fmt.Errorf("ExpectedNamespaceReservationDigest: %w", err)
	}
	activeNamespace := TenantNamespaceReservationV1{
		ValueVersion:      NamespaceReservationValueVersion,
		TenantNamespaceID: request.TargetBundleKey.SlotOwnerNamespace,
		State:             NamespaceStateActive,
	}
	activeNamespaceDigest, err := NamespaceValueDigest(activeNamespace)
	if err != nil {
		return fmt.Errorf("derive active namespace dependency: %w", err)
	}
	if request.ExpectedNamespaceReservationDigest != activeNamespaceDigest {
		return fmt.Errorf("ExpectedNamespaceReservationDigest must equal the exact ACTIVE namespace-value digest")
	}
	if err := validateDigest(request.ExpectedAuthorizingOrgBindingDigest); err != nil {
		return fmt.Errorf("ExpectedAuthorizingOrgBindingDigest: %w", err)
	}
	activeBinding := TenantOrgNamespaceBindingV1{
		ValueVersion:      OrgNamespaceBindingValueVersion,
		OrgID:             request.AuthorizingOrgID,
		TenantNamespaceID: request.TargetBundleKey.SlotOwnerNamespace,
		State:             OrgBindingStateActive,
	}
	activeBindingDigest, err := OrgBindingValueDigest(activeBinding)
	if err != nil {
		return fmt.Errorf("derive active authorizing binding dependency: %w", err)
	}
	if request.ExpectedAuthorizingOrgBindingDigest != activeBindingDigest {
		return fmt.Errorf("ExpectedAuthorizingOrgBindingDigest must equal the exact ACTIVE binding-value digest")
	}
	if err := validateSlotExpectedPrior(request.ExpectedPriorSlot); err != nil {
		return fmt.Errorf("TenantLayoutBundleSlotCommandRequest.ExpectedPriorSlot: %w", err)
	}

	switch request.Operation {
	case SlotOperationCreate:
		if request.DesiredSlotValue.State != SlotStateActive ||
			request.ExpectedPriorSlot.Variant != SlotExpectedPriorAbsent {
			return fmt.Errorf("CREATE requires desired ACTIVE and expected ABSENT")
		}
	case SlotOperationRetire:
		if request.DesiredSlotValue.State != SlotStateTombstone ||
			request.ExpectedPriorSlot.Variant != SlotExpectedPriorExact {
			return fmt.Errorf("RETIRE requires desired TOMBSTONE and expected EXACT active digest")
		}
		active := request.DesiredSlotValue
		active.State = SlotStateActive
		activeDigest, err := SlotValueDigest(active)
		if err != nil {
			return fmt.Errorf("derive active slot prior: %w", err)
		}
		if request.ExpectedPriorSlot.SlotValueDigest != activeDigest {
			return fmt.Errorf("ExpectedPriorSlot digest must equal the exact ACTIVE slot-value digest")
		}
	default:
		return fmt.Errorf("TenantLayoutBundleSlotCommandRequest.Operation must be CREATE or RETIRE")
	}
	return nil
}

// ValidateEvidence validates closed evidence bytes only. The concrete
// authority must still derive them from locked source, namespace, binding,
// slot, history, and command facts.
func ValidateEvidence(evidence TenantLayoutBundleAuthorityEvidenceV1) error {
	if evidence.EvidenceVersion != EvidenceVersion {
		return fmt.Errorf(
			"TenantLayoutBundleAuthorityEvidenceV1.EvidenceVersion must equal %q",
			EvidenceVersion,
		)
	}
	if err := catalogpublicationv1.ValidateCatalogSnapshotBinding(evidence.SourceCatalogSnapshot); err != nil {
		return fmt.Errorf("TenantLayoutBundleAuthorityEvidenceV1.SourceCatalogSnapshot: %w", err)
	}
	if err := ValidateBundleKey(evidence.BundleKey); err != nil {
		return fmt.Errorf("TenantLayoutBundleAuthorityEvidenceV1.BundleKey: %w", err)
	}
	if evidence.NamespaceReservationValueVersion != NamespaceReservationValueVersion {
		return fmt.Errorf(
			"TenantLayoutBundleAuthorityEvidenceV1.NamespaceReservationValueVersion must equal %q",
			NamespaceReservationValueVersion,
		)
	}
	if evidence.NamespaceReservationState != NamespaceStateActive {
		return fmt.Errorf("TenantLayoutBundleAuthorityEvidenceV1.NamespaceReservationState must equal ACTIVE")
	}
	if evidence.OrgBindingValueVersion != OrgNamespaceBindingValueVersion {
		return fmt.Errorf(
			"TenantLayoutBundleAuthorityEvidenceV1.OrgBindingValueVersion must equal %q",
			OrgNamespaceBindingValueVersion,
		)
	}
	if evidence.OrgBindingState != OrgBindingStateActive {
		return fmt.Errorf("TenantLayoutBundleAuthorityEvidenceV1.OrgBindingState must equal ACTIVE")
	}
	if evidence.SlotValueVersion != LayoutBundleSlotValueVersion {
		return fmt.Errorf(
			"TenantLayoutBundleAuthorityEvidenceV1.SlotValueVersion must equal %q",
			LayoutBundleSlotValueVersion,
		)
	}
	if evidence.SlotState != SlotStateActive {
		return fmt.Errorf("TenantLayoutBundleAuthorityEvidenceV1.SlotState must equal ACTIVE")
	}
	return nil
}

func validateNamespaceExpectedPrior(prior TenantNamespaceExpectedPrior) error {
	switch prior.Variant {
	case NamespaceExpectedPriorAbsent:
		if prior.NamespaceValueDigest != "" {
			return fmt.Errorf("ABSENT must not carry a digest")
		}
	case NamespaceExpectedPriorExact:
		if err := validateDigest(prior.NamespaceValueDigest); err != nil {
			return fmt.Errorf("EXACT NamespaceValueDigest: %w", err)
		}
	default:
		return fmt.Errorf("Variant must be ABSENT or EXACT")
	}
	return nil
}

func validateBindingExpectedPrior(prior TenantOrgBindingExpectedPrior) error {
	switch prior.Variant {
	case BindingExpectedPriorAbsent:
		if prior.BindingValueDigest != "" {
			return fmt.Errorf("ABSENT must not carry a digest")
		}
	case BindingExpectedPriorExact:
		if err := validateDigest(prior.BindingValueDigest); err != nil {
			return fmt.Errorf("EXACT BindingValueDigest: %w", err)
		}
	default:
		return fmt.Errorf("Variant must be ABSENT or EXACT")
	}
	return nil
}

func validateSlotExpectedPrior(prior TenantSlotExpectedPrior) error {
	switch prior.Variant {
	case SlotExpectedPriorAbsent:
		if prior.SlotValueDigest != "" {
			return fmt.Errorf("ABSENT must not carry a digest")
		}
	case SlotExpectedPriorExact:
		if err := validateDigest(prior.SlotValueDigest); err != nil {
			return fmt.Errorf("EXACT SlotValueDigest: %w", err)
		}
	default:
		return fmt.Errorf("Variant must be ABSENT or EXACT")
	}
	return nil
}

func portableBundleKey(key BundleKey) layoutbundlekeyv1.BundleKey {
	return layoutbundlekeyv1.BundleKey{
		SlotOwnerKind:      key.SlotOwnerKind,
		SlotOwnerNamespace: key.SlotOwnerNamespace,
		LocalComponentKey:  key.LocalComponentKey,
	}
}

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 isLowerHex(value byte) bool {
	return value >= '0' && value <= '9' || value >= 'a' && value <= 'f'
}
