package connectorbindingownerv1

import (
	"bytes"
	"encoding/json"
	"fmt"
	"reflect"
	"strings"
	"testing"

	catalogv1 "github.com/pingcap-inc/nexuscrm-contract/pkg/catalog/consumerv1"
)

func TestDefinitionSetPublicSurfaceAndGoldenCanonicalContract(t *testing.T) {
	assertExactDefinitionSetFields(t, reflect.TypeOf(DefinitionSetCoordinate{}), []string{
		"OwnerPack", "PackInstallationID", "PackPayloadDigest",
	})
	assertExactDefinitionSetFields(t, reflect.TypeOf(DefinitionSetHeader{}), []string{
		"Protocol", "DefinitionSetCoordinate", "DefinitionCount", "OrderedSetDigest",
	})
	assertExactDefinitionSetFields(t, reflect.TypeOf(DefinitionSetEntry{}), []string{
		"Ordinal", "Kind", "OwnerPack", "PackInstallationID", "PackPayloadDigest",
		"BusinessKey", "DefinitionVersion", "EnvelopeJSON", "EnvelopeDigest",
	})
	assertExactDefinitionSetFields(t, reflect.TypeOf(DefinitionSet{}), []string{"Header", "Entries"})
	if DefinitionSetProtocolV1 != "nexuscrm.connector-binding-owner-definition-set/v1" {
		t.Fatalf("DefinitionSetProtocolV1 = %q", DefinitionSetProtocolV1)
	}
	if MaxDefinitionCount != 4096 {
		t.Fatalf("MaxDefinitionCount = %d", MaxDefinitionCount)
	}
	if MaxDefinitionSetBytes != 64<<20 {
		t.Fatalf("MaxDefinitionSetBytes = %d", MaxDefinitionSetBytes)
	}

	coordinate := validDefinitionSetCoordinate()
	input := []DefinitionEnvelope{
		newDefinitionSetEnvelope(t, coordinate, catalogv1.BindingConnectorPushContract, "connector-a/push", 1, "push"),
		newDefinitionSetEnvelope(t, coordinate, catalogv1.BindingConnectorInboundRoute, "connector-a/event.created", 7, "route"),
		newDefinitionSetEnvelope(t, coordinate, catalogv1.BindingConnectorFieldMapping, "connector-a/contact", 1, "mapping"),
		newDefinitionSetEnvelope(t, coordinate, catalogv1.BindingConnectorCalloutProfile, "connector-a/default", 1, "profile"),
	}
	set, err := NewDefinitionSet(coordinate, input)
	if err != nil {
		t.Fatal(err)
	}
	if set.Header.Protocol != DefinitionSetProtocolV1 || set.Header.DefinitionCount != 4 ||
		set.Header.DefinitionSetCoordinate != coordinate {
		t.Fatalf("header = %#v", set.Header)
	}
	if set.Header.OrderedSetDigest != "sha256:40666827b61b53fb24f401b2acb4a728f996bdb5b30305dce01c05cb89e07f67" {
		t.Fatalf("non-empty ordered-set digest = %q", set.Header.OrderedSetDigest)
	}

	wantKinds := []catalogv1.IntegrationBindingKind{
		catalogv1.BindingConnectorCalloutProfile,
		catalogv1.BindingConnectorFieldMapping,
		catalogv1.BindingConnectorInboundRoute,
		catalogv1.BindingConnectorPushContract,
	}
	validated, err := ValidateDefinitionSet(set)
	if err != nil {
		t.Fatal(err)
	}
	for index, definition := range validated {
		entry := set.Entries[index]
		if entry.Ordinal != uint64(index) || definition.Kind != wantKinds[index] ||
			entry.Kind != definition.Kind || entry.OwnerPack != definition.OwnerPack ||
			entry.PackInstallationID != definition.PackInstallationID ||
			entry.PackPayloadDigest != definition.PackPayloadDigest ||
			entry.BusinessKey != definition.BusinessKey ||
			entry.DefinitionVersion != definition.DefinitionVersion {
			t.Fatalf("entry[%d] = %#v; definition = %#v", index, entry, definition.Identity)
		}
		wantEnvelopeDigest, err := DefinitionEnvelopeDigest(definition)
		if err != nil {
			t.Fatal(err)
		}
		if entry.EnvelopeDigest != wantEnvelopeDigest {
			t.Fatalf("entry[%d] envelope digest = %q, want %q", index, entry.EnvelopeDigest, wantEnvelopeDigest)
		}
	}
}

func TestDefinitionSetCanonicalOrderUsesKindThenBusinessKeyBytes(t *testing.T) {
	coordinate := validDefinitionSetCoordinate()
	keys := []string{
		"connector-a/中",
		"connector-a/a",
		"connector-a/é",
		"connector-a/Z",
		"connector-a/A",
	}
	definitions := make([]DefinitionEnvelope, len(keys))
	for index, key := range keys {
		definitions[index] = newDefinitionSetEnvelope(
			t,
			coordinate,
			catalogv1.BindingConnectorFieldMapping,
			key,
			1,
			fmt.Sprintf("mapping-%d", index),
		)
	}

	set, err := NewDefinitionSet(coordinate, definitions)
	if err != nil {
		t.Fatal(err)
	}
	want := []string{
		"connector-a/A",
		"connector-a/Z",
		"connector-a/a",
		"connector-a/é",
		"connector-a/中",
	}
	for index, entry := range set.Entries {
		if entry.Kind != catalogv1.BindingConnectorFieldMapping || entry.BusinessKey != want[index] {
			t.Fatalf("entry[%d] identity = %s/%q, want %s/%q", index, entry.Kind, entry.BusinessKey, catalogv1.BindingConnectorFieldMapping, want[index])
		}
	}
	if _, err := ValidateDefinitionSet(set); err != nil {
		t.Fatalf("byte-ordered set failed validation: %v", err)
	}
}

func TestDefinitionSetCanonicalEmptyPublication(t *testing.T) {
	coordinate := validDefinitionSetCoordinate()
	fromNil, err := NewDefinitionSet(coordinate, nil)
	if err != nil {
		t.Fatal(err)
	}
	fromEmpty, err := NewDefinitionSet(coordinate, []DefinitionEnvelope{})
	if err != nil {
		t.Fatal(err)
	}
	if !reflect.DeepEqual(fromNil, fromEmpty) {
		t.Fatalf("nil and explicit empty inputs differ:\n%#v\n%#v", fromNil, fromEmpty)
	}
	if fromNil.Header.DefinitionCount != 0 || len(fromNil.Entries) != 0 || fromNil.Entries == nil {
		t.Fatalf("canonical empty set = %#v", fromNil)
	}
	if fromNil.Header.OrderedSetDigest != "sha256:de2bbddd4acb623aa9ecdd1cbe0686757c420298f04870db0a20c932d182d14a" {
		t.Fatalf("empty ordered-set digest = %q", fromNil.Header.OrderedSetDigest)
	}
	validated, err := ValidateDefinitionSet(fromNil)
	if err != nil {
		t.Fatal(err)
	}
	if validated == nil || len(validated) != 0 {
		t.Fatalf("validated empty definitions = %#v", validated)
	}

	changedParent := fromNil
	changedParent.Header.OwnerPack = "connector-b"
	if _, err := ValidateDefinitionSet(changedParent); err == nil ||
		!strings.Contains(err.Error(), "OrderedSetDigest") {
		t.Fatalf("changed empty-set parent error = %v", err)
	}
	if _, err := ValidateDefinitionSet(DefinitionSet{}); err == nil {
		t.Fatal("missing authority unexpectedly validated as an explicit empty set")
	}
}

func TestDefinitionSetDeepCopiesEveryMutableBoundary(t *testing.T) {
	coordinate := validDefinitionSetCoordinate()
	push := newDefinitionSetEnvelope(
		t, coordinate, catalogv1.BindingConnectorPushContract, "connector-a/push", 1, "push",
	)
	route := newDefinitionSetEnvelope(
		t, coordinate, catalogv1.BindingConnectorInboundRoute, "connector-a/event", 9, "route",
	)
	input := []DefinitionEnvelope{push, route}
	set, err := NewDefinitionSet(coordinate, input)
	if err != nil {
		t.Fatal(err)
	}

	input[0].CanonicalDefinition[0] ^= 0xff
	input[0].OwnerReceipt[0] ^= 0xff
	if _, err := ValidateDefinitionSet(set); err != nil {
		t.Fatalf("constructed set retained input bytes: %v", err)
	}

	validated, err := ValidateDefinitionSet(set)
	if err != nil {
		t.Fatal(err)
	}
	validated[0].CanonicalDefinition[0] ^= 0xff
	validated[0].OwnerReceipt[0] ^= 0xff
	if _, err := ValidateDefinitionSet(set); err != nil {
		t.Fatalf("validated result aliases stored entry bytes: %v", err)
	}
}

func TestValidateDefinitionSetRejectsEveryCompletenessMutation(t *testing.T) {
	base := validDefinitionSet(t)
	tests := []struct {
		name   string
		mutate func(*DefinitionSet)
		want   string
	}{
		{name: "protocol", mutate: func(value *DefinitionSet) {
			value.Header.Protocol = "v2"
		}, want: "Protocol"},
		{name: "owner pack", mutate: func(value *DefinitionSet) {
			value.Header.OwnerPack = "connector-b"
		}, want: "OwnerPack"},
		{name: "installation", mutate: func(value *DefinitionSet) {
			value.Header.PackInstallationID = "install-2"
		}, want: "PackInstallationID"},
		{name: "pack payload digest", mutate: func(value *DefinitionSet) {
			value.Header.PackPayloadDigest = packDigest('b')
		}, want: "PackPayloadDigest"},
		{name: "count", mutate: func(value *DefinitionSet) {
			value.Header.DefinitionCount++
		}, want: "stored entry count"},
		{name: "missing entry", mutate: func(value *DefinitionSet) {
			value.Entries = value.Entries[:len(value.Entries)-1]
		}, want: "stored entry count"},
		{name: "extra entry", mutate: func(value *DefinitionSet) {
			extra := value.Entries[0]
			extra.EnvelopeJSON = cloneBytes(extra.EnvelopeJSON)
			entries := make([]DefinitionSetEntry, 0, len(value.Entries)+1)
			entries = append(entries, value.Entries[0], extra)
			entries = append(entries, value.Entries[1:]...)
			for index := range entries {
				entries[index].Ordinal = uint64(index)
			}
			value.Entries = entries
			value.Header.DefinitionCount++
		}, want: "duplicate identity"},
		{name: "ordinal gap", mutate: func(value *DefinitionSet) {
			value.Entries[1].Ordinal++
		}, want: "Ordinal"},
		{name: "reordered entries", mutate: func(value *DefinitionSet) {
			value.Entries[0], value.Entries[1] = value.Entries[1], value.Entries[0]
			value.Entries[0].Ordinal = 0
			value.Entries[1].Ordinal = 1
		}, want: "canonical Kind/BusinessKey order"},
		{name: "kind projection", mutate: func(value *DefinitionSet) {
			value.Entries[0].Kind = catalogv1.BindingConnectorPushContract
		}, want: "projected identity"},
		{name: "owner projection", mutate: func(value *DefinitionSet) {
			value.Entries[0].OwnerPack = "connector-b"
		}, want: "projected identity"},
		{name: "installation projection", mutate: func(value *DefinitionSet) {
			value.Entries[0].PackInstallationID = "install-2"
		}, want: "projected identity"},
		{name: "payload projection", mutate: func(value *DefinitionSet) {
			value.Entries[0].PackPayloadDigest = packDigest('b')
		}, want: "projected identity"},
		{name: "business key projection", mutate: func(value *DefinitionSet) {
			value.Entries[0].BusinessKey = "connector-a/other"
		}, want: "projected identity"},
		{name: "version projection", mutate: func(value *DefinitionSet) {
			value.Entries[0].DefinitionVersion++
		}, want: "projected identity"},
		{name: "envelope digest", mutate: func(value *DefinitionSet) {
			value.Entries[0].EnvelopeDigest = digest('b')
		}, want: "EnvelopeDigest"},
		{name: "ordered set digest", mutate: func(value *DefinitionSet) {
			value.Header.OrderedSetDigest = digest('b')
		}, want: "OrderedSetDigest"},
		{name: "mixed owner generation", mutate: func(value *DefinitionSet) {
			otherCoordinate := DefinitionSetCoordinate{
				OwnerPack: "connector-b", PackInstallationID: "install-2", PackPayloadDigest: packDigest('b'),
			}
			other := newDefinitionSetEnvelope(
				t, otherCoordinate, catalogv1.BindingConnectorFieldMapping, "connector-b/contact", 1, "other",
			)
			value.Entries[1] = definitionSetEntryFromEnvelope(t, 1, other)
		}, want: "OwnerPack"},
		{name: "valid substituted definition with stale set digest", mutate: func(value *DefinitionSet) {
			original, err := ParseDefinitionEnvelopeJSON(value.Entries[0].EnvelopeJSON)
			if err != nil {
				t.Fatal(err)
			}
			replacement, err := NewDefinitionEnvelope(AuthoringInput{
				Identity: original.Identity, CanonicalDefinition: []byte(`{"definition":"replacement"}`),
			})
			if err != nil {
				t.Fatal(err)
			}
			value.Entries[0] = definitionSetEntryFromEnvelope(t, 0, replacement)
		}, want: "OrderedSetDigest"},
	}

	for _, test := range tests {
		t.Run(test.name, func(t *testing.T) {
			value := cloneValidDefinitionSetForTest(t, base)
			test.mutate(&value)
			_, err := ValidateDefinitionSet(value)
			if err == nil || !strings.Contains(err.Error(), test.want) {
				t.Fatalf("error = %v, want substring %q", err, test.want)
			}
		})
	}
}

func TestDefinitionSetRejectsEnvelopeCorruption(t *testing.T) {
	base := validDefinitionSet(t)
	tests := []struct {
		name   string
		mutate func(*DefinitionSetEntry)
		want   string
	}{
		{name: "noncanonical JSON", mutate: func(entry *DefinitionSetEntry) {
			entry.EnvelopeJSON = append([]byte(" "), entry.EnvelopeJSON...)
		}, want: "not canonical JSON"},
		{name: "malformed JSON", mutate: func(entry *DefinitionSetEntry) {
			entry.EnvelopeJSON[0] = '['
		}, want: "decode definition envelope"},
		{name: "definition without rederived digest", mutate: func(entry *DefinitionSetEntry) {
			definition, err := ParseDefinitionEnvelopeJSON(entry.EnvelopeJSON)
			if err != nil {
				t.Fatal(err)
			}
			definition.CanonicalDefinition[0] ^= 0xff
			entry.EnvelopeJSON, _ = json.Marshal(definition)
		}, want: "CanonicalDefinitionDigest"},
		{name: "receipt without rederived digest", mutate: func(entry *DefinitionSetEntry) {
			definition, err := ParseDefinitionEnvelopeJSON(entry.EnvelopeJSON)
			if err != nil {
				t.Fatal(err)
			}
			definition.OwnerReceipt[0] ^= 0xff
			entry.EnvelopeJSON, _ = json.Marshal(definition)
		}, want: "OwnerReceipt"},
		{name: "oversized JSON", mutate: func(entry *DefinitionSetEntry) {
			entry.EnvelopeJSON = make([]byte, MaxSerializedEnvelopeBytes+1)
		}, want: "size is invalid"},
	}
	for _, test := range tests {
		t.Run(test.name, func(t *testing.T) {
			value := cloneValidDefinitionSetForTest(t, base)
			test.mutate(&value.Entries[0])
			_, err := ValidateDefinitionSet(value)
			if err == nil || !strings.Contains(err.Error(), test.want) {
				t.Fatalf("error = %v, want substring %q", err, test.want)
			}
		})
	}
}

func TestNewDefinitionSetRejectsInvalidInputsAndVersions(t *testing.T) {
	coordinate := validDefinitionSetCoordinate()
	validRoute := newDefinitionSetEnvelope(
		t, coordinate, catalogv1.BindingConnectorInboundRoute, "connector-a/event", 7, "route",
	)
	duplicateRoute := newDefinitionSetEnvelope(
		t, coordinate, catalogv1.BindingConnectorInboundRoute, "connector-a/event", 8, "route-v8",
	)
	mixedCoordinate := DefinitionSetCoordinate{
		OwnerPack: "connector-b", PackInstallationID: "install-2", PackPayloadDigest: packDigest('b'),
	}
	mixed := newDefinitionSetEnvelope(
		t, mixedCoordinate, catalogv1.BindingConnectorPushContract, "connector-b/push", 1, "mixed",
	)
	corrupt := CloneDefinitionEnvelope(validRoute)
	corrupt.OwnerReceiptDigest = digest('c')

	for _, test := range []struct {
		name        string
		coordinate  DefinitionSetCoordinate
		definitions []DefinitionEnvelope
		want        string
	}{
		{name: "blank owner", coordinate: DefinitionSetCoordinate{
			PackInstallationID: coordinate.PackInstallationID, PackPayloadDigest: coordinate.PackPayloadDigest,
		}, want: "OwnerPack"},
		{name: "blank installation", coordinate: DefinitionSetCoordinate{
			OwnerPack: coordinate.OwnerPack, PackPayloadDigest: coordinate.PackPayloadDigest,
		}, want: "PackInstallationID"},
		{name: "invalid pack digest", coordinate: DefinitionSetCoordinate{
			OwnerPack: coordinate.OwnerPack, PackInstallationID: coordinate.PackInstallationID, PackPayloadDigest: digest('a'),
		}, want: "PackPayloadDigest"},
		{name: "duplicate natural identity", coordinate: coordinate, definitions: []DefinitionEnvelope{validRoute, duplicateRoute}, want: "duplicate identity"},
		{name: "mixed parent", coordinate: coordinate, definitions: []DefinitionEnvelope{validRoute, mixed}, want: "OwnerPack"},
		{name: "invalid envelope", coordinate: coordinate, definitions: []DefinitionEnvelope{corrupt}, want: "OwnerReceiptDigest"},
	} {
		t.Run(test.name, func(t *testing.T) {
			_, err := NewDefinitionSet(test.coordinate, test.definitions)
			if err == nil || !strings.Contains(err.Error(), test.want) {
				t.Fatalf("error = %v, want substring %q", err, test.want)
			}
		})
	}

	for _, kind := range []catalogv1.IntegrationBindingKind{
		catalogv1.BindingConnectorCalloutProfile,
		catalogv1.BindingConnectorFieldMapping,
		catalogv1.BindingConnectorPushContract,
	} {
		t.Run("non-route-v2-"+string(kind), func(t *testing.T) {
			definition := newDefinitionSetEnvelope(t, coordinate, kind, "connector-a/non-route-"+string(kind), 2, "v2")
			if _, err := NewDefinitionSet(coordinate, []DefinitionEnvelope{definition}); err == nil ||
				!strings.Contains(err.Error(), "DefinitionVersion 1") {
				t.Fatalf("error = %v", err)
			}
		})
	}
}

func TestValidateDefinitionSetRejectsPersistedNonRouteVersionTwo(t *testing.T) {
	coordinate := validDefinitionSetCoordinate()
	definition := newDefinitionSetEnvelope(
		t, coordinate, catalogv1.BindingConnectorFieldMapping, "connector-a/contact", 2, "v2",
	)
	empty, err := NewDefinitionSet(coordinate, nil)
	if err != nil {
		t.Fatal(err)
	}
	empty.Header.DefinitionCount = 1
	empty.Entries = []DefinitionSetEntry{definitionSetEntryFromEnvelope(t, 0, definition)}
	if _, err := ValidateDefinitionSet(empty); err == nil || !strings.Contains(err.Error(), "DefinitionVersion 1") {
		t.Fatalf("error = %v", err)
	}
}

func TestDefinitionSetPublicPathsEnforceDefinitionCountBoundary(t *testing.T) {
	coordinate := validDefinitionSetCoordinate()
	definitions := make([]DefinitionEnvelope, MaxDefinitionCount)
	for index := range definitions {
		definitions[index] = newDefinitionSetEnvelope(
			t,
			coordinate,
			catalogv1.BindingConnectorInboundRoute,
			fmt.Sprintf("connector-a/route-%04d", index),
			uint64(index)+1,
			fmt.Sprintf("route-%04d", index),
		)
	}
	set, err := NewDefinitionSet(coordinate, definitions)
	if err != nil {
		t.Fatalf("exact maximum rejected: %v", err)
	}
	if len(set.Entries) != MaxDefinitionCount {
		t.Fatalf("entry count = %d", len(set.Entries))
	}
	if _, err := ValidateDefinitionSet(set); err != nil {
		t.Fatalf("exact maximum stored set rejected: %v", err)
	}

	tooMany := make([]DefinitionEnvelope, MaxDefinitionCount+1)
	if _, err := NewDefinitionSet(coordinate, tooMany); err == nil || !strings.Contains(err.Error(), "exceed") {
		t.Fatalf("authoring overflow error = %v", err)
	}
	overflow := DefinitionSet{
		Header: DefinitionSetHeader{
			Protocol: DefinitionSetProtocolV1, DefinitionSetCoordinate: coordinate,
			DefinitionCount: MaxDefinitionCount + 1,
		},
		Entries: make([]DefinitionSetEntry, MaxDefinitionCount+1),
	}
	if _, err := ValidateDefinitionSet(overflow); err == nil || !strings.Contains(err.Error(), "exceed") {
		t.Fatalf("stored overflow error = %v", err)
	}
}

func TestDefinitionSetByteBudgetArithmetic(t *testing.T) {
	total, err := addDefinitionSetBytes(0, MaxDefinitionSetBytes-1)
	if err != nil {
		t.Fatalf("limit-1 rejected: %v", err)
	}
	total, err = addDefinitionSetBytes(total, 1)
	if err != nil || total != MaxDefinitionSetBytes {
		t.Fatalf("exact limit = %d, %v", total, err)
	}
	for _, test := range []struct {
		name        string
		total, next uint64
	}{
		{name: "limit plus one", total: MaxDefinitionSetBytes, next: 1},
		{name: "single addition over limit", total: 0, next: MaxDefinitionSetBytes + 1},
		{name: "already over limit", total: MaxDefinitionSetBytes + 1, next: 0},
		{name: "uint64 overflow", total: ^uint64(0), next: 1},
	} {
		t.Run(test.name, func(t *testing.T) {
			if _, err := addDefinitionSetBytes(test.total, test.next); err == nil ||
				!strings.Contains(err.Error(), "serialized envelopes exceed") {
				t.Fatalf("error = %v", err)
			}
		})
	}
}

func TestDefinitionSetPublicPathsRejectAggregateEnvelopeBytes(t *testing.T) {
	coordinate := validDefinitionSetCoordinate()
	large, err := NewDefinitionEnvelope(AuthoringInput{
		Identity: Identity{
			Kind:               catalogv1.BindingConnectorInboundRoute,
			OwnerPack:          coordinate.OwnerPack,
			PackInstallationID: coordinate.PackInstallationID,
			PackPayloadDigest:  coordinate.PackPayloadDigest,
			BusinessKey:        "connector-a/large-route",
			DefinitionVersion:  1,
		},
		CanonicalDefinition: bytes.Repeat([]byte{'x'}, MaxCanonicalDefinitionBytes),
	})
	if err != nil {
		t.Fatal(err)
	}
	raw, err := MarshalDefinitionEnvelopeJSON(large)
	if err != nil {
		t.Fatal(err)
	}
	repetitions := int(MaxDefinitionSetBytes/uint64(len(raw))) + 1
	if repetitions > MaxDefinitionCount {
		t.Fatalf("aggregate fixture needs %d definitions", repetitions)
	}

	definitions := make([]DefinitionEnvelope, repetitions)
	for index := range definitions {
		definitions[index] = large
	}
	if _, err := NewDefinitionSet(coordinate, definitions); err == nil ||
		!strings.Contains(err.Error(), "serialized envelopes exceed") {
		t.Fatalf("NewDefinitionSet aggregate error = %v", err)
	}

	entries := make([]DefinitionSetEntry, repetitions)
	for index := range entries {
		entries[index] = DefinitionSetEntry{EnvelopeJSON: raw}
	}
	stored := DefinitionSet{
		Header: DefinitionSetHeader{
			Protocol:                DefinitionSetProtocolV1,
			DefinitionSetCoordinate: coordinate,
			DefinitionCount:         uint64(len(entries)),
		},
		Entries: entries,
	}
	if _, err := ValidateDefinitionSet(stored); err == nil ||
		!strings.Contains(err.Error(), "serialized envelopes exceed") {
		t.Fatalf("ValidateDefinitionSet aggregate error = %v", err)
	}
}

func validDefinitionSet(t *testing.T) DefinitionSet {
	t.Helper()
	coordinate := validDefinitionSetCoordinate()
	set, err := NewDefinitionSet(coordinate, []DefinitionEnvelope{
		newDefinitionSetEnvelope(t, coordinate, catalogv1.BindingConnectorInboundRoute, "connector-a/event", 7, "route"),
		newDefinitionSetEnvelope(t, coordinate, catalogv1.BindingConnectorFieldMapping, "connector-a/contact", 1, "mapping"),
		newDefinitionSetEnvelope(t, coordinate, catalogv1.BindingConnectorPushContract, "connector-a/push", 1, "push"),
	})
	if err != nil {
		t.Fatal(err)
	}
	return set
}

func validDefinitionSetCoordinate() DefinitionSetCoordinate {
	return DefinitionSetCoordinate{
		OwnerPack:          "connector-a",
		PackInstallationID: "install-1",
		PackPayloadDigest:  packDigest('a'),
	}
}

func newDefinitionSetEnvelope(
	t *testing.T,
	coordinate DefinitionSetCoordinate,
	kind catalogv1.IntegrationBindingKind,
	businessKey string,
	version uint64,
	definition string,
) DefinitionEnvelope {
	t.Helper()
	value, err := NewDefinitionEnvelope(AuthoringInput{
		Identity: Identity{
			Kind:               kind,
			OwnerPack:          coordinate.OwnerPack,
			PackInstallationID: coordinate.PackInstallationID,
			PackPayloadDigest:  coordinate.PackPayloadDigest,
			BusinessKey:        businessKey,
			DefinitionVersion:  version,
		},
		CanonicalDefinition: []byte(`{"definition":"` + definition + `"}`),
	})
	if err != nil {
		t.Fatal(err)
	}
	return value
}

func definitionSetEntryFromEnvelope(
	t *testing.T,
	ordinal uint64,
	definition DefinitionEnvelope,
) DefinitionSetEntry {
	t.Helper()
	raw, err := MarshalDefinitionEnvelopeJSON(definition)
	if err != nil {
		t.Fatal(err)
	}
	envelopeDigest, err := DefinitionEnvelopeDigest(definition)
	if err != nil {
		t.Fatal(err)
	}
	return DefinitionSetEntry{
		Ordinal: ordinal, Kind: definition.Kind, OwnerPack: definition.OwnerPack,
		PackInstallationID: definition.PackInstallationID,
		PackPayloadDigest:  definition.PackPayloadDigest,
		BusinessKey:        definition.BusinessKey,
		DefinitionVersion:  definition.DefinitionVersion,
		EnvelopeJSON:       raw, EnvelopeDigest: envelopeDigest,
	}
}

// cloneValidDefinitionSetForTest keeps mutation fixtures independent without
// adding an unbounded clone operation to the production API. Validation owns
// both count and aggregate-byte preflight before this helper allocates.
func cloneValidDefinitionSetForTest(t *testing.T, set DefinitionSet) DefinitionSet {
	t.Helper()
	if _, err := ValidateDefinitionSet(set); err != nil {
		t.Fatalf("clone test fixture: %v", err)
	}
	entries := make([]DefinitionSetEntry, len(set.Entries))
	for index, entry := range set.Entries {
		entries[index] = entry
		entries[index].EnvelopeJSON = cloneBytes(entry.EnvelopeJSON)
	}
	return DefinitionSet{Header: set.Header, Entries: entries}
}

func assertExactDefinitionSetFields(t *testing.T, value reflect.Type, want []string) {
	t.Helper()
	if value.NumField() != len(want) {
		t.Fatalf("%s field count = %d, want %d", value.Name(), value.NumField(), len(want))
	}
	for index, name := range want {
		if value.Field(index).Name != name {
			t.Fatalf("%s field[%d] = %s, want %s", value.Name(), index, value.Field(index).Name, name)
		}
	}
}
