package tenantlayoutbundleauthorityv1

import (
	"bytes"
	"math"
	"reflect"
	"strings"
	"testing"

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

const testTenantNamespace = "com.example.tenant"

func TestTenantNamespaceGrammarIsExact(t *testing.T) {
	maximum := strings.Repeat("a", 63) + "." + strings.Repeat("b", 61) + ".cc"
	for _, value := range []string{
		"a.b",
		"com.example.tenant",
		"tenant-1.production",
		maximum,
	} {
		if err := ValidateTenantNamespaceID(value); err != nil {
			t.Fatalf("ValidateTenantNamespaceID(%q): %v", value, err)
		}
	}
	if len(maximum) != MaxTenantNamespaceIDBytes {
		t.Fatalf("maximum namespace fixture length = %d", len(maximum))
	}
	for name, value := range map[string]string{
		"empty":             "",
		"one segment":       "tenant",
		"leading digit":     "1com.example",
		"uppercase":         "Com.example",
		"unicode":           "com.exámple",
		"empty segment":     "com..example",
		"leading hyphen":    "-com.example",
		"trailing hyphen":   "com-.example",
		"segment over 63":   strings.Repeat("a", 64) + ".b",
		"value over 128":    strings.Repeat("a", 63) + "." + strings.Repeat("b", 63) + ".c",
		"reserved system":   ReservedSystemNamespace,
		"surrounding space": " com.example",
	} {
		t.Run(name, func(t *testing.T) {
			if err := ValidateTenantNamespaceID(value); err == nil {
				t.Fatalf("ValidateTenantNamespaceID(%q) succeeded", value)
			}
		})
	}
}

func TestCatalogOrgIDGrammarIsExact(t *testing.T) {
	for _, value := range []string{
		"o",
		"org-1",
		"组织-α",
		"org internal space",
		strings.Repeat("o", MaxCatalogOrgIDBytes),
	} {
		if err := ValidateCatalogOrgID(value); err != nil {
			t.Fatalf("ValidateCatalogOrgID(%q): %v", value, err)
		}
	}
	for name, value := range map[string]string{
		"empty":          "",
		"overlong":       strings.Repeat("o", MaxCatalogOrgIDBytes+1),
		"invalid utf8":   string([]byte{0xff}),
		"leading space":  " org",
		"trailing space": "org ",
		"nul":            "org\x00id",
		"control":        "org\x1fid",
		"del":            "org\x7fid",
	} {
		t.Run(name, func(t *testing.T) {
			if err := ValidateCatalogOrgID(value); err == nil {
				t.Fatalf("ValidateCatalogOrgID(%q) succeeded", value)
			}
		})
	}
}

func TestAllThreeValuesAcceptOnlyActiveOrTombstone(t *testing.T) {
	for _, state := range []NamespaceState{NamespaceStateActive, NamespaceStateTombstone} {
		value := namespaceValue(testTenantNamespace, state)
		if err := ValidateNamespaceValue(value); err != nil {
			t.Fatal(err)
		}
		if canonical, err := CanonicalNamespaceValueBytes(value); err != nil || len(canonical) == 0 {
			t.Fatalf("namespace canonical = %x, %v", canonical, err)
		}
		if digest, err := NamespaceValueDigest(value); err != nil || !strings.HasPrefix(digest, "sha256:") {
			t.Fatalf("namespace digest = %q, %v", digest, err)
		}
	}
	for _, state := range []OrgBindingState{OrgBindingStateActive, OrgBindingStateTombstone} {
		value := bindingValue("org-1", testTenantNamespace, state)
		if err := ValidateOrgBindingValue(value); err != nil {
			t.Fatal(err)
		}
		if canonical, err := CanonicalOrgBindingValueBytes(value); err != nil || len(canonical) == 0 {
			t.Fatalf("binding canonical = %x, %v", canonical, err)
		}
		if digest, err := OrgBindingValueDigest(value); err != nil || !strings.HasPrefix(digest, "sha256:") {
			t.Fatalf("binding digest = %q, %v", digest, err)
		}
	}
	for _, state := range []SlotState{SlotStateActive, SlotStateTombstone} {
		value := slotValue(testTenantNamespace, state, "1")
		if err := ValidateSlotValue(value); err != nil {
			t.Fatal(err)
		}
		if canonical, err := CanonicalSlotValueBytes(value); err != nil || len(canonical) == 0 {
			t.Fatalf("slot canonical = %x, %v", canonical, err)
		}
		if digest, err := SlotValueDigest(value); err != nil || !strings.HasPrefix(digest, "sha256:") {
			t.Fatalf("slot digest = %q, %v", digest, err)
		}
	}

	namespace := namespaceValue(testTenantNamespace, NamespaceStateActive)
	namespace.ValueVersion = "v1"
	assertInvalidNamespaceValue(t, namespace)
	namespace = namespaceValue(testTenantNamespace, NamespaceStateActive)
	namespace.State = "ABSENT"
	assertInvalidNamespaceValue(t, namespace)

	binding := bindingValue("org-1", testTenantNamespace, OrgBindingStateActive)
	binding.ValueVersion = "v1"
	assertInvalidBindingValue(t, binding)
	binding = bindingValue("org-1", testTenantNamespace, OrgBindingStateActive)
	binding.OrgID = " org-1"
	assertInvalidBindingValue(t, binding)
	binding = bindingValue("org-1", testTenantNamespace, OrgBindingStateActive)
	binding.State = "ABSENT"
	assertInvalidBindingValue(t, binding)

	slot := slotValue(testTenantNamespace, SlotStateActive, "1")
	slot.ValueVersion = "v1"
	assertInvalidSlotValue(t, slot)
	slot = slotValue(testTenantNamespace, SlotStateActive, "1")
	slot.BundleKey.BundleKeyVersion = "v1"
	assertInvalidSlotValue(t, slot)
	slot = slotValue(testTenantNamespace, SlotStateActive, "1")
	slot.BundleKey.SlotOwnerKind = layoutbundlekeyv1.SlotOwnerKindSystem
	assertInvalidSlotValue(t, slot)
	slot = slotValue(testTenantNamespace, SlotStateActive, "1")
	slot.BundleKey.SlotOwnerNamespace = ReservedSystemNamespace
	assertInvalidSlotValue(t, slot)
	slot = slotValue(testTenantNamespace, SlotStateActive, "1")
	slot.BundleKey.LocalComponentKey = componentKey("A")
	assertInvalidSlotValue(t, slot)
	slot = slotValue(testTenantNamespace, SlotStateActive, "1")
	slot.State = "ABSENT"
	assertInvalidSlotValue(t, slot)
}

func TestCommandIDPinsLowercaseCanonicalUUIDv7(t *testing.T) {
	for _, value := range []CommandID{
		"01890f9e-7b2a-7000-8000-000000000001",
		"ffffffff-ffff-7fff-bfff-ffffffffffff",
		"00000000-0000-7000-9000-000000000000",
	} {
		if err := ValidateCommandID(value); err != nil {
			t.Fatalf("ValidateCommandID(%q): %v", value, err)
		}
	}
	for _, value := range []CommandID{
		"",
		"01890F9E-7B2A-7000-8000-000000000001",
		"01890f9e-7b2a-4000-8000-000000000001",
		"01890f9e-7b2a-7000-7000-000000000001",
		"01890f9e-7b2a-7000-c000-000000000001",
		"01890f9e7b2a-7000-8000-000000000001",
		"01890f9e-7b2a-7000-8000-00000000000g",
	} {
		if err := ValidateCommandID(value); err == nil {
			t.Fatalf("ValidateCommandID(%q) succeeded", value)
		}
	}
}

func TestAllSixTypedRequestsAndDerivableDependencies(t *testing.T) {
	namespaceReserve := namespaceReserveRequest(testTenantNamespace)
	namespaceTombstone := namespaceTombstoneRequest(t, testTenantNamespace)
	bindingBind := bindingBindRequest(t, "org-1", testTenantNamespace)
	bindingTombstone := bindingTombstoneRequest(t, "org-1", testTenantNamespace)
	slotCreate := slotCreateRequest(t, "org-1", testTenantNamespace, "1")
	slotRetire := slotRetireRequest(t, "org-1", testTenantNamespace, "1")

	if err := ValidateNamespaceCommandRequest(namespaceReserve); err != nil {
		t.Fatal(err)
	}
	if err := ValidateNamespaceCommandRequest(namespaceTombstone); err != nil {
		t.Fatal(err)
	}
	if err := ValidateOrgBindingCommandRequest(bindingBind); err != nil {
		t.Fatal(err)
	}
	if err := ValidateOrgBindingCommandRequest(bindingTombstone); err != nil {
		t.Fatal(err)
	}
	if err := ValidateSlotCommandRequest(slotCreate); err != nil {
		t.Fatal(err)
	}
	if err := ValidateSlotCommandRequest(slotRetire); err != nil {
		t.Fatal(err)
	}

	namespaceBad := namespaceReserve
	namespaceBad.TargetTenantNamespaceID = "com.example.other"
	assertInvalidNamespaceRequest(t, namespaceBad)
	namespaceBad = namespaceReserve
	namespaceBad.DesiredNamespaceValue.State = NamespaceStateTombstone
	assertInvalidNamespaceRequest(t, namespaceBad)
	namespaceBad = namespaceReserve
	namespaceBad.ExpectedPriorNamespace = TenantNamespaceExpectedPrior{
		Variant: NamespaceExpectedPriorExact, NamespaceValueDigest: mustNamespaceDigest(
			t,
			namespaceValue(testTenantNamespace, NamespaceStateActive),
		),
	}
	assertInvalidNamespaceRequest(t, namespaceBad)
	namespaceBad = namespaceTombstone
	namespaceBad.ExpectedPriorNamespace.NamespaceValueDigest = digest("9")
	assertInvalidNamespaceRequest(t, namespaceBad)

	bindingBad := bindingBind
	bindingBad.TargetOrgID = "org-2"
	assertInvalidBindingRequest(t, bindingBad)
	bindingBad = bindingBind
	bindingBad.ExpectedNamespaceReservationDigest = digest("9")
	assertInvalidBindingRequest(t, bindingBad)
	bindingBad = bindingBind
	bindingBad.DesiredBindingValue.State = OrgBindingStateTombstone
	assertInvalidBindingRequest(t, bindingBad)
	bindingBad = bindingTombstone
	bindingBad.ExpectedPriorBinding.BindingValueDigest = digest("9")
	assertInvalidBindingRequest(t, bindingBad)

	slotBad := slotCreate
	slotBad.TargetBundleKey.LocalComponentKey = componentKey("2")
	assertInvalidSlotRequest(t, slotBad)
	slotBad = slotCreate
	slotBad.ExpectedNamespaceReservationDigest = digest("9")
	assertInvalidSlotRequest(t, slotBad)
	slotBad = slotCreate
	slotBad.ExpectedAuthorizingOrgBindingDigest = digest("9")
	assertInvalidSlotRequest(t, slotBad)
	slotBad = slotCreate
	slotBad.DesiredSlotValue.State = SlotStateTombstone
	assertInvalidSlotRequest(t, slotBad)
	slotBad = slotRetire
	slotBad.ExpectedPriorSlot.SlotValueDigest = digest("9")
	assertInvalidSlotRequest(t, slotBad)
}

func TestManyOrgIDsMayShareOneNamespaceWithoutCollapsingIdentity(t *testing.T) {
	first := bindingBindRequest(t, "staging-org", testTenantNamespace)
	second := bindingBindRequest(t, "production-org", testTenantNamespace)
	if err := ValidateOrgBindingCommandRequest(first); err != nil {
		t.Fatal(err)
	}
	if err := ValidateOrgBindingCommandRequest(second); err != nil {
		t.Fatal(err)
	}
	firstDigest, err := OrgBindingCommandRequestDigest(first)
	if err != nil {
		t.Fatal(err)
	}
	secondDigest, err := OrgBindingCommandRequestDigest(second)
	if err != nil {
		t.Fatal(err)
	}
	if firstDigest == secondDigest {
		t.Fatal("distinct OrgID binding requests have one digest")
	}
}

func TestRequestKindsAreDomainSeparated(t *testing.T) {
	namespaceBytes, err := CanonicalNamespaceCommandRequestBytes(namespaceReserveRequest(testTenantNamespace))
	if err != nil {
		t.Fatal(err)
	}
	bindingBytes, err := CanonicalOrgBindingCommandRequestBytes(bindingBindRequest(t, "org-1", testTenantNamespace))
	if err != nil {
		t.Fatal(err)
	}
	slotBytes, err := CanonicalSlotCommandRequestBytes(slotCreateRequest(t, "org-1", testTenantNamespace, "1"))
	if err != nil {
		t.Fatal(err)
	}
	if bytes.Equal(namespaceBytes, bindingBytes) || bytes.Equal(namespaceBytes, slotBytes) || bytes.Equal(bindingBytes, slotBytes) {
		t.Fatal("typed request canonical bytes are not domain-separated")
	}
	if NamespaceCommandRequestDigestVersion == BindingCommandRequestDigestVersion ||
		NamespaceCommandRequestDigestVersion == SlotCommandRequestDigestVersion ||
		BindingCommandRequestDigestVersion == SlotCommandRequestDigestVersion {
		t.Fatal("request digest markers are not distinct")
	}
}

func TestCommandIDIsNotAnyRequestDigestInput(t *testing.T) {
	namespaceRequest := namespaceReserveRequest(testTenantNamespace)
	bindingRequest := bindingBindRequest(t, "org-1", testTenantNamespace)
	slotRequest := slotCreateRequest(t, "org-1", testTenantNamespace, "1")
	wantNamespace, _ := NamespaceCommandRequestDigest(namespaceRequest)
	wantBinding, _ := OrgBindingCommandRequestDigest(bindingRequest)
	wantSlot, _ := SlotCommandRequestDigest(slotRequest)
	for _, commandID := range []CommandID{
		"01890f9e-7b2a-7000-8000-000000000001",
		"01890f9e-7b2a-7000-8000-000000000002",
	} {
		if err := ValidateCommandID(commandID); err != nil {
			t.Fatal(err)
		}
		gotNamespace, _ := NamespaceCommandRequestDigest(namespaceRequest)
		gotBinding, _ := OrgBindingCommandRequestDigest(bindingRequest)
		gotSlot, _ := SlotCommandRequestDigest(slotRequest)
		if gotNamespace != wantNamespace || gotBinding != wantBinding || gotSlot != wantSlot {
			t.Fatal("independent CommandID changed a request digest")
		}
	}
}

func TestEvidenceAcceptsOnlyCompleteActiveTenantClaim(t *testing.T) {
	evidence := validEvidence("org-1", testTenantNamespace, "1")
	if err := ValidateEvidence(evidence); err != nil {
		t.Fatal(err)
	}
	if _, err := CanonicalEvidenceBytes(evidence); err != nil {
		t.Fatal(err)
	}
	if _, err := EvidenceDigest(evidence); err != nil {
		t.Fatal(err)
	}
	tests := map[string]func(*TenantLayoutBundleAuthorityEvidenceV1){
		"evidence version": func(value *TenantLayoutBundleAuthorityEvidenceV1) {
			value.EvidenceVersion = "v1"
		},
		"source": func(value *TenantLayoutBundleAuthorityEvidenceV1) {
			value.SourceCatalogSnapshot.CatalogRevision = 0
		},
		"bundle owner": func(value *TenantLayoutBundleAuthorityEvidenceV1) {
			value.BundleKey.SlotOwnerKind = layoutbundlekeyv1.SlotOwnerKindApp
		},
		"namespace value version": func(value *TenantLayoutBundleAuthorityEvidenceV1) {
			value.NamespaceReservationValueVersion = "v1"
		},
		"namespace tombstone": func(value *TenantLayoutBundleAuthorityEvidenceV1) {
			value.NamespaceReservationState = NamespaceStateTombstone
		},
		"binding value version": func(value *TenantLayoutBundleAuthorityEvidenceV1) {
			value.OrgBindingValueVersion = "v1"
		},
		"binding tombstone": func(value *TenantLayoutBundleAuthorityEvidenceV1) {
			value.OrgBindingState = OrgBindingStateTombstone
		},
		"slot value version": func(value *TenantLayoutBundleAuthorityEvidenceV1) {
			value.SlotValueVersion = "v1"
		},
		"slot tombstone": func(value *TenantLayoutBundleAuthorityEvidenceV1) {
			value.SlotState = SlotStateTombstone
		},
	}
	for name, mutate := range tests {
		t.Run(name, func(t *testing.T) {
			value := validEvidence("org-1", testTenantNamespace, "1")
			mutate(&value)
			assertInvalidEvidence(t, value)
		})
	}
}

func TestEvidenceMultibyteUnsignedLEB128Boundaries(t *testing.T) {
	value := validEvidence(strings.Repeat("o", 128), testTenantNamespace, "1")
	value.SourceCatalogSnapshot.CatalogRevision = 127
	value.SourceCatalogSnapshot.SchemaGeneration = 128
	value.SourceCatalogSnapshot.EngineGeneration = math.MaxUint64
	canonical, err := CanonicalEvidenceBytes(value)
	if err != nil {
		t.Fatal(err)
	}
	if !bytes.Contains(canonical, []byte{0x80, 0x01}) ||
		!bytes.Contains(canonical, []byte{0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x01}) {
		t.Fatal("evidence does not exercise multibyte unsigned LEB128 boundaries")
	}
}

func TestCanonicalResultsAreDetachedFromInputMutation(t *testing.T) {
	namespace := namespaceValue(testTenantNamespace, NamespaceStateActive)
	namespaceBytes, err := CanonicalNamespaceValueBytes(namespace)
	if err != nil {
		t.Fatal(err)
	}
	savedNamespace := append([]byte(nil), namespaceBytes...)
	namespace.TenantNamespaceID = "com.example.other"
	if !bytes.Equal(namespaceBytes, savedNamespace) {
		t.Fatal("namespace input mutation altered returned bytes")
	}

	binding := bindingBindRequest(t, "org-1", testTenantNamespace)
	bindingBytes, err := CanonicalOrgBindingCommandRequestBytes(binding)
	if err != nil {
		t.Fatal(err)
	}
	savedBinding := append([]byte(nil), bindingBytes...)
	binding.TargetOrgID = "org-2"
	if !bytes.Equal(bindingBytes, savedBinding) {
		t.Fatal("binding request mutation altered returned bytes")
	}

	slot := slotCreateRequest(t, "org-1", testTenantNamespace, "1")
	slotBytes, err := CanonicalSlotCommandRequestBytes(slot)
	if err != nil {
		t.Fatal(err)
	}
	savedSlot := append([]byte(nil), slotBytes...)
	slot.TargetBundleKey.LocalComponentKey = componentKey("2")
	if !bytes.Equal(slotBytes, savedSlot) {
		t.Fatal("slot request mutation altered returned bytes")
	}

	evidence := validEvidence("org-1", testTenantNamespace, "1")
	evidenceBytes, err := CanonicalEvidenceBytes(evidence)
	if err != nil {
		t.Fatal(err)
	}
	savedEvidence := append([]byte(nil), evidenceBytes...)
	evidence.SourceCatalogSnapshot.OrgID = "org-2"
	if !bytes.Equal(evidenceBytes, savedEvidence) {
		t.Fatal("evidence input mutation altered returned bytes")
	}
}

func TestDigestsCommitEveryVariableCanonicalField(t *testing.T) {
	const otherNamespace = "com.example.other"

	baseNamespace := namespaceValue(testTenantNamespace, NamespaceStateActive)
	for name, changed := range map[string]TenantNamespaceReservationV1{
		"TenantNamespaceID": namespaceValue(otherNamespace, NamespaceStateActive),
		"State":             namespaceValue(testTenantNamespace, NamespaceStateTombstone),
	} {
		t.Run("namespace-value/"+name, func(t *testing.T) {
			assertDigestChanged(t, baseNamespace, changed, NamespaceValueDigest)
		})
	}

	baseBinding := bindingValue("org-1", testTenantNamespace, OrgBindingStateActive)
	for name, changed := range map[string]TenantOrgNamespaceBindingV1{
		"OrgID":             bindingValue("org-2", testTenantNamespace, OrgBindingStateActive),
		"TenantNamespaceID": bindingValue("org-1", otherNamespace, OrgBindingStateActive),
		"State":             bindingValue("org-1", testTenantNamespace, OrgBindingStateTombstone),
	} {
		t.Run("binding-value/"+name, func(t *testing.T) {
			assertDigestChanged(t, baseBinding, changed, OrgBindingValueDigest)
		})
	}

	baseSlot := slotValue(testTenantNamespace, SlotStateActive, "1")
	for name, changed := range map[string]TenantLayoutBundleSlotV1{
		"BundleKey.SlotOwnerNamespace": slotValue(otherNamespace, SlotStateActive, "1"),
		"BundleKey.LocalComponentKey":  slotValue(testTenantNamespace, SlotStateActive, "2"),
		"State":                        slotValue(testTenantNamespace, SlotStateTombstone, "1"),
	} {
		t.Run("slot-value/"+name, func(t *testing.T) {
			assertDigestChanged(t, baseSlot, changed, SlotValueDigest)
		})
	}

	baseNamespaceRequest := namespaceReserveRequest(testTenantNamespace)
	for name, changed := range map[string]TenantNamespaceCommandRequest{
		"TargetTenantNamespaceID and DesiredNamespaceValue.TenantNamespaceID": namespaceReserveRequest(otherNamespace),
		"Operation DesiredNamespaceValue.State and ExpectedPriorNamespace":    namespaceTombstoneRequest(t, testTenantNamespace),
	} {
		t.Run("namespace-request/"+name, func(t *testing.T) {
			assertDigestChanged(t, baseNamespaceRequest, changed, NamespaceCommandRequestDigest)
		})
	}

	baseBindingRequest := bindingBindRequest(t, "org-1", testTenantNamespace)
	for name, changed := range map[string]TenantOrgNamespaceBindingCommandRequest{
		"TargetOrgID and DesiredBindingValue.OrgID": bindingBindRequest(
			t,
			"org-2",
			testTenantNamespace,
		),
		"DesiredBindingValue.TenantNamespaceID and ExpectedNamespaceReservationDigest": bindingBindRequest(
			t,
			"org-1",
			otherNamespace,
		),
		"Operation DesiredBindingValue.State and ExpectedPriorBinding": bindingTombstoneRequest(
			t,
			"org-1",
			testTenantNamespace,
		),
	} {
		t.Run("binding-request/"+name, func(t *testing.T) {
			assertDigestChanged(t, baseBindingRequest, changed, OrgBindingCommandRequestDigest)
		})
	}

	baseSlotRequest := slotCreateRequest(t, "org-1", testTenantNamespace, "1")
	for name, changed := range map[string]TenantLayoutBundleSlotCommandRequest{
		"AuthorizingOrgID and ExpectedAuthorizingOrgBindingDigest": slotCreateRequest(
			t,
			"org-2",
			testTenantNamespace,
			"1",
		),
		"TargetBundleKey and DesiredSlotValue local component key": slotCreateRequest(
			t,
			"org-1",
			testTenantNamespace,
			"2",
		),
		"TargetBundleKey and DesiredSlotValue namespace plus dependency digests": slotCreateRequest(
			t,
			"org-1",
			otherNamespace,
			"1",
		),
		"Operation DesiredSlotValue.State and ExpectedPriorSlot": slotRetireRequest(
			t,
			"org-1",
			testTenantNamespace,
			"1",
		),
	} {
		t.Run("slot-request/"+name, func(t *testing.T) {
			assertDigestChanged(t, baseSlotRequest, changed, SlotCommandRequestDigest)
		})
	}

	baseEvidence := validEvidence("org-1", testTenantNamespace, "1")
	evidenceMutations := map[string]func(*TenantLayoutBundleAuthorityEvidenceV1){
		"SourceCatalogSnapshot.OrgID": func(value *TenantLayoutBundleAuthorityEvidenceV1) {
			value.SourceCatalogSnapshot.OrgID = "org-2"
		},
		"SourceCatalogSnapshot.CatalogRevision": func(value *TenantLayoutBundleAuthorityEvidenceV1) {
			value.SourceCatalogSnapshot.CatalogRevision++
		},
		"SourceCatalogSnapshot.CatalogSnapshotDigest": func(value *TenantLayoutBundleAuthorityEvidenceV1) {
			value.SourceCatalogSnapshot.CatalogSnapshotDigest = digest("2")
		},
		"SourceCatalogSnapshot.SchemaGeneration": func(value *TenantLayoutBundleAuthorityEvidenceV1) {
			value.SourceCatalogSnapshot.SchemaGeneration++
		},
		"SourceCatalogSnapshot.EngineGeneration": func(value *TenantLayoutBundleAuthorityEvidenceV1) {
			value.SourceCatalogSnapshot.EngineGeneration++
		},
		"BundleKey.SlotOwnerNamespace": func(value *TenantLayoutBundleAuthorityEvidenceV1) {
			value.BundleKey.SlotOwnerNamespace = otherNamespace
		},
		"BundleKey.LocalComponentKey": func(value *TenantLayoutBundleAuthorityEvidenceV1) {
			value.BundleKey.LocalComponentKey = componentKey("2")
		},
	}
	for name, mutate := range evidenceMutations {
		t.Run("evidence/"+name, func(t *testing.T) {
			changed := baseEvidence
			mutate(&changed)
			assertDigestChanged(t, baseEvidence, changed, EvidenceDigest)
		})
	}
}

func assertDigestChanged[T any](
	t *testing.T,
	base T,
	changed T,
	digestFunction func(T) (string, error),
) {
	t.Helper()
	baseDigest, err := digestFunction(base)
	if err != nil {
		t.Fatalf("base digest: %v", err)
	}
	changedDigest, err := digestFunction(changed)
	if err != nil {
		t.Fatalf("changed digest: %v", err)
	}
	if changedDigest == baseDigest {
		t.Fatalf("changed value retained digest %q", baseDigest)
	}
}

func namespaceValue(namespace string, state NamespaceState) TenantNamespaceReservationV1 {
	return TenantNamespaceReservationV1{
		ValueVersion: NamespaceReservationValueVersion, TenantNamespaceID: namespace, State: state,
	}
}

func bindingValue(orgID, namespace string, state OrgBindingState) TenantOrgNamespaceBindingV1 {
	return TenantOrgNamespaceBindingV1{
		ValueVersion: OrgNamespaceBindingValueVersion,
		OrgID:        orgID, TenantNamespaceID: namespace, State: state,
	}
}

func tenantBundleKey(namespace, character string) BundleKey {
	return BundleKey{
		BundleKeyVersion:   layoutbundlekeyv1.BundleKeyVersion,
		SlotOwnerKind:      layoutbundlekeyv1.SlotOwnerKindTenant,
		SlotOwnerNamespace: namespace,
		LocalComponentKey:  componentKey(character),
	}
}

func slotValue(namespace string, state SlotState, character string) TenantLayoutBundleSlotV1 {
	return TenantLayoutBundleSlotV1{
		ValueVersion: LayoutBundleSlotValueVersion,
		BundleKey:    tenantBundleKey(namespace, character),
		State:        state,
	}
}

func namespaceReserveRequest(namespace string) TenantNamespaceCommandRequest {
	return TenantNamespaceCommandRequest{
		Operation:               NamespaceOperationReserve,
		TargetTenantNamespaceID: namespace,
		DesiredNamespaceValue:   namespaceValue(namespace, NamespaceStateActive),
		ExpectedPriorNamespace:  TenantNamespaceExpectedPrior{Variant: NamespaceExpectedPriorAbsent},
	}
}

func namespaceTombstoneRequest(t *testing.T, namespace string) TenantNamespaceCommandRequest {
	t.Helper()
	return TenantNamespaceCommandRequest{
		Operation:               NamespaceOperationTombstone,
		TargetTenantNamespaceID: namespace,
		DesiredNamespaceValue:   namespaceValue(namespace, NamespaceStateTombstone),
		ExpectedPriorNamespace: TenantNamespaceExpectedPrior{
			Variant:              NamespaceExpectedPriorExact,
			NamespaceValueDigest: mustNamespaceDigest(t, namespaceValue(namespace, NamespaceStateActive)),
		},
	}
}

func bindingBindRequest(t *testing.T, orgID, namespace string) TenantOrgNamespaceBindingCommandRequest {
	t.Helper()
	return TenantOrgNamespaceBindingCommandRequest{
		Operation:           BindingOperationBind,
		TargetOrgID:         orgID,
		DesiredBindingValue: bindingValue(orgID, namespace, OrgBindingStateActive),
		ExpectedNamespaceReservationDigest: mustNamespaceDigest(
			t,
			namespaceValue(namespace, NamespaceStateActive),
		),
		ExpectedPriorBinding: TenantOrgBindingExpectedPrior{Variant: BindingExpectedPriorAbsent},
	}
}

func bindingTombstoneRequest(t *testing.T, orgID, namespace string) TenantOrgNamespaceBindingCommandRequest {
	t.Helper()
	return TenantOrgNamespaceBindingCommandRequest{
		Operation:           BindingOperationTombstone,
		TargetOrgID:         orgID,
		DesiredBindingValue: bindingValue(orgID, namespace, OrgBindingStateTombstone),
		ExpectedNamespaceReservationDigest: mustNamespaceDigest(
			t,
			namespaceValue(namespace, NamespaceStateActive),
		),
		ExpectedPriorBinding: TenantOrgBindingExpectedPrior{
			Variant:            BindingExpectedPriorExact,
			BindingValueDigest: mustBindingDigest(t, bindingValue(orgID, namespace, OrgBindingStateActive)),
		},
	}
}

func slotCreateRequest(t *testing.T, orgID, namespace, character string) TenantLayoutBundleSlotCommandRequest {
	t.Helper()
	key := tenantBundleKey(namespace, character)
	return TenantLayoutBundleSlotCommandRequest{
		Operation:        SlotOperationCreate,
		AuthorizingOrgID: orgID,
		TargetBundleKey:  key,
		DesiredSlotValue: TenantLayoutBundleSlotV1{
			ValueVersion: LayoutBundleSlotValueVersion, BundleKey: key, State: SlotStateActive,
		},
		ExpectedNamespaceReservationDigest: mustNamespaceDigest(
			t,
			namespaceValue(namespace, NamespaceStateActive),
		),
		ExpectedAuthorizingOrgBindingDigest: mustBindingDigest(
			t,
			bindingValue(orgID, namespace, OrgBindingStateActive),
		),
		ExpectedPriorSlot: TenantSlotExpectedPrior{Variant: SlotExpectedPriorAbsent},
	}
}

func slotRetireRequest(t *testing.T, orgID, namespace, character string) TenantLayoutBundleSlotCommandRequest {
	t.Helper()
	key := tenantBundleKey(namespace, character)
	return TenantLayoutBundleSlotCommandRequest{
		Operation:        SlotOperationRetire,
		AuthorizingOrgID: orgID,
		TargetBundleKey:  key,
		DesiredSlotValue: TenantLayoutBundleSlotV1{
			ValueVersion: LayoutBundleSlotValueVersion, BundleKey: key, State: SlotStateTombstone,
		},
		ExpectedNamespaceReservationDigest: mustNamespaceDigest(
			t,
			namespaceValue(namespace, NamespaceStateActive),
		),
		ExpectedAuthorizingOrgBindingDigest: mustBindingDigest(
			t,
			bindingValue(orgID, namespace, OrgBindingStateActive),
		),
		ExpectedPriorSlot: TenantSlotExpectedPrior{
			Variant:         SlotExpectedPriorExact,
			SlotValueDigest: mustSlotDigest(t, slotValue(namespace, SlotStateActive, character)),
		},
	}
}

func validEvidence(orgID, namespace, character string) TenantLayoutBundleAuthorityEvidenceV1 {
	return TenantLayoutBundleAuthorityEvidenceV1{
		EvidenceVersion: EvidenceVersion,
		SourceCatalogSnapshot: catalogpublicationv1.CatalogSnapshotBinding{
			OrgID: orgID, CatalogRevision: 1, CatalogSnapshotDigest: digest("1"),
			SchemaGeneration: 2, EngineGeneration: 3,
		},
		BundleKey:                        tenantBundleKey(namespace, character),
		NamespaceReservationValueVersion: NamespaceReservationValueVersion,
		NamespaceReservationState:        NamespaceStateActive,
		OrgBindingValueVersion:           OrgNamespaceBindingValueVersion,
		OrgBindingState:                  OrgBindingStateActive,
		SlotValueVersion:                 LayoutBundleSlotValueVersion,
		SlotState:                        SlotStateActive,
	}
}

func componentKey(character string) layoutbundlekeyv1.LocalComponentKey {
	return layoutbundlekeyv1.LocalComponentKey("key256:" + strings.Repeat(character, 64))
}

func digest(character string) string {
	return "sha256:" + strings.Repeat(character, 64)
}

func mustNamespaceDigest(t *testing.T, value TenantNamespaceReservationV1) string {
	t.Helper()
	result, err := NamespaceValueDigest(value)
	if err != nil {
		t.Fatal(err)
	}
	return result
}

func mustBindingDigest(t *testing.T, value TenantOrgNamespaceBindingV1) string {
	t.Helper()
	result, err := OrgBindingValueDigest(value)
	if err != nil {
		t.Fatal(err)
	}
	return result
}

func mustSlotDigest(t *testing.T, value TenantLayoutBundleSlotV1) string {
	t.Helper()
	result, err := SlotValueDigest(value)
	if err != nil {
		t.Fatal(err)
	}
	return result
}

func assertInvalidNamespaceValue(t *testing.T, value TenantNamespaceReservationV1) {
	t.Helper()
	if err := ValidateNamespaceValue(value); err == nil {
		t.Fatal("ValidateNamespaceValue accepted invalid value")
	}
	if canonical, err := CanonicalNamespaceValueBytes(value); err == nil || canonical != nil {
		t.Fatalf("invalid namespace acquired canonical bytes %x, %v", canonical, err)
	}
	if result, err := NamespaceValueDigest(value); err == nil || result != "" {
		t.Fatalf("invalid namespace acquired digest %q, %v", result, err)
	}
}

func assertInvalidBindingValue(t *testing.T, value TenantOrgNamespaceBindingV1) {
	t.Helper()
	if err := ValidateOrgBindingValue(value); err == nil {
		t.Fatal("ValidateOrgBindingValue accepted invalid value")
	}
	if canonical, err := CanonicalOrgBindingValueBytes(value); err == nil || canonical != nil {
		t.Fatalf("invalid binding acquired canonical bytes %x, %v", canonical, err)
	}
	if result, err := OrgBindingValueDigest(value); err == nil || result != "" {
		t.Fatalf("invalid binding acquired digest %q, %v", result, err)
	}
}

func assertInvalidSlotValue(t *testing.T, value TenantLayoutBundleSlotV1) {
	t.Helper()
	if err := ValidateSlotValue(value); err == nil {
		t.Fatal("ValidateSlotValue accepted invalid value")
	}
	if canonical, err := CanonicalSlotValueBytes(value); err == nil || canonical != nil {
		t.Fatalf("invalid slot acquired canonical bytes %x, %v", canonical, err)
	}
	if result, err := SlotValueDigest(value); err == nil || result != "" {
		t.Fatalf("invalid slot acquired digest %q, %v", result, err)
	}
}

func assertInvalidNamespaceRequest(t *testing.T, request TenantNamespaceCommandRequest) {
	t.Helper()
	if err := ValidateNamespaceCommandRequest(request); err == nil {
		t.Fatal("ValidateNamespaceCommandRequest accepted invalid request")
	}
	if canonical, err := CanonicalNamespaceCommandRequestBytes(request); err == nil || canonical != nil {
		t.Fatalf("invalid namespace request acquired canonical bytes %x, %v", canonical, err)
	}
	if result, err := NamespaceCommandRequestDigest(request); err == nil || result != "" {
		t.Fatalf("invalid namespace request acquired digest %q, %v", result, err)
	}
}

func assertInvalidBindingRequest(t *testing.T, request TenantOrgNamespaceBindingCommandRequest) {
	t.Helper()
	if err := ValidateOrgBindingCommandRequest(request); err == nil {
		t.Fatal("ValidateOrgBindingCommandRequest accepted invalid request")
	}
	if canonical, err := CanonicalOrgBindingCommandRequestBytes(request); err == nil || canonical != nil {
		t.Fatalf("invalid binding request acquired canonical bytes %x, %v", canonical, err)
	}
	if result, err := OrgBindingCommandRequestDigest(request); err == nil || result != "" {
		t.Fatalf("invalid binding request acquired digest %q, %v", result, err)
	}
}

func assertInvalidSlotRequest(t *testing.T, request TenantLayoutBundleSlotCommandRequest) {
	t.Helper()
	if err := ValidateSlotCommandRequest(request); err == nil {
		t.Fatal("ValidateSlotCommandRequest accepted invalid request")
	}
	if canonical, err := CanonicalSlotCommandRequestBytes(request); err == nil || canonical != nil {
		t.Fatalf("invalid slot request acquired canonical bytes %x, %v", canonical, err)
	}
	if result, err := SlotCommandRequestDigest(request); err == nil || result != "" {
		t.Fatalf("invalid slot request acquired digest %q, %v", result, err)
	}
}

func assertInvalidEvidence(t *testing.T, value TenantLayoutBundleAuthorityEvidenceV1) {
	t.Helper()
	if err := ValidateEvidence(value); err == nil {
		t.Fatal("ValidateEvidence accepted invalid evidence")
	}
	if canonical, err := CanonicalEvidenceBytes(value); err == nil || canonical != nil {
		t.Fatalf("invalid evidence acquired canonical bytes %x, %v", canonical, err)
	}
	if result, err := EvidenceDigest(value); err == nil || result != "" {
		t.Fatalf("invalid evidence acquired digest %q, %v", result, err)
	}
}

func TestHelperFixturesRemainStructurallyDistinct(t *testing.T) {
	if reflect.TypeOf(namespaceReserveRequest(testTenantNamespace)) ==
		reflect.TypeOf(bindingBindRequest(t, "org-1", testTenantNamespace)) {
		t.Fatal("namespace and binding requests share a type")
	}
}
