package layoutvalidationviewv1

import (
	"bytes"
	"reflect"
	"sort"
	"strings"
	"testing"

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

func TestNormalizeFieldAPIName(t *testing.T) {
	for input, want := range map[string]string{
		"Name":          "name",
		"Account_ID__C": "account_id__c",
		"_PRIVATE9":     "_private9",
		strings.Repeat("A", MaxFieldAPINameBytes): strings.Repeat("a", MaxFieldAPINameBytes),
	} {
		got, err := NormalizeFieldAPIName(input)
		if err != nil {
			t.Fatalf("NormalizeFieldAPIName(%q): %v", input, err)
		}
		if got != want {
			t.Fatalf("NormalizeFieldAPIName(%q) = %q, want %q", input, got, want)
		}
		if err := ValidateFieldKey(fieldKey("account", got)); err != nil {
			t.Fatalf("normalized key rejected: %v", err)
		}
	}
	left, _ := NormalizeFieldAPIName("Name")
	right, _ := NormalizeFieldAPIName("name")
	if left != right {
		t.Fatalf("case variants do not collide: %q != %q", left, right)
	}
	for name, input := range map[string]string{
		"empty": "", "overlong": strings.Repeat("a", MaxFieldAPINameBytes+1),
		"digit": "9name", "hyphen": "account-id", "space": "account id",
		"unicode": "namé", "invalid UTF-8": string([]byte{0xff}),
	} {
		t.Run("reject "+name, func(t *testing.T) {
			if _, err := NormalizeFieldAPIName(input); err == nil {
				t.Fatalf("NormalizeFieldAPIName(%q) unexpectedly succeeded", input)
			}
		})
	}
	for _, key := range []FieldKey{
		fieldKey("account", "Name"), fieldKey("account", "9name"), fieldKey("account", "namé"),
	} {
		if err := ValidateFieldKey(key); err == nil {
			t.Fatalf("ValidateFieldKey(%#v) unexpectedly succeeded", key)
		}
	}
}

func TestValidateNamespacedContractKeyUsesTheFrozenLocalKeyGrammar(t *testing.T) {
	for _, localKey := range []string{
		"field",
		"field.text-render.v1",
		strings.Repeat("a", catalogassignmentidentityv1.MaxKeySegmentBytes),
	} {
		key := NamespacedContractKey{NamespaceID: "io.nexuscrm.ui", LocalKey: localKey}
		if err := ValidateNamespacedContractKey(key); err != nil {
			t.Fatalf("ValidateNamespacedContractKey(%q): %v", localKey, err)
		}
	}
	for _, localKey := range []string{
		"",
		"Field",
		"1field",
		"field-",
		"field..render",
		"field_render",
		"fiéld",
		strings.Repeat("a", catalogassignmentidentityv1.MaxKeySegmentBytes+1),
	} {
		key := NamespacedContractKey{NamespaceID: "io.nexuscrm.ui", LocalKey: localKey}
		if err := ValidateNamespacedContractKey(key); err == nil {
			t.Fatalf("ValidateNamespacedContractKey(%q) unexpectedly succeeded", localKey)
		}
	}
}

func TestValidateViewAcceptsCompleteAndExplicitlyEmptyValues(t *testing.T) {
	view, _, _ := validReferencedFixture(t)
	if err := ValidateView(view); err != nil {
		t.Fatalf("ValidateView(valid): %v", err)
	}

	empty := emptyView()
	if err := ValidateView(empty); err != nil {
		t.Fatalf("ValidateView(explicit empty): %v", err)
	}
}

func TestValidateViewRejectsEnvelopeArrayAndScalarFailures(t *testing.T) {
	for name, mutate := range map[string]func(*View){
		"canonical version":    func(view *View) { view.CanonicalizationVersion = "v2" },
		"key grammar":          func(view *View) { view.KeyGrammarVersion = "v2" },
		"object normalization": func(view *View) { view.ObjectKeyNormalizationVersion = "v2" },
		"field normalization":  func(view *View) { view.FieldKeyNormalizationVersion = "v2" },
		"source binding":       func(view *View) { view.SourceCatalogSnapshot.CatalogRevision = 0 },
		"assignment digest":    func(view *View) { view.CatalogAssignmentIdentityViewDigest = "sha256:BAD" },
		"required digest":      func(view *View) { view.RequiredBundleKeySetDigest = "sha256:bad" },
		"zero action version":  func(view *View) { view.Actions[0].ContractVersion = 0 },
		"bad renderer digest":  func(view *View) { view.RendererComponents[0].ContractDigest = "bad" },
		"bad format ID":        func(view *View) { view.AcceptedLayoutFormats[0].LayoutFormatVersion = "layout.format.record.v0" },
	} {
		t.Run(name, func(t *testing.T) {
			view, _, _ := validReferencedFixture(t)
			mutate(&view)
			if err := ValidateView(view); err == nil {
				t.Fatal("ValidateView unexpectedly succeeded")
			}
		})
	}

	for name, mutate := range map[string]func(*View){
		"objects":                   func(view *View) { view.Objects = nil },
		"fields":                    func(view *View) { view.Fields = nil },
		"relationships":             func(view *View) { view.Relationships = nil },
		"related lists":             func(view *View) { view.RelatedLists = nil },
		"actions":                   func(view *View) { view.Actions = nil },
		"renderers":                 func(view *View) { view.RendererComponents = nil },
		"formats":                   func(view *View) { view.AcceptedLayoutFormats = nil },
		"evidence":                  func(view *View) { view.BundleAuthorityEvidence = nil },
		"object capabilities":       func(view *View) { view.Objects[0].Capabilities = nil },
		"reference targets":         func(view *View) { view.Fields[0].ReferenceTargets = nil },
		"related-list capabilities": func(view *View) { view.RelatedLists[0].Capabilities = nil },
		"action capabilities":       func(view *View) { view.Actions[0].Capabilities = nil },
		"renderer capabilities":     func(view *View) { view.RendererComponents[0].Capabilities = nil },
	} {
		t.Run("nil "+name, func(t *testing.T) {
			view, _, _ := validReferencedFixture(t)
			mutate(&view)
			expectError(t, ValidateView(view), "must be present")
		})
	}
}

func TestValidateViewChecksEveryArrayCountBeforeMapsOrCanonicalBuffer(t *testing.T) {
	view, _, _ := validReferencedFixture(t)
	view.Objects = make([]ObjectContract, MaxEntriesPerArray+1)
	expectError(t, ValidateView(view), "View.Objects exceeds")

	view, _, _ = validReferencedFixture(t)
	view.Objects[0].Capabilities = make([]CapabilityID, MaxEntriesPerArray+1)
	expectError(t, ValidateView(view), "View.Objects[0].Capabilities exceeds")

	view, _, _ = validReferencedFixture(t)
	view.Fields[0].ReferenceTargets = make([]ObjectKey, MaxEntriesPerArray+1)
	expectError(t, ValidateView(view), "View.Fields[0].ReferenceTargets exceeds")

	view, _, _ = validReferencedFixture(t)
	view.RelatedLists[0].Capabilities = make([]CapabilityID, MaxEntriesPerArray+1)
	expectError(t, ValidateView(view), "View.RelatedLists[0].Capabilities exceeds")

	view, _, _ = validReferencedFixture(t)
	view.Actions[0].Capabilities = make([]CapabilityID, MaxEntriesPerArray+1)
	expectError(t, ValidateView(view), "View.Actions[0].Capabilities exceeds")

	view, _, _ = validReferencedFixture(t)
	view.RendererComponents[0].Capabilities = make([]CapabilityID, MaxEntriesPerArray+1)
	expectError(t, ValidateView(view), "View.RendererComponents[0].Capabilities exceeds")
}

func TestValidateViewRejectsDuplicateAndReferentialFailures(t *testing.T) {
	tests := []struct {
		name   string
		mutate func(*View)
		want   string
	}{
		{"duplicate object", func(view *View) { view.Objects = append(view.Objects, view.Objects[0]) }, "duplicate ObjectKey"},
		{"duplicate object capability", func(view *View) {
			view.Objects[0].Capabilities = append(view.Objects[0].Capabilities, view.Objects[0].Capabilities[0])
		}, "duplicate CapabilityID"},
		{"missing field owner", func(view *View) { view.Fields[0].Key.ObjectKey = objectKey("missing") }, "missing ObjectKey"},
		{"duplicate field", func(view *View) { view.Fields = append(view.Fields, view.Fields[0]) }, "duplicate FieldKey"},
		{"duplicate target", func(view *View) {
			view.Fields[0].ReferenceTargets = append(view.Fields[0].ReferenceTargets, view.Fields[0].ReferenceTargets[0])
		}, "duplicate ObjectKey"},
		{"missing relationship", func(view *View) { view.Relationships = view.Relationships[1:] }, "exactly cover"},
		{"relationship non-target", func(view *View) { view.Relationships[0].TargetObjectKey = objectKey("contact") }, "does not match"},
		{"duplicate relationship", func(view *View) { view.Relationships = append(view.Relationships, view.Relationships[0]) }, "duplicate reference-field/target"},
		{"master detail multi-target", func(view *View) {
			view.Fields[1].ReferenceTargets = append(view.Fields[1].ReferenceTargets, objectKey("detail"))
			view.Relationships = append(view.Relationships, RelationshipContract{ReferenceFieldKey: view.Fields[1].Key, TargetObjectKey: objectKey("detail"), Kind: RelationshipKindLookup})
		}, "MASTER_DETAIL requires exactly one"},
		{"bad relationship kind", func(view *View) { view.Relationships[0].Kind = "OTHER" }, "LOOKUP or MASTER_DETAIL"},
		{"related list wrong parent", func(view *View) { view.RelatedLists[0].ParentObjectKey = objectKey("contact") }, "present relationship"},
		{"duplicate related list", func(view *View) { view.RelatedLists = append(view.RelatedLists, view.RelatedLists[0]) }, "duplicate complete stable identity"},
		{"missing action source", func(view *View) { view.Actions[0].ObjectKey = objectKey("missing") }, "missing ObjectKey"},
		{"missing action target", func(view *View) { missing := objectKey("missing"); view.Actions[0].TargetObjectKey = &missing }, "missing TargetObjectKey"},
		{"duplicate action", func(view *View) { view.Actions = append(view.Actions, view.Actions[0]) }, "duplicate complete stable identity"},
		{"absent editable contract", func(view *View) { view.Fields[2].EditContractKey = nil }, "may be absent only"},
		{"missing renderer", func(view *View) { view.RendererComponents = view.RendererComponents[1:] }, "names no renderer"},
		{"duplicate renderer", func(view *View) {
			view.RendererComponents = append(view.RendererComponents, view.RendererComponents[0])
		}, "duplicate Key"},
		{"duplicate format", func(view *View) {
			view.AcceptedLayoutFormats = append(view.AcceptedLayoutFormats, view.AcceptedLayoutFormats[0])
		}, "duplicate LayoutFormatVersion"},
		{"duplicate evidence", func(view *View) {
			view.BundleAuthorityEvidence = append(view.BundleAuthorityEvidence, view.BundleAuthorityEvidence[0])
		}, "duplicate BundleKey"},
		{"wrong owner evidence version", func(view *View) { view.BundleAuthorityEvidence[0].EvidenceVersion = TenantAuthorityEvidenceVersion }, "EvidenceVersion must equal"},
	}
	for _, test := range tests {
		t.Run(test.name, func(t *testing.T) {
			view, _, _ := validReferencedFixture(t)
			test.mutate(&view)
			expectError(t, ValidateView(view), test.want)
		})
	}
}

func TestValidateViewEnforcesRelationshipShapeAndPolymorphicOwnership(t *testing.T) {
	tests := []struct {
		name   string
		mutate func(*View)
		want   string
	}{
		{
			name: "NONE with targets",
			mutate: func(view *View) {
				view.Fields[2].ReferenceTargets = []ObjectKey{objectKey("contact")}
			},
			want: "NONE requires no ReferenceTargets",
		},
		{
			name: "NONE with discriminator",
			mutate: func(view *View) {
				view.Fields[2].OwnsPolymorphicDiscriminator = true
			},
			want: "NONE cannot own",
		},
		{
			name: "LOOKUP without targets",
			mutate: func(view *View) {
				view.Fields[3].ReferenceTargets = []ObjectKey{}
			},
			want: "LOOKUP requires at least one",
		},
		{
			name: "multi-target LOOKUP without discriminator",
			mutate: func(view *View) {
				view.Fields[0].OwnsPolymorphicDiscriminator = false
			},
			want: "multi-target LOOKUP must own",
		},
		{
			name: "MASTER_DETAIL without targets",
			mutate: func(view *View) {
				view.Fields[1].ReferenceTargets = []ObjectKey{}
			},
			want: "MASTER_DETAIL requires exactly one",
		},
		{
			name: "multi-target MASTER_DETAIL",
			mutate: func(view *View) {
				view.Fields[1].ReferenceTargets = append(view.Fields[1].ReferenceTargets, objectKey("detail"))
			},
			want: "MASTER_DETAIL requires exactly one",
		},
		{
			name: "MASTER_DETAIL with discriminator",
			mutate: func(view *View) {
				view.Fields[1].OwnsPolymorphicDiscriminator = true
			},
			want: "MASTER_DETAIL cannot own",
		},
		{
			name: "relationship kind differs from field",
			mutate: func(view *View) {
				view.Relationships[0].Kind = RelationshipKindMasterDetail
			},
			want: "must equal its field RelationshipKind",
		},
		{
			name: "owned discriminator over physical-name limit",
			mutate: func(view *View) {
				name := strings.Repeat("p", MaxPolymorphicOwnerFieldAPINameBytes+1)
				view.Fields[3].Key.NormalizedFieldAPIName = name
				view.Relationships[3].ReferenceFieldKey.NormalizedFieldAPIName = name
			},
			want: "must be at most 59 bytes",
		},
	}
	for _, test := range tests {
		t.Run(test.name, func(t *testing.T) {
			view, _, _ := validReferencedFixture(t)
			test.mutate(&view)
			expectError(t, ValidateView(view), test.want)
		})
	}
}

func TestValidateViewAcceptsFieldNameAndPolymorphicBoundaries(t *testing.T) {
	t.Run("ordinary non-owner 64 bytes", func(t *testing.T) {
		view, _, _ := validReferencedFixture(t)
		view.Fields[2].Key.NormalizedFieldAPIName = strings.Repeat("n", MaxFieldAPINameBytes)
		if err := ValidateView(view); err != nil {
			t.Fatalf("ValidateView: %v", err)
		}
	})
	t.Run("polymorphic owner 59 bytes", func(t *testing.T) {
		view, _, _ := validReferencedFixture(t)
		name := strings.Repeat("p", MaxPolymorphicOwnerFieldAPINameBytes)
		view.Fields[3].Key.NormalizedFieldAPIName = name
		view.Relationships[3].ReferenceFieldKey.NormalizedFieldAPIName = name
		if err := ValidateView(view); err != nil {
			t.Fatalf("ValidateView: %v", err)
		}
	})
	t.Run("single-target transitional polymorphic lookup", func(t *testing.T) {
		view, _, _ := validReferencedFixture(t)
		if !view.Fields[3].OwnsPolymorphicDiscriminator || len(view.Fields[3].ReferenceTargets) != 1 {
			t.Fatal("fixture does not contain a single-target polymorphic lookup")
		}
		if err := ValidateView(view); err != nil {
			t.Fatalf("ValidateView: %v", err)
		}
	})
	t.Run("multi-target polymorphic lookup", func(t *testing.T) {
		view, _, _ := validReferencedFixture(t)
		if !view.Fields[0].OwnsPolymorphicDiscriminator || len(view.Fields[0].ReferenceTargets) < 2 {
			t.Fatal("fixture does not contain a multi-target polymorphic lookup")
		}
		if err := ValidateView(view); err != nil {
			t.Fatalf("ValidateView: %v", err)
		}
	})
}

func TestViewDigestCommitsToFieldRelationshipShape(t *testing.T) {
	view, _, _ := validReferencedFixture(t)
	baseline, err := ViewDigest(view)
	if err != nil {
		t.Fatal(err)
	}

	withoutOwnedDiscriminator := view
	withoutOwnedDiscriminator.Fields = append([]FieldContract{}, view.Fields...)
	withoutOwnedDiscriminator.Fields[3].OwnsPolymorphicDiscriminator = false
	withoutOwnedDigest, err := ViewDigest(withoutOwnedDiscriminator)
	if err != nil {
		t.Fatal(err)
	}
	if baseline == withoutOwnedDigest {
		t.Fatal("OwnsPolymorphicDiscriminator did not change ViewDigest")
	}

	masterDetail := withoutOwnedDiscriminator
	masterDetail.Fields = append([]FieldContract{}, withoutOwnedDiscriminator.Fields...)
	masterDetail.Relationships = append([]RelationshipContract{}, withoutOwnedDiscriminator.Relationships...)
	masterDetail.Fields[3].RelationshipKind = RelationshipKindMasterDetail
	masterDetail.Relationships[3].Kind = RelationshipKindMasterDetail
	masterDetailDigest, err := ViewDigest(masterDetail)
	if err != nil {
		t.Fatal(err)
	}
	if withoutOwnedDigest == masterDetailDigest {
		t.Fatal("RelationshipKind did not change ViewDigest")
	}
}

func TestCanonicalizationAcceptsPermutationsAndDefensivelyCopies(t *testing.T) {
	view, _, _ := validReferencedFixture(t)
	firstBytes, err := CanonicalViewBytes(view)
	if err != nil {
		t.Fatal(err)
	}
	firstDigest, err := ViewDigest(view)
	if err != nil {
		t.Fatal(err)
	}

	permuted := deepCopyView(t, view)
	reverse(permuted.Objects)
	reverse(permuted.Fields)
	reverse(permuted.Relationships)
	reverse(permuted.RelatedLists)
	reverse(permuted.Actions)
	reverse(permuted.RendererComponents)
	reverse(permuted.AcceptedLayoutFormats)
	reverse(permuted.BundleAuthorityEvidence)
	for index := range permuted.Objects {
		reverse(permuted.Objects[index].Capabilities)
	}
	for index := range permuted.Fields {
		reverse(permuted.Fields[index].ReferenceTargets)
	}
	for index := range permuted.RelatedLists {
		reverse(permuted.RelatedLists[index].Capabilities)
	}
	for index := range permuted.Actions {
		reverse(permuted.Actions[index].Capabilities)
	}
	for index := range permuted.RendererComponents {
		reverse(permuted.RendererComponents[index].Capabilities)
	}
	secondBytes, err := CanonicalViewBytes(permuted)
	if err != nil {
		t.Fatal(err)
	}
	secondDigest, err := ViewDigest(permuted)
	if err != nil {
		t.Fatal(err)
	}
	if !bytes.Equal(firstBytes, secondBytes) || firstDigest != secondDigest {
		t.Fatal("input permutation changed canonical identity")
	}

	canonical, err := CanonicalizeView(view)
	if err != nil {
		t.Fatal(err)
	}
	before := canonical.Objects[2].Capabilities[0]
	view.Objects[2].Capabilities[0] = "changed.invalid.v1"
	if canonical.Objects[2].Capabilities[0] != before {
		t.Fatal("canonical nested slice aliases input storage")
	}
	if !sort.SliceIsSorted(canonical.BundleAuthorityEvidence, func(left, right int) bool {
		return bundleKeyLess(canonical.BundleAuthorityEvidence[left].BundleKey, canonical.BundleAuthorityEvidence[right].BundleKey)
	}) {
		t.Fatal("evidence is not in canonical BundleKey order")
	}
	gotFormats := make([]LayoutFormatVersion, len(canonical.AcceptedLayoutFormats))
	for index, format := range canonical.AcceptedLayoutFormats {
		gotFormats[index] = format.LayoutFormatVersion
	}
	wantFormats := []LayoutFormatVersion{"layout.format.record.v1", "layout.format.record.v10", "layout.format.record.v2"}
	if !reflect.DeepEqual(gotFormats, wantFormats) {
		t.Fatalf("layout format raw-byte order = %v, want %v", gotFormats, wantFormats)
	}
}

func TestCanonicalSizeAndAggregateBoundary(t *testing.T) {
	view, _, _ := validReferencedFixture(t)
	size, err := CanonicalViewSize(view)
	if err != nil {
		t.Fatal(err)
	}
	canonical, err := CanonicalViewBytes(view)
	if err != nil {
		t.Fatal(err)
	}
	if uint64(len(canonical)) != size {
		t.Fatalf("CanonicalViewSize = %d, bytes = %d", size, len(canonical))
	}
	if measured, err := measureCanonicalView(view, size); err != nil || measured != size {
		t.Fatalf("measure at exact boundary = %d, %v", measured, err)
	}
	if _, err := measureCanonicalView(view, size-1); err == nil {
		t.Fatal("measure below exact aggregate size unexpectedly succeeded")
	}
	measure := canonicalMeasure{limit: MaxCanonicalViewBytes}
	measure.add(MaxCanonicalViewBytes)
	if measure.exceeded || measure.size != MaxCanonicalViewBytes {
		t.Fatal("exact 128 MiB boundary was rejected")
	}
	measure.add(1)
	if !measure.exceeded {
		t.Fatal("128 MiB + 1 boundary was accepted")
	}
}

func TestVerifyReferencedArtifacts(t *testing.T) {
	view, assignment, required := validReferencedFixture(t)
	if err := VerifyReferencedArtifacts(view, assignment, required); err != nil {
		t.Fatalf("VerifyReferencedArtifacts(valid): %v", err)
	}

	t.Run("assignment digest mismatch", func(t *testing.T) {
		bad := view
		bad.CatalogAssignmentIdentityViewDigest = digestOf('9')
		expectError(t, VerifyReferencedArtifacts(bad, assignment, required), "digest does not match")
	})
	t.Run("assignment source mismatch", func(t *testing.T) {
		badAssignment := assignment
		badAssignment.SourceCatalogSnapshot.CatalogRevision++
		bad := view
		bad.CatalogAssignmentIdentityViewDigest = mustAssignmentDigest(t, badAssignment)
		expectError(t, VerifyReferencedArtifacts(bad, badAssignment, required), "does not exactly match")
	})
	t.Run("object tombstoned", func(t *testing.T) {
		badAssignment := assignment
		badAssignment.Objects = append([]catalogassignmentidentityv1.ObjectIdentity{}, assignment.Objects...)
		badAssignment.Objects[0].State = catalogassignmentidentityv1.IdentityStateTombstone
		bad := view
		bad.CatalogAssignmentIdentityViewDigest = mustAssignmentDigest(t, badAssignment)
		expectError(t, VerifyReferencedArtifacts(bad, badAssignment, required), "ACTIVE assignment-view object")
	})
	t.Run("active assignment object omitted from validation view", func(t *testing.T) {
		badAssignment := assignment
		badAssignment.Objects = append(
			append([]catalogassignmentidentityv1.ObjectIdentity{}, assignment.Objects...),
			catalogassignmentidentityv1.ObjectIdentity{
				Key: objectKey("opportunity"), ResolvedObjectID: "object-opportunity",
				State: catalogassignmentidentityv1.IdentityStateActive,
			},
		)
		bad := view
		bad.CatalogAssignmentIdentityViewDigest = mustAssignmentDigest(t, badAssignment)
		expectError(t, VerifyReferencedArtifacts(bad, badAssignment, required), "exactly cover every ACTIVE")
	})
	t.Run("tombstoned assignment object may be absent from validation view", func(t *testing.T) {
		withTombstone := assignment
		withTombstone.Objects = append(
			append([]catalogassignmentidentityv1.ObjectIdentity{}, assignment.Objects...),
			catalogassignmentidentityv1.ObjectIdentity{
				Key: objectKey("opportunity"), ResolvedObjectID: "object-opportunity",
				State: catalogassignmentidentityv1.IdentityStateTombstone,
			},
		)
		withTombstoneView := view
		withTombstoneView.CatalogAssignmentIdentityViewDigest = mustAssignmentDigest(t, withTombstone)
		if err := VerifyReferencedArtifacts(withTombstoneView, withTombstone, required); err != nil {
			t.Fatalf("VerifyReferencedArtifacts(with tombstone): %v", err)
		}
	})
	t.Run("required digest mismatch", func(t *testing.T) {
		bad := view
		bad.RequiredBundleKeySetDigest = digestOf('8')
		expectError(t, VerifyReferencedArtifacts(bad, assignment, required), "digest does not match")
	})
	t.Run("required evidence set mismatch", func(t *testing.T) {
		badRequired := required
		badRequired.BundleKeys = append([]layoutbundlekeyv1.BundleKey{}, required.BundleKeys[1:]...)
		bad := view
		bad.RequiredBundleKeySetDigest = mustRequiredDigest(t, badRequired)
		expectError(t, VerifyReferencedArtifacts(bad, assignment, badRequired), "does not exactly equal")
	})
}

func validReferencedFixture(t *testing.T) (View, catalogassignmentidentityv1.View, layoutbundlekeyv1.RequiredBundleKeySet) {
	t.Helper()
	source := testSourceBinding()
	assignment := catalogassignmentidentityv1.View{
		SourceCatalogSnapshot:         source,
		CanonicalizationVersion:       catalogassignmentidentityv1.CanonicalizationVersion,
		KeyGrammarVersion:             catalogassignmentidentityv1.KeyGrammarVersion,
		ObjectKeyNormalizationVersion: catalogassignmentidentityv1.ObjectKeyNormalizationVersion,
		Objects: []catalogassignmentidentityv1.ObjectIdentity{
			{Key: objectKey("detail"), ResolvedObjectID: "object-detail", State: catalogassignmentidentityv1.IdentityStateActive},
			{Key: objectKey("account"), ResolvedObjectID: "object-account", State: catalogassignmentidentityv1.IdentityStateActive},
			{Key: objectKey("contact"), ResolvedObjectID: "object-contact", State: catalogassignmentidentityv1.IdentityStateActive},
		},
		RecordTypes: []catalogassignmentidentityv1.RecordTypeIdentity{},
		ProductApps: []catalogassignmentidentityv1.ProductAppIdentity{{
			Key:   catalogassignmentidentityv1.ProductAppKey{AppID: "com.example.sales"},
			State: catalogassignmentidentityv1.IdentityStateActive,
			ResolvedTarget: catalogassignmentidentityv1.ProductAppResolvedTarget{
				Kind:                      catalogassignmentidentityv1.ProductAppResolvedTargetKindAppInstallationID,
				ResolvedAppInstallationID: "0190d800-0000-7000-8000-000000000001",
			},
		}},
		NavigationApps: []catalogassignmentidentityv1.NavigationAppIdentity{},
	}
	appKey := bundleKey(layoutbundlekeyv1.SlotOwnerKindApp, "com.example.sales", '1')
	tenantKey := bundleKey(layoutbundlekeyv1.SlotOwnerKindTenant, "org.pingcap", '2')
	systemKey := bundleKey(layoutbundlekeyv1.SlotOwnerKindSystem, layoutbundlekeyv1.SystemSlotOwnerNamespace, '3')
	required := layoutbundlekeyv1.RequiredBundleKeySet{
		Version:    layoutbundlekeyv1.RequiredBundleKeySetVersion,
		BundleKeys: []layoutbundlekeyv1.BundleKey{systemKey, appKey, tenantKey},
	}
	editKey := NamespacedContractKey{NamespaceID: "io.nexuscrm.ui", LocalKey: "field.text-edit.v1"}
	renderKey := NamespacedContractKey{NamespaceID: "io.nexuscrm.ui", LocalKey: "field.text-render.v1"}
	view := View{
		CanonicalizationVersion:             CanonicalizationVersion,
		KeyGrammarVersion:                   KeyGrammarVersion,
		ObjectKeyNormalizationVersion:       ObjectKeyNormalizationVersion,
		FieldKeyNormalizationVersion:        FieldKeyNormalizationVersion,
		SourceCatalogSnapshot:               source,
		CatalogAssignmentIdentityViewDigest: mustAssignmentDigest(t, assignment),
		RequiredBundleKeySetDigest:          mustRequiredDigest(t, required),
		Objects: []ObjectContract{
			{Key: objectKey("detail"), Capabilities: []CapabilityID{"runtime.object.write.v1", "runtime.object.read.v1"}},
			{Key: objectKey("account"), Capabilities: []CapabilityID{}},
			{Key: objectKey("contact"), Capabilities: []CapabilityID{"runtime.object.read.v1"}},
		},
		Fields: []FieldContract{
			{
				Key: fieldKey("contact", "parent_id"), DataTypeID: "catalog.data-type.reference.v1",
				RelationshipKind: RelationshipKindLookup, OwnsPolymorphicDiscriminator: true,
				RequiredOnCreate: false, Nillable: true, DefaultedOnCreate: true,
				Createable: false, Updateable: false, EditContractKey: nil, RenderContractKey: renderKey,
				ReferenceTargets: []ObjectKey{objectKey("detail"), objectKey("account")},
			},
			{
				Key: fieldKey("detail", "account_id"), DataTypeID: "catalog.data-type.reference.v1",
				RelationshipKind: RelationshipKindMasterDetail, OwnsPolymorphicDiscriminator: false,
				RequiredOnCreate: true, Nillable: false, DefaultedOnCreate: false,
				Createable: false, Updateable: false, EditContractKey: nil, RenderContractKey: renderKey,
				ReferenceTargets: []ObjectKey{objectKey("account")},
			},
			{
				Key: fieldKey("account", "name"), DataTypeID: "catalog.data-type.string.v1",
				RelationshipKind: RelationshipKindNone, OwnsPolymorphicDiscriminator: false,
				RequiredOnCreate: true, Nillable: false, DefaultedOnCreate: false,
				Createable: true, Updateable: true, EditContractKey: &editKey, RenderContractKey: renderKey,
				ReferenceTargets: []ObjectKey{},
			},
			{
				Key: fieldKey("account", "parent_id"), DataTypeID: "catalog.data-type.reference.v1",
				RelationshipKind: RelationshipKindLookup, OwnsPolymorphicDiscriminator: true,
				RequiredOnCreate: false, Nillable: true, DefaultedOnCreate: false,
				Createable: false, Updateable: false, EditContractKey: nil, RenderContractKey: renderKey,
				ReferenceTargets: []ObjectKey{objectKey("contact")},
			},
		},
		Relationships: []RelationshipContract{
			{ReferenceFieldKey: fieldKey("contact", "parent_id"), TargetObjectKey: objectKey("detail"), Kind: RelationshipKindLookup},
			{ReferenceFieldKey: fieldKey("detail", "account_id"), TargetObjectKey: objectKey("account"), Kind: RelationshipKindMasterDetail},
			{ReferenceFieldKey: fieldKey("contact", "parent_id"), TargetObjectKey: objectKey("account"), Kind: RelationshipKindLookup},
			{ReferenceFieldKey: fieldKey("account", "parent_id"), TargetObjectKey: objectKey("contact"), Kind: RelationshipKindLookup},
		},
		RelatedLists: []RelatedListContract{
			{ParentObjectKey: objectKey("detail"), ReferenceFieldKey: fieldKey("contact", "parent_id"), Capabilities: []CapabilityID{}},
			{
				ParentObjectKey:   objectKey("account"),
				ReferenceFieldKey: fieldKey("detail", "account_id"),
				Capabilities:      []CapabilityID{"runtime.related-list.open.v1", "runtime.related-list.inline.v1"},
			},
		},
		Actions: []ActionContract{
			{
				ObjectKey: objectKey("contact"), Key: NamespacedContractKey{NamespaceID: "com.example.sales", LocalKey: "action.open-parent.v1"},
				ActionKindID: "layout.action.navigate.v1", TargetObjectKey: nil,
				ContractVersion: 128, ContractDigest: digestOf('4'), Capabilities: []CapabilityID{},
			},
			{
				ObjectKey: objectKey("account"), Key: NamespacedContractKey{NamespaceID: "com.example.sales", LocalKey: "action.new-contact.v1"},
				ActionKindID: "layout.action.create.v1", TargetObjectKey: objectKeyPointer("contact"),
				ContractVersion: 1, ContractDigest: digestOf('5'),
				Capabilities: []CapabilityID{"runtime.action.invoke.v1", "runtime.action.audit.v1"},
			},
		},
		RendererComponents: []RendererComponentContract{
			{
				Key: renderKey, ContractVersion: 16384, ContractDigest: digestOf('6'),
				Capabilities: []CapabilityID{"runtime.renderer.text.v1", "runtime.renderer.base.v1"},
			},
			{Key: editKey, ContractVersion: 127, ContractDigest: digestOf('7'), Capabilities: []CapabilityID{}},
		},
		AcceptedLayoutFormats: []LayoutFormatContract{
			{LayoutFormatVersion: "layout.format.record.v2", ContractDigest: digestOf('9')},
			{LayoutFormatVersion: "layout.format.record.v10", ContractDigest: digestOf('d')},
			{LayoutFormatVersion: "layout.format.record.v1", ContractDigest: digestOf('8')},
		},
		BundleAuthorityEvidence: []BundleAuthorityEvidenceRef{
			{BundleKey: systemKey, EvidenceVersion: SystemAuthorityEvidenceVersion, EvidenceDigest: digestOf('c')},
			{BundleKey: tenantKey, EvidenceVersion: TenantAuthorityEvidenceVersion, EvidenceDigest: digestOf('b')},
			{BundleKey: appKey, EvidenceVersion: AppAuthorityEvidenceVersion, EvidenceDigest: digestOf('a')},
		},
	}
	return view, assignment, required
}

func emptyView() View {
	return View{
		CanonicalizationVersion:             CanonicalizationVersion,
		KeyGrammarVersion:                   KeyGrammarVersion,
		ObjectKeyNormalizationVersion:       ObjectKeyNormalizationVersion,
		FieldKeyNormalizationVersion:        FieldKeyNormalizationVersion,
		SourceCatalogSnapshot:               testSourceBinding(),
		CatalogAssignmentIdentityViewDigest: digestOf('1'),
		RequiredBundleKeySetDigest:          digestOf('2'),
		Objects:                             []ObjectContract{}, Fields: []FieldContract{}, Relationships: []RelationshipContract{},
		RelatedLists: []RelatedListContract{}, Actions: []ActionContract{}, RendererComponents: []RendererComponentContract{},
		AcceptedLayoutFormats: []LayoutFormatContract{}, BundleAuthorityEvidence: []BundleAuthorityEvidenceRef{},
	}
}

func testSourceBinding() catalogpublicationv1.CatalogSnapshotBinding {
	return catalogpublicationv1.CatalogSnapshotBinding{
		OrgID: strings.Repeat("o", 128), CatalogRevision: 128, CatalogSnapshotDigest: digestOf('0'),
		SchemaGeneration: 16384, EngineGeneration: 127,
	}
}

func objectKey(name string) ObjectKey { return ObjectKey{NormalizedAPIName: name} }

func objectKeyPointer(name string) *ObjectKey {
	value := objectKey(name)
	return &value
}

func fieldKey(object, field string) FieldKey {
	return FieldKey{ObjectKey: objectKey(object), NormalizedFieldAPIName: field}
}

func bundleKey(kind layoutbundlekeyv1.SlotOwnerKind, namespace string, hex byte) layoutbundlekeyv1.BundleKey {
	return layoutbundlekeyv1.BundleKey{
		SlotOwnerKind: kind, SlotOwnerNamespace: namespace,
		LocalComponentKey: layoutbundlekeyv1.LocalComponentKey("key256:" + strings.Repeat(string(hex), 64)),
	}
}

func digestOf(hex byte) string { return "sha256:" + strings.Repeat(string(hex), 64) }

func mustAssignmentDigest(t *testing.T, view catalogassignmentidentityv1.View) string {
	t.Helper()
	digest, err := catalogassignmentidentityv1.ViewDigest(view)
	if err != nil {
		t.Fatal(err)
	}
	return digest
}

func mustRequiredDigest(t *testing.T, set layoutbundlekeyv1.RequiredBundleKeySet) string {
	t.Helper()
	digest, err := layoutbundlekeyv1.RequiredBundleKeySetDigest(set)
	if err != nil {
		t.Fatal(err)
	}
	return digest
}

func expectError(t *testing.T, err error, want string) {
	t.Helper()
	if err == nil || !strings.Contains(err.Error(), want) {
		t.Fatalf("error = %v, want substring %q", err, want)
	}
}

func reverse[T any](values []T) {
	for left, right := 0, len(values)-1; left < right; left, right = left+1, right-1 {
		values[left], values[right] = values[right], values[left]
	}
}

func deepCopyView(t *testing.T, view View) View {
	t.Helper()
	canonical, err := CanonicalizeView(view)
	if err != nil {
		t.Fatal(err)
	}
	return canonical
}

func TestFixtureCoversEveryBooleanAndOptionalState(t *testing.T) {
	view, _, _ := validReferencedFixture(t)
	bools := [][6]bool{}
	editAbsent, editPresent := false, false
	actionTargetAbsent, actionTargetPresent := false, false
	for _, field := range view.Fields {
		bools = append(bools, [6]bool{
			field.OwnsPolymorphicDiscriminator,
			field.RequiredOnCreate,
			field.Nillable,
			field.DefaultedOnCreate,
			field.Createable,
			field.Updateable,
		})
		editAbsent = editAbsent || field.EditContractKey == nil
		editPresent = editPresent || field.EditContractKey != nil
	}
	for _, action := range view.Actions {
		actionTargetAbsent = actionTargetAbsent || action.TargetObjectKey == nil
		actionTargetPresent = actionTargetPresent || action.TargetObjectKey != nil
	}
	if !editAbsent || !editPresent || !actionTargetAbsent || !actionTargetPresent {
		t.Fatal("fixture does not cover both optional states")
	}
	for column := 0; column < 6; column++ {
		seenFalse, seenTrue := false, false
		for _, values := range bools {
			seenFalse = seenFalse || !values[column]
			seenTrue = seenTrue || values[column]
		}
		if !seenFalse || !seenTrue {
			t.Fatalf("boolean column %d does not cover both values", column)
		}
	}
}

func TestCanonicalizeViewDoesNotMutateInput(t *testing.T) {
	view, _, _ := validReferencedFixture(t)
	before := deepCopyView(t, view)
	_, err := CanonicalizeView(view)
	if err != nil {
		t.Fatal(err)
	}
	if !reflect.DeepEqual(view, before) {
		// before is canonical, while the fixture is intentionally permuted; compare
		// its canonical identity to avoid hiding an in-place mutation.
		beforeBytes, _ := CanonicalViewBytes(before)
		afterBytes, _ := CanonicalViewBytes(view)
		if !bytes.Equal(beforeBytes, afterBytes) {
			t.Fatal("CanonicalizeView changed input")
		}
	}
}
