package resourcepoolpolicy

import (
	"testing"

	"github.com/stretchr/testify/require"

	globalngv1 "github.com/tidbcloud/tidb-management-service/api/spec/global/nextgen/v1"
)

func TestDecidePauseResumeMatrix(t *testing.T) {
	t.Parallel()

	states := []globalngv1.ResourcePool_LifecycleState{
		globalngv1.ResourcePool_ACTIVE,
		globalngv1.ResourcePool_PAUSING,
		globalngv1.ResourcePool_PAUSED,
		globalngv1.ResourcePool_RESUMING,
	}
	expected := map[Operation][4]Decision{
		OperationCreateInstance:         {DecisionAllowed, DecisionBlocked, DecisionTriggerOrReuseResume, DecisionTriggerOrReuseResume},
		OperationRestoreInstance:        {DecisionAllowed, DecisionBlocked, DecisionTriggerOrReuseResume, DecisionTriggerOrReuseResume},
		OperationManualBackup:           {DecisionAllowed, DecisionBlocked, DecisionBlocked, DecisionBlocked},
		OperationImportExport:           {DecisionAllowed, DecisionBlocked, DecisionBlocked, DecisionBlocked},
		OperationScale:                  {DecisionAllowed, DecisionBlocked, DecisionBlocked, DecisionBlocked},
		OperationUpdateInstance:         {DecisionAllowed, DecisionBlocked, DecisionBlocked, DecisionBlocked},
		OperationDeleteInstance:         {DecisionAllowed, DecisionBlocked, DecisionAllowed, DecisionBlocked},
		OperationUpdateNetwork:          {DecisionAllowed, DecisionBlocked, DecisionBlocked, DecisionBlocked},
		OperationUpdatePoolLimits:       {DecisionAllowed, DecisionBlocked, DecisionAllowed, DecisionBlocked},
		OperationRenamePool:             {DecisionAllowed, DecisionBlocked, DecisionAllowed, DecisionBlocked},
		OperationEditCloudResourceTags:  {DecisionAllowed, DecisionBlocked, DecisionAllowed, DecisionBlocked},
		OperationDeletePool:             {DecisionAllowed, DecisionAllowed, DecisionAllowed, DecisionAllowed},
		OperationScheduledBackup:        {DecisionAllowed, DecisionBlocked, DecisionBlocked, DecisionBlocked},
		OperationPausePreparationBackup: {DecisionBlocked, DecisionBlocked, DecisionBlocked, DecisionBlocked},
	}

	require.Len(t, expected, int(operationCount-1))
	for operation := OperationUnknown + 1; operation < operationCount; operation++ {
		operationExpected, ok := expected[operation]
		require.True(t, ok, "operation %d is missing from the matrix", operation)
		for i, state := range states {
			require.Equal(t, operationExpected[i], Decide(operation, state),
				"operation %d in state %s", operation, state)
		}
	}
}

func TestDecidePreservesExistingLifecycleBehavior(t *testing.T) {
	t.Parallel()

	for _, state := range []globalngv1.ResourcePool_LifecycleState{
		globalngv1.ResourcePool_CREATING,
		globalngv1.ResourcePool_MODIFYING,
		globalngv1.ResourcePool_FAILED,
	} {
		for operation := OperationUnknown + 1; operation < operationCount; operation++ {
			if operation == OperationPausePreparationBackup {
				require.Equal(t, DecisionBlocked, Decide(operation, state))
				continue
			}
			require.Equal(t, DecisionAllowed, Decide(operation, state))
		}
	}
}

func TestDecidePausePrecheckingAllowsOnlyPreparationBackup(t *testing.T) {
	t.Parallel()

	for operation := OperationUnknown + 1; operation < operationCount; operation++ {
		expected := DecisionBlocked
		if operation == OperationPausePreparationBackup {
			expected = DecisionAllowed
		}
		require.Equal(t, expected, Decide(operation, globalngv1.ResourcePool_PAUSE_PRECHECKING))
	}
}

func TestDecideAllowsUnspecifiedDuringMetadataBackfill(t *testing.T) {
	t.Parallel()

	for operation := OperationUnknown + 1; operation < operationCount; operation++ {
		if operation == OperationPausePreparationBackup {
			require.Equal(
				t,
				DecisionBlocked,
				Decide(operation, globalngv1.ResourcePool_LIFECYCLE_STATE_UNSPECIFIED),
			)
			continue
		}
		require.Equal(
			t,
			DecisionAllowed,
			Decide(operation, globalngv1.ResourcePool_LIFECYCLE_STATE_UNSPECIFIED),
		)
	}
}

func TestDecideFailsClosed(t *testing.T) {
	t.Parallel()

	require.Equal(t, DecisionBlocked, Decide(OperationUnknown, globalngv1.ResourcePool_ACTIVE))
	require.Equal(t, DecisionBlocked, Decide(operationCount, globalngv1.ResourcePool_ACTIVE))
	require.Equal(t, DecisionBlocked, Decide(OperationCreateInstance, globalngv1.ResourcePool_LifecycleState(99)))
}
