package catalogtenant

import (
	"testing"

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

func TestNamespaceDecisionNeverRevivesAndBlocksRetirementDependencies(t *testing.T) {
	reserve := namespaceReserve(testNamespace)
	active := ValueReference{
		Present: true,
		Digest: mustNamespaceDigest(
			t,
			namespaceValue(testNamespace, tenantauthorityv1.NamespaceStateActive),
		),
	}
	tombstone := ValueReference{
		Present: true,
		Digest: mustNamespaceDigest(
			t,
			namespaceValue(testNamespace, tenantauthorityv1.NamespaceStateTombstone),
		),
	}
	if got := decideNamespace(reserve, ValueReference{}, false, false); got != ResultApplied {
		t.Fatalf("absent reserve = %q", got)
	}
	for name, observed := range map[string]ValueReference{
		"active":    active,
		"tombstone": tombstone,
	} {
		if got := decideNamespace(reserve, observed, false, false); got != ResultExpectedPriorMismatch {
			t.Fatalf("%s reserve = %q", name, got)
		}
	}

	retire := namespaceTombstone(t, testNamespace)
	if got := decideNamespace(retire, active, true, true); got != ResultActiveBinding {
		t.Fatalf("active binding = %q", got)
	}
	if got := decideNamespace(retire, active, false, true); got != ResultActiveSlot {
		t.Fatalf("active slot = %q", got)
	}
	if got := decideNamespace(retire, active, false, false); got != ResultApplied {
		t.Fatalf("clear retirement = %q", got)
	}
	if got := decideNamespace(retire, tombstone, false, false); got != ResultExpectedPriorMismatch {
		t.Fatalf("repeated retirement = %q", got)
	}
}

func TestBindingDecisionRequiresActiveNamespaceBeforePrior(t *testing.T) {
	request := bindingBind(t, testOrg, testNamespace)
	activeNamespace := namespaceCurrent{
		Value: namespaceValue(
			testNamespace,
			tenantauthorityv1.NamespaceStateActive,
		),
		Digest: request.ExpectedNamespaceReservationDigest,
	}
	if got := decideBinding(
		request,
		namespaceCurrent{},
		false,
		bindingCurrent{},
		false,
		false,
	); got != ResultNamespaceDependencyMismatch {
		t.Fatalf("absent namespace = %q", got)
	}
	tombstonedNamespace := activeNamespace
	tombstonedNamespace.Value.State = tenantauthorityv1.NamespaceStateTombstone
	if got := decideBinding(
		request,
		tombstonedNamespace,
		true,
		bindingCurrent{},
		false,
		false,
	); got != ResultNamespaceDependencyMismatch {
		t.Fatalf("tombstoned namespace with matching digest = %q", got)
	}
	if got := decideBinding(
		request,
		activeNamespace,
		true,
		bindingCurrent{},
		false,
		false,
	); got != ResultApplied {
		t.Fatalf("valid bind = %q", got)
	}
	tombstonedBinding := bindingCurrent{
		Value: bindingValue(
			testOrg,
			testNamespace,
			tenantauthorityv1.OrgBindingStateTombstone,
		),
		Digest: mustBindingDigest(
			t,
			bindingValue(testOrg, testNamespace, tenantauthorityv1.OrgBindingStateTombstone),
		),
	}
	if got := decideBinding(
		request,
		activeNamespace,
		true,
		tombstonedBinding,
		true,
		false,
	); got != ResultExpectedPriorMismatch {
		t.Fatalf("tombstoned binding revival = %q", got)
	}

	retire := bindingTombstone(t, testOrg, testNamespace)
	activeBinding := bindingCurrent{
		Value: bindingValue(
			testOrg,
			testNamespace,
			tenantauthorityv1.OrgBindingStateActive,
		),
		Digest: retire.ExpectedPriorBinding.BindingValueDigest,
	}
	if got := decideBinding(
		retire,
		activeNamespace,
		true,
		activeBinding,
		true,
		false,
	); got != ResultApplied {
		t.Fatalf("valid retirement = %q", got)
	}
	if got := decideBinding(
		retire,
		activeNamespace,
		true,
		activeBinding,
		true,
		true,
	); got != ResultActiveSlot {
		t.Fatalf("last binding with active slot = %q", got)
	}
	foreignNamespaceBinding := activeBinding
	foreignNamespaceBinding.Value.TenantNamespaceID = "com.example.foreign"
	if got := decideBinding(
		retire,
		activeNamespace,
		true,
		foreignNamespaceBinding,
		true,
		false,
	); got != ResultExpectedPriorMismatch {
		t.Fatalf("foreign namespace binding with matching digest = %q", got)
	}
	tombstonedPrior := activeBinding
	tombstonedPrior.Value.State = tenantauthorityv1.OrgBindingStateTombstone
	if got := decideBinding(
		retire,
		activeNamespace,
		true,
		tombstonedPrior,
		true,
		true,
	); got != ResultExpectedPriorMismatch {
		t.Fatalf("tombstoned binding with matching digest = %q", got)
	}
}

func TestSlotDecisionRequiresBothActiveDependenciesBeforePrior(t *testing.T) {
	request := slotCreate(t, testOrg, testNamespace)
	activeNamespace := namespaceCurrent{
		Value: namespaceValue(
			testNamespace,
			tenantauthorityv1.NamespaceStateActive,
		),
		Digest: request.ExpectedNamespaceReservationDigest,
	}
	activeBinding := bindingCurrent{
		Value: bindingValue(
			testOrg,
			testNamespace,
			tenantauthorityv1.OrgBindingStateActive,
		),
		Digest: request.ExpectedAuthorizingOrgBindingDigest,
	}
	if got := decideSlot(
		request,
		namespaceCurrent{},
		false,
		activeBinding,
		true,
		ValueReference{},
	); got != ResultNamespaceDependencyMismatch {
		t.Fatalf("absent namespace = %q", got)
	}
	tombstonedNamespace := activeNamespace
	tombstonedNamespace.Value.State = tenantauthorityv1.NamespaceStateTombstone
	if got := decideSlot(
		request,
		tombstonedNamespace,
		true,
		activeBinding,
		true,
		ValueReference{},
	); got != ResultNamespaceDependencyMismatch {
		t.Fatalf("tombstoned namespace with matching digest = %q", got)
	}
	if got := decideSlot(
		request,
		activeNamespace,
		true,
		bindingCurrent{},
		false,
		ValueReference{},
	); got != ResultBindingDependencyMismatch {
		t.Fatalf("absent binding = %q", got)
	}
	tombstonedBinding := activeBinding
	tombstonedBinding.Value.State = tenantauthorityv1.OrgBindingStateTombstone
	if got := decideSlot(
		request,
		activeNamespace,
		true,
		tombstonedBinding,
		true,
		ValueReference{},
	); got != ResultBindingDependencyMismatch {
		t.Fatalf("tombstoned binding with matching digest = %q", got)
	}
	foreignNamespaceBinding := activeBinding
	foreignNamespaceBinding.Value.TenantNamespaceID = "com.example.foreign"
	if got := decideSlot(
		request,
		activeNamespace,
		true,
		foreignNamespaceBinding,
		true,
		ValueReference{},
	); got != ResultBindingDependencyMismatch {
		t.Fatalf("foreign namespace binding with matching digest = %q", got)
	}
	if got := decideSlot(
		request,
		activeNamespace,
		true,
		activeBinding,
		true,
		ValueReference{},
	); got != ResultApplied {
		t.Fatalf("valid create = %q", got)
	}
	tombstone := ValueReference{
		Present: true,
		Digest: mustSlotDigest(
			t,
			slotValue(testNamespace, tenantauthorityv1.SlotStateTombstone),
		),
	}
	if got := decideSlot(
		request,
		activeNamespace,
		true,
		activeBinding,
		true,
		tombstone,
	); got != ResultExpectedPriorMismatch {
		t.Fatalf("tombstoned slot revival = %q", got)
	}

	retire := slotRetire(t, testOrg, testNamespace)
	active := slotExpectedReference(retire.ExpectedPriorSlot)
	if got := decideSlot(
		retire,
		activeNamespace,
		true,
		activeBinding,
		true,
		active,
	); got != ResultApplied {
		t.Fatalf("valid retirement = %q", got)
	}
}
