package tenantlayoutbundleauthorityv1

import (
	"reflect"
	"testing"

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

func TestValueSurfacesAreExact(t *testing.T) {
	assertExactFields(t, BundleKey{},
		"BundleKeyVersion", "SlotOwnerKind", "SlotOwnerNamespace", "LocalComponentKey")
	assertExactFields(t, TenantNamespaceReservationV1{},
		"ValueVersion", "TenantNamespaceID", "State")
	assertExactFields(t, TenantOrgNamespaceBindingV1{},
		"ValueVersion", "OrgID", "TenantNamespaceID", "State")
	assertExactFields(t, TenantLayoutBundleSlotV1{},
		"ValueVersion", "BundleKey", "State")
	assertExactFields(t, TenantNamespaceExpectedPrior{},
		"Variant", "NamespaceValueDigest")
	assertExactFields(t, TenantOrgBindingExpectedPrior{},
		"Variant", "BindingValueDigest")
	assertExactFields(t, TenantSlotExpectedPrior{},
		"Variant", "SlotValueDigest")
	assertExactFields(t, TenantNamespaceCommandRequest{},
		"Operation", "TargetTenantNamespaceID", "DesiredNamespaceValue", "ExpectedPriorNamespace")
	assertExactFields(t, TenantOrgNamespaceBindingCommandRequest{},
		"Operation", "TargetOrgID", "DesiredBindingValue",
		"ExpectedNamespaceReservationDigest", "ExpectedPriorBinding")
	assertExactFields(t, TenantLayoutBundleSlotCommandRequest{},
		"Operation", "AuthorizingOrgID", "TargetBundleKey", "DesiredSlotValue",
		"ExpectedNamespaceReservationDigest", "ExpectedAuthorizingOrgBindingDigest", "ExpectedPriorSlot")
	assertExactFields(t, TenantLayoutBundleAuthorityEvidenceV1{},
		"EvidenceVersion", "SourceCatalogSnapshot", "BundleKey",
		"NamespaceReservationValueVersion", "NamespaceReservationState",
		"OrgBindingValueVersion", "OrgBindingState", "SlotValueVersion", "SlotState")
}

func TestValueSurfacesContainNoMutableOrOpenFields(t *testing.T) {
	values := []any{
		BundleKey{},
		TenantNamespaceReservationV1{},
		TenantOrgNamespaceBindingV1{},
		TenantLayoutBundleSlotV1{},
		TenantNamespaceExpectedPrior{},
		TenantOrgBindingExpectedPrior{},
		TenantSlotExpectedPrior{},
		TenantNamespaceCommandRequest{},
		TenantOrgNamespaceBindingCommandRequest{},
		TenantLayoutBundleSlotCommandRequest{},
		TenantLayoutBundleAuthorityEvidenceV1{},
	}
	for _, value := range values {
		typ := reflect.TypeOf(value)
		for index := 0; index < typ.NumField(); index++ {
			kind := typ.Field(index).Type.Kind()
			if kind == reflect.Map || kind == reflect.Slice || kind == reflect.Pointer || kind == reflect.Interface {
				t.Fatalf("%s.%s has mutable or open kind %s", typ.Name(), typ.Field(index).Name, kind)
			}
		}
	}
}

func TestCommandKindsAndExpectedPriorsRemainTypeDistinct(t *testing.T) {
	distinct := []reflect.Type{
		reflect.TypeOf(NamespaceOperation("")),
		reflect.TypeOf(BindingOperation("")),
		reflect.TypeOf(SlotOperation("")),
		reflect.TypeOf(NamespaceExpectedPriorVariant("")),
		reflect.TypeOf(BindingExpectedPriorVariant("")),
		reflect.TypeOf(SlotExpectedPriorVariant("")),
		reflect.TypeOf(TenantNamespaceCommandRequest{}),
		reflect.TypeOf(TenantOrgNamespaceBindingCommandRequest{}),
		reflect.TypeOf(TenantLayoutBundleSlotCommandRequest{}),
	}
	for left := range distinct {
		for right := left + 1; right < len(distinct); right++ {
			if distinct[left] == distinct[right] {
				t.Fatalf("types %s and %s unexpectedly collapse", distinct[left], distinct[right])
			}
		}
	}
}

func TestCanonicalEnumTagsAreFrozen(t *testing.T) {
	namespaceStates := map[NamespaceState]byte{
		NamespaceStateActive: 0x01, NamespaceStateTombstone: 0x02,
	}
	for value, want := range namespaceStates {
		got, err := canonicalNamespaceStateByte(value)
		if err != nil || got != want {
			t.Fatalf("namespace state %q tag = 0x%02x, %v", value, got, err)
		}
	}
	bindingStates := map[OrgBindingState]byte{
		OrgBindingStateActive: 0x01, OrgBindingStateTombstone: 0x02,
	}
	for value, want := range bindingStates {
		got, err := canonicalOrgBindingStateByte(value)
		if err != nil || got != want {
			t.Fatalf("binding state %q tag = 0x%02x, %v", value, got, err)
		}
	}
	slotStates := map[SlotState]byte{
		SlotStateActive: 0x01, SlotStateTombstone: 0x02,
	}
	for value, want := range slotStates {
		got, err := canonicalSlotStateByte(value)
		if err != nil || got != want {
			t.Fatalf("slot state %q tag = 0x%02x, %v", value, got, err)
		}
	}

	for operation, want := range map[NamespaceOperation]byte{
		NamespaceOperationReserve: 0x01, NamespaceOperationTombstone: 0x02,
	} {
		got, err := canonicalNamespaceOperationByte(operation)
		if err != nil || got != want {
			t.Fatalf("namespace operation %q tag = 0x%02x, %v", operation, got, err)
		}
	}
	for operation, want := range map[BindingOperation]byte{
		BindingOperationBind: 0x01, BindingOperationTombstone: 0x02,
	} {
		got, err := canonicalBindingOperationByte(operation)
		if err != nil || got != want {
			t.Fatalf("binding operation %q tag = 0x%02x, %v", operation, got, err)
		}
	}
	for operation, want := range map[SlotOperation]byte{
		SlotOperationCreate: 0x01, SlotOperationRetire: 0x02,
	} {
		got, err := canonicalSlotOperationByte(operation)
		if err != nil || got != want {
			t.Fatalf("slot operation %q tag = 0x%02x, %v", operation, got, err)
		}
	}

	if got, _ := canonicalNamespaceExpectedPriorByte(NamespaceExpectedPriorAbsent); got != 0x00 {
		t.Fatalf("namespace ABSENT tag = 0x%02x", got)
	}
	if got, _ := canonicalNamespaceExpectedPriorByte(NamespaceExpectedPriorExact); got != 0x01 {
		t.Fatalf("namespace EXACT tag = 0x%02x", got)
	}
	if got, _ := canonicalBindingExpectedPriorByte(BindingExpectedPriorAbsent); got != 0x00 {
		t.Fatalf("binding ABSENT tag = 0x%02x", got)
	}
	if got, _ := canonicalBindingExpectedPriorByte(BindingExpectedPriorExact); got != 0x01 {
		t.Fatalf("binding EXACT tag = 0x%02x", got)
	}
	if got, _ := canonicalSlotExpectedPriorByte(SlotExpectedPriorAbsent); got != 0x00 {
		t.Fatalf("slot ABSENT tag = 0x%02x", got)
	}
	if got, _ := canonicalSlotExpectedPriorByte(SlotExpectedPriorExact); got != 0x01 {
		t.Fatalf("slot EXACT tag = 0x%02x", got)
	}
	ownerTag, err := layoutbundlekeyv1.CanonicalSlotOwnerKindByte(layoutbundlekeyv1.SlotOwnerKindTenant)
	if err != nil || ownerTag != 0x02 {
		t.Fatalf("tenant owner tag = 0x%02x, %v", ownerTag, err)
	}
}

func assertExactFields(t *testing.T, value any, expected ...string) {
	t.Helper()
	typ := reflect.TypeOf(value)
	if typ.NumField() != len(expected) {
		t.Fatalf("%s field count = %d, want %d", typ.Name(), typ.NumField(), len(expected))
	}
	for index, fieldName := range expected {
		if got := typ.Field(index).Name; got != fieldName {
			t.Fatalf("%s field %d = %s, want %s", typ.Name(), index, got, fieldName)
		}
	}
}
