package topology

import (
	"testing"

	"github.com/stretchr/testify/assert"
	"github.com/stretchr/testify/require"
	clusterngv1beta "github.com/tidbcloud/cluster-service-ng/api/cluster-service-ng/v1beta"

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

func TestBuildTopologySpecFiltersSharedPoolComponents(t *testing.T) {
	topologySpec, err := BuildTopologySpec(testFixedTopology(), SharedPoolComponentFilter)

	require.NoError(t, err)
	assertComponentTypes(t, topologySpec, []clusterngv1beta.ComponentType{
		clusterngv1beta.ComponentType_PD,
		clusterngv1beta.ComponentType_TIKV,
		clusterngv1beta.ComponentType_TIKV_WORKER,
		clusterngv1beta.ComponentType_COPROCESSOR_WORKER,
		clusterngv1beta.ComponentType_TIDB_WORKER,
		clusterngv1beta.ComponentType_TIFLASH_COMPUTE,
		clusterngv1beta.ComponentType_SCHEMA_MANAGER,
	})
}

func TestBuildTopologySpecFiltersOutLegacyTiflashWriteForSharedPool(t *testing.T) {
	topologySpec, err := BuildTopologySpec(testFixedTopology(), SharedPoolComponentFilter)

	require.NoError(t, err)
	for _, component := range topologySpec.GetComponents() {
		require.NotEqual(t, clusterngv1beta.ComponentType_TIFLASH_WRITE, component.GetComponentType())
	}
}

func TestBuildTopologySpecFiltersTiDBClusterComponents(t *testing.T) {
	topologySpec, err := BuildTopologySpec(testFixedTopology(), TiDBClusterComponentFilter)

	require.NoError(t, err)
	assertComponentTypes(t, topologySpec, []clusterngv1beta.ComponentType{
		clusterngv1beta.ComponentType_TIPROXY,
	})
}

func TestBuildTopologySpecFiltersTiDBGroupComponents(t *testing.T) {
	topologySpec, err := BuildTopologySpec(testFixedTopology(), TiDBGroupComponentFilter)

	require.NoError(t, err)
	assertComponentTypes(t, topologySpec, []clusterngv1beta.ComponentType{
		clusterngv1beta.ComponentType_TIDB,
	})
}

func TestBuildTopologySpecValidatesNodeQuota(t *testing.T) {
	tests := []struct {
		name      string
		component *globalngv1.ComponentTopology
		wantErr   string
	}{
		{
			name: "missing node quota",
			component: &globalngv1.ComponentTopology{
				ComponentType: globalngv1.ComponentType_TIDB,
			},
			wantErr: "node_quota is required",
		},
		{
			name: "missing node spec",
			component: &globalngv1.ComponentTopology{
				ComponentType: globalngv1.ComponentType_TIDB,
				NodeQuota:     &globalngv1.NodeQuota{NodeCount: 1},
			},
			wantErr: "node_quota.node_spec is required",
		},
		{
			name: "zero node count",
			component: &globalngv1.ComponentTopology{
				ComponentType: globalngv1.ComponentType_TIDB,
				NodeQuota: &globalngv1.NodeQuota{
					NodeSpec: &globalngv1.NodeSpec{Cpu: 4, MemoryGi: 8},
				},
			},
			wantErr: "node_quota.node_count must be greater than or equal to 1",
		},
		{
			name: "zero cpu",
			component: &globalngv1.ComponentTopology{
				ComponentType: globalngv1.ComponentType_TIDB,
				NodeQuota: &globalngv1.NodeQuota{
					NodeSpec:  &globalngv1.NodeSpec{MemoryGi: 8},
					NodeCount: 1,
				},
			},
			wantErr: "node_quota.node_spec.cpu must be greater than 0",
		},
		{
			name: "zero memory",
			component: &globalngv1.ComponentTopology{
				ComponentType: globalngv1.ComponentType_TIDB,
				NodeQuota: &globalngv1.NodeQuota{
					NodeSpec:  &globalngv1.NodeSpec{Cpu: 4},
					NodeCount: 1,
				},
			},
			wantErr: "node_quota.node_spec.memory_gi must be greater than 0",
		},
	}

	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			_, err := BuildTopologySpec(&globalngv1.FixedTopology{
				Components: []*globalngv1.ComponentTopology{tt.component},
			}, TiDBGroupComponentFilter)

			require.Error(t, err)
			assert.Contains(t, err.Error(), tt.wantErr)
		})
	}
}

func testFixedTopology() *globalngv1.FixedTopology {
	return &globalngv1.FixedTopology{
		Components: []*globalngv1.ComponentTopology{
			testComponent(globalngv1.ComponentType_PD),
			testComponent(globalngv1.ComponentType_TIKV),
			testComponent(globalngv1.ComponentType_TIKV_WORKER),
			testComponent(globalngv1.ComponentType_COPROCESSOR_WORKER),
			testComponent(globalngv1.ComponentType_TIDB),
			testComponent(globalngv1.ComponentType_TIDB_WORKER),
			testComponent(globalngv1.ComponentType_TIPROXY),
			testComponent(globalngv1.ComponentType_TIFLASH_WRITE),
			testComponent(globalngv1.ComponentType_TIFLASH_COMPUTE),
			testComponent(globalngv1.ComponentType_SCHEMA_MANAGER),
		},
	}
}

func testComponent(componentType globalngv1.ComponentType) *globalngv1.ComponentTopology {
	nodeCount := uint32(1)
	if componentType == globalngv1.ComponentType_PD || componentType == globalngv1.ComponentType_TIKV {
		nodeCount = 3
	}

	return &globalngv1.ComponentTopology{
		ComponentType: componentType,
		NodeQuota: &globalngv1.NodeQuota{
			NodeSpec:  &globalngv1.NodeSpec{Cpu: 4, MemoryGi: 8},
			NodeCount: nodeCount,
		},
	}
}

func assertComponentTypes(t *testing.T, topologySpec *clusterngv1beta.TopologySpec, want []clusterngv1beta.ComponentType) {
	t.Helper()
	got := make([]clusterngv1beta.ComponentType, 0, len(topologySpec.GetComponents()))
	for _, component := range topologySpec.GetComponents() {
		got = append(got, component.GetComponentType())
	}
	assert.Equal(t, want, got)
}
