package server

import (
	"context"
	"testing"

	"github.com/samber/lo"
	"github.com/stretchr/testify/assert"
	"github.com/stretchr/testify/require"
	apiv1 "github.com/tidbcloud/cluster-service-ng/api/cluster-service-ng/v1beta"
	"github.com/tidbcloud/cluster-service-ng/config"
	"github.com/tidbcloud/cluster-service-ng/domain/entity"
	mysqlschema "github.com/tidbcloud/cluster-service-ng/pkg/storage/mysql/schema"
	"google.golang.org/grpc/codes"
	"google.golang.org/grpc/status"
)

func TestResolveCreateSharedPoolHAType(t *testing.T) {
	tests := []struct {
		name       string
		server     *serverImpl
		sharedPool *entity.SharedPool
		requested  apiv1.CreateSharedPoolRequest_InitSharedPoolConfig_HighAvailabilityType
		want       apiv1.SharedPool_HighAvailabilityType
	}{
		{
			name:       "explicit regional wins",
			server:     &serverImpl{meteringConfig: &config.MeteringConfig{Plan: entity.EssentialServicePlan}},
			sharedPool: &entity.SharedPool{},
			requested:  apiv1.CreateSharedPoolRequest_InitSharedPoolConfig_REGIONAL,
			want:       apiv1.SharedPool_REGIONAL,
		},
		{
			name:       "explicit zonal wins",
			server:     &serverImpl{meteringConfig: &config.MeteringConfig{Plan: entity.PremiumServicePlan}},
			sharedPool: &entity.SharedPool{},
			requested:  apiv1.CreateSharedPoolRequest_InitSharedPoolConfig_ZONAL,
			want:       apiv1.SharedPool_ZONAL,
		},
		{
			name:       "premium defaults regional",
			server:     &serverImpl{meteringConfig: &config.MeteringConfig{Plan: entity.PremiumServicePlan}},
			sharedPool: &entity.SharedPool{},
			want:       apiv1.SharedPool_REGIONAL,
		},
		{
			name:       "byoc defaults regional",
			server:     &serverImpl{meteringConfig: &config.MeteringConfig{Plan: "byoc"}},
			sharedPool: &entity.SharedPool{},
			want:       apiv1.SharedPool_REGIONAL,
		},
		{
			name:   "essential v2 defaults zonal",
			server: &serverImpl{meteringConfig: &config.MeteringConfig{Plan: entity.PremiumServicePlan}},
			sharedPool: &entity.SharedPool{
				DeployType: mysqlschema.SharedPoolDeployTypeSharedTiProxy,
			},
			want: apiv1.SharedPool_ZONAL,
		},
		{
			name:       "configured essential defaults zonal",
			server:     &serverImpl{meteringConfig: &config.MeteringConfig{Plan: entity.EssentialServicePlan}},
			sharedPool: &entity.SharedPool{},
			want:       apiv1.SharedPool_ZONAL,
		},
	}

	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			require.Equal(t, tt.want, tt.server.resolveCreateSharedPoolHAType(tt.sharedPool, tt.requested))
		})
	}
}

func TestValidateTopologyDrivenRequiredComponents(t *testing.T) {
	t.Run("required components present", func(t *testing.T) {
		spec := &apiv1.TopologySpec{
			Components: []*apiv1.Component{
				{ComponentType: apiv1.ComponentType_PD},
				{ComponentType: apiv1.ComponentType_TIKV},
				{ComponentType: apiv1.ComponentType_TIKV_WORKER},
				{ComponentType: apiv1.ComponentType_TIDB_WORKER},
			},
		}

		err := validateTopologyDrivenRequiredComponents(spec)
		require.NoError(t, err)
	})

	t.Run("required with all allowed components", func(t *testing.T) {
		spec := &apiv1.TopologySpec{
			Components: []*apiv1.Component{
				{ComponentType: apiv1.ComponentType_PD},
				{ComponentType: apiv1.ComponentType_TIKV},
				{ComponentType: apiv1.ComponentType_TIKV_WORKER},
				{ComponentType: apiv1.ComponentType_TIDB_WORKER},
				{ComponentType: apiv1.ComponentType_TIFLASH_WRITE},
				{ComponentType: apiv1.ComponentType_TIFLASH_COMPUTE},
				{ComponentType: apiv1.ComponentType_SCHEMA_MANAGER},
			},
		}

		err := validateTopologyDrivenRequiredComponents(spec)
		require.NoError(t, err)
	})

	t.Run("required components allow node_count zero", func(t *testing.T) {
		spec := &apiv1.TopologySpec{
			Components: []*apiv1.Component{
				{ComponentType: apiv1.ComponentType_PD, NodeQuota: &apiv1.NodeQuota{NodeCount: 3}},
				{ComponentType: apiv1.ComponentType_TIKV, NodeQuota: &apiv1.NodeQuota{NodeCount: 3}},
				{ComponentType: apiv1.ComponentType_TIKV_WORKER, NodeQuota: &apiv1.NodeQuota{NodeCount: 0}},
				{ComponentType: apiv1.ComponentType_TIDB_WORKER, NodeQuota: &apiv1.NodeQuota{NodeCount: 0}},
			},
		}

		err := validateTopologyDrivenRequiredComponents(spec)
		require.NoError(t, err)
	})

	t.Run("missing required components returns invalid argument", func(t *testing.T) {
		spec := &apiv1.TopologySpec{
			Components: []*apiv1.Component{
				{ComponentType: apiv1.ComponentType_PD},
				{ComponentType: apiv1.ComponentType_TIKV},
				{ComponentType: apiv1.ComponentType_TIKV_WORKER},
			},
		}

		err := validateTopologyDrivenRequiredComponents(spec)
		require.Error(t, err)
		assert.Equal(t, codes.InvalidArgument, status.Code(err))
		assert.Contains(t, err.Error(), "TIDB_WORKER")
	})

	t.Run("unsupported components returns invalid argument", func(t *testing.T) {
		spec := &apiv1.TopologySpec{
			Components: []*apiv1.Component{
				{ComponentType: apiv1.ComponentType_PD},
				{ComponentType: apiv1.ComponentType_TIKV},
				{ComponentType: apiv1.ComponentType_TIKV_WORKER},
				{ComponentType: apiv1.ComponentType_TIDB_WORKER},
				{ComponentType: apiv1.ComponentType_PD_TSO},
				{ComponentType: apiv1.ComponentType_COPROCESSOR_WORKER},
			},
		}

		err := validateTopologyDrivenRequiredComponents(spec)
		require.Error(t, err)
		assert.Equal(t, codes.InvalidArgument, status.Code(err))
		assert.Contains(t, err.Error(), "PD_TSO")
		assert.Contains(t, err.Error(), "COPROCESSOR_WORKER")
	})
}

func TestValidateTopologyStorageConfigsRejectsSchemaManagerLocalSSD(t *testing.T) {
	err := validateTopologyStorageConfigs(apiv1.ProviderType_AWS.String(), &apiv1.TopologySpec{
		Components: []*apiv1.Component{{
			ComponentType: apiv1.ComponentType_SCHEMA_MANAGER,
			Storages: []*apiv1.Component_Storage{{
				Name: apiv1.Component_Storage_DATA,
				Type: entity.LocalSSD,
			}},
		}},
	})

	require.Error(t, err)
	assert.Equal(t, codes.InvalidArgument, status.Code(err))
	assert.Contains(t, err.Error(), "SCHEMA_MANAGER does not support local-ssd storage")
}

func TestValidateCreateSharedPoolPDDeploymentMode(t *testing.T) {
	tests := []struct {
		name       string
		sharedPool *entity.SharedPool
		wantCode   codes.Code
	}{
		{
			name: "unspecified defaults to microservice",
			sharedPool: &entity.SharedPool{SharedPool: apiv1.SharedPool{
				CapacityMode: apiv1.CapacityMode_RCU_DRIVEN,
			}},
			wantCode: codes.OK,
		},
		{
			name: "monolithic rcu driven is allowed",
			sharedPool: &entity.SharedPool{SharedPool: apiv1.SharedPool{
				CapacityMode:     apiv1.CapacityMode_RCU_DRIVEN,
				PdDeploymentMode: apiv1.PDDeploymentMode_PD_DEPLOYMENT_MODE_MONOLITHIC,
			}},
			wantCode: codes.OK,
		},
		{
			name: "monolithic topology driven is rejected",
			sharedPool: &entity.SharedPool{SharedPool: apiv1.SharedPool{
				CapacityMode:     apiv1.CapacityMode_TOPOLOGY_DRIVEN,
				PdDeploymentMode: apiv1.PDDeploymentMode_PD_DEPLOYMENT_MODE_MONOLITHIC,
			}},
			wantCode: codes.InvalidArgument,
		},
		{
			name: "monolithic rejects pd_tso component",
			sharedPool: &entity.SharedPool{SharedPool: apiv1.SharedPool{
				CapacityMode:     apiv1.CapacityMode_RCU_DRIVEN,
				PdDeploymentMode: apiv1.PDDeploymentMode_PD_DEPLOYMENT_MODE_MONOLITHIC,
				ComponentGroups: []*apiv1.Component{
					{ComponentType: apiv1.ComponentType_PD_TSO},
				},
			}},
			wantCode: codes.InvalidArgument,
		},
		{
			name: "monolithic rejects pd_scheduling component",
			sharedPool: &entity.SharedPool{SharedPool: apiv1.SharedPool{
				CapacityMode:     apiv1.CapacityMode_RCU_DRIVEN,
				PdDeploymentMode: apiv1.PDDeploymentMode_PD_DEPLOYMENT_MODE_MONOLITHIC,
				ComponentGroups: []*apiv1.Component{
					{ComponentType: apiv1.ComponentType_PD_SCHEDULING},
				},
			}},
			wantCode: codes.InvalidArgument,
		},
		{
			name: "monolithic rejects resource_manager component",
			sharedPool: &entity.SharedPool{SharedPool: apiv1.SharedPool{
				CapacityMode:     apiv1.CapacityMode_RCU_DRIVEN,
				PdDeploymentMode: apiv1.PDDeploymentMode_PD_DEPLOYMENT_MODE_MONOLITHIC,
				ComponentGroups: []*apiv1.Component{
					{ComponentType: apiv1.ComponentType_RESOURCE_MANAGER},
				},
			}},
			wantCode: codes.InvalidArgument,
		},
	}

	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			err := validateCreateSharedPoolPDDeploymentMode(tt.sharedPool)
			assert.Equal(t, tt.wantCode, status.Code(err))
		})
	}
}

func TestValidateCreateSharedPoolCapacityMode(t *testing.T) {
	tests := []struct {
		name     string
		mode     apiv1.CapacityMode
		wantCode codes.Code
	}{
		{
			name:     "unspecified is allowed",
			mode:     apiv1.CapacityMode_CAPACITY_MODE_UNSPECIFIED,
			wantCode: codes.OK,
		},
		{
			name:     "rcu driven is allowed",
			mode:     apiv1.CapacityMode_RCU_DRIVEN,
			wantCode: codes.OK,
		},
		{
			name:     "topology driven is allowed",
			mode:     apiv1.CapacityMode_TOPOLOGY_DRIVEN,
			wantCode: codes.OK,
		},
		{
			name:     "unknown capacity mode is rejected",
			mode:     apiv1.CapacityMode(99),
			wantCode: codes.InvalidArgument,
		},
	}

	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			sharedPool := &entity.SharedPool{SharedPool: apiv1.SharedPool{
				CapacityMode: tt.mode,
			}}

			err := validateCreateSharedPoolCapacityMode(sharedPool)
			assert.Equal(t, tt.wantCode, status.Code(err))
		})
	}
}

func TestNormalizeAndValidateCreateSharedPoolEnabledTiKVRemoteWorkers(t *testing.T) {
	tests := []struct {
		name        string
		sharedPool  *entity.SharedPool
		wantCode    codes.Code
		wantWorkers []apiv1.ComponentType
	}{
		{
			name: "empty is allowed for topology driven",
			sharedPool: &entity.SharedPool{SharedPool: apiv1.SharedPool{
				CapacityMode: apiv1.CapacityMode_TOPOLOGY_DRIVEN,
			}},
			wantCode:    codes.OK,
			wantWorkers: []apiv1.ComponentType{},
		},
		{
			name: "empty defaults for rcu driven",
			sharedPool: &entity.SharedPool{SharedPool: apiv1.SharedPool{
				CapacityMode: apiv1.CapacityMode_RCU_DRIVEN,
			}},
			wantCode: codes.OK,
			wantWorkers: []apiv1.ComponentType{
				apiv1.ComponentType_COPROCESSOR_WORKER,
				apiv1.ComponentType_TIKV_WORKER,
			},
		},
		{
			name:       "unspecified capacity mode defaults for rcu driven",
			sharedPool: &entity.SharedPool{SharedPool: apiv1.SharedPool{}},
			wantCode:   codes.OK,
			wantWorkers: []apiv1.ComponentType{
				apiv1.ComponentType_COPROCESSOR_WORKER,
				apiv1.ComponentType_TIKV_WORKER,
			},
		},
		{
			name: "worker only is allowed for rcu driven",
			sharedPool: &entity.SharedPool{SharedPool: apiv1.SharedPool{
				CapacityMode: apiv1.CapacityMode_RCU_DRIVEN,
				EnabledTikvRemoteWorkers: []apiv1.ComponentType{
					apiv1.ComponentType_TIKV_WORKER,
				},
			}},
			wantCode: codes.OK,
			wantWorkers: []apiv1.ComponentType{
				apiv1.ComponentType_TIKV_WORKER,
			},
		},
		{
			name: "duplicates are deduplicated and sorted",
			sharedPool: &entity.SharedPool{SharedPool: apiv1.SharedPool{
				CapacityMode: apiv1.CapacityMode_RCU_DRIVEN,
				EnabledTikvRemoteWorkers: []apiv1.ComponentType{
					apiv1.ComponentType_TIKV_WORKER,
					apiv1.ComponentType_TIKV_WORKER,
					apiv1.ComponentType_COPROCESSOR_WORKER,
				},
			}},
			wantCode: codes.OK,
			wantWorkers: []apiv1.ComponentType{
				apiv1.ComponentType_COPROCESSOR_WORKER,
				apiv1.ComponentType_TIKV_WORKER,
			},
		},
		{
			name: "coprocessor without tikv worker is rejected",
			sharedPool: &entity.SharedPool{SharedPool: apiv1.SharedPool{
				CapacityMode: apiv1.CapacityMode_RCU_DRIVEN,
				EnabledTikvRemoteWorkers: []apiv1.ComponentType{
					apiv1.ComponentType_COPROCESSOR_WORKER,
				},
			}},
			wantCode: codes.InvalidArgument,
		},
		{
			name: "unspecified component type is rejected",
			sharedPool: &entity.SharedPool{SharedPool: apiv1.SharedPool{
				CapacityMode: apiv1.CapacityMode_RCU_DRIVEN,
				EnabledTikvRemoteWorkers: []apiv1.ComponentType{
					apiv1.ComponentType_COMPONENT_TYPE_UNSPECIFIED,
				},
			}},
			wantCode: codes.InvalidArgument,
		},
		{
			name: "tidb worker is rejected",
			sharedPool: &entity.SharedPool{SharedPool: apiv1.SharedPool{
				CapacityMode: apiv1.CapacityMode_RCU_DRIVEN,
				EnabledTikvRemoteWorkers: []apiv1.ComponentType{
					apiv1.ComponentType_TIDB_WORKER,
				},
			}},
			wantCode: codes.InvalidArgument,
		},
		{
			name: "enabled workers are rejected for topology driven",
			sharedPool: &entity.SharedPool{SharedPool: apiv1.SharedPool{
				CapacityMode: apiv1.CapacityMode_TOPOLOGY_DRIVEN,
				EnabledTikvRemoteWorkers: []apiv1.ComponentType{
					apiv1.ComponentType_TIKV_WORKER,
				},
			}},
			wantCode: codes.InvalidArgument,
		},
		{
			name: "tikv only rejects explicit coprocessor worker component",
			sharedPool: &entity.SharedPool{SharedPool: apiv1.SharedPool{
				CapacityMode: apiv1.CapacityMode_RCU_DRIVEN,
				EnabledTikvRemoteWorkers: []apiv1.ComponentType{
					apiv1.ComponentType_TIKV_WORKER,
				},
				ComponentGroups: []*apiv1.Component{
					{ComponentType: apiv1.ComponentType_COPROCESSOR_WORKER},
				},
			}},
			wantCode: codes.InvalidArgument,
		},
	}

	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			err := normalizeAndValidateCreateSharedPoolEnabledTiKVRemoteWorkers(tt.sharedPool)
			assert.Equal(t, tt.wantCode, status.Code(err))
			if tt.wantCode == codes.OK {
				assert.Equal(t, tt.wantWorkers, tt.sharedPool.GetEnabledTikvRemoteWorkers())
			}
		})
	}
}

func TestEnsureTopologyDrivenTiFlashComponents(t *testing.T) {
	nodeSpecMap := map[string][]*entity.NodeSpec{
		"4C8G": {
			{Cpu: 4, MemoryGi: 8, AvailableCpu: 3500, AvailableMemoryMi: 7000, WithLocalDisk: false},
		},
	}

	t.Run("auto add tiflash write compute and learners when missing", func(t *testing.T) {
		components := []*entity.Component{
			{Id: "default", ComponentType: apiv1.ComponentType_PD, NodeQuota: &entity.NodeQuota{NodeSpec: &entity.NodeSpec{Cpu: 4, MemoryGi: 8}}},
			{Id: "default", ComponentType: apiv1.ComponentType_TIKV, NodeQuota: &entity.NodeQuota{NodeSpec: &entity.NodeSpec{Cpu: 4, MemoryGi: 8}}},
			{Id: "default", ComponentType: apiv1.ComponentType_TIKV_WORKER, NodeQuota: &entity.NodeQuota{NodeSpec: &entity.NodeSpec{Cpu: 4, MemoryGi: 8}}},
			{Id: "default", ComponentType: apiv1.ComponentType_TIDB_WORKER, NodeQuota: &entity.NodeQuota{NodeSpec: &entity.NodeSpec{Cpu: 4, MemoryGi: 8}}},
		}

		got, err := ensureTopologyDrivenTiFlashComponents(components, "default", nodeSpecMap, &entity.InitSharedPoolConfig{}, false)
		require.NoError(t, err)

		counts := map[apiv1.ComponentType]int{}
		for _, comp := range got {
			counts[comp.GetComponentType()]++
		}

		assert.Equal(t, 1, counts[apiv1.ComponentType_TIFLASH_WRITE])
		assert.Equal(t, 1, counts[apiv1.ComponentType_TIFLASH_COMPUTE])
		assert.Equal(t, 1, counts[apiv1.ComponentType_TIFLASH_LEARNER])
		assert.Equal(t, 1, counts[apiv1.ComponentType_TIFLASH_COMPUTE_LEARNER])

		for _, comp := range got {
			switch comp.GetComponentType() {
			case apiv1.ComponentType_TIFLASH_WRITE, apiv1.ComponentType_TIFLASH_COMPUTE:
				assert.Equal(t, uint32(0), comp.GetNodeQuota().GetNodeCount())
				assert.Equal(t, uint32(0), comp.GetNodeQuota().GetMinNodeCount())
				assert.Equal(t, uint32(0), comp.GetNodeQuota().GetMaxNodeCount())
			case apiv1.ComponentType_TIFLASH_LEARNER, apiv1.ComponentType_TIFLASH_COMPUTE_LEARNER:
				assert.Equal(t, apiv1.Component_UNAVAILABLE, comp.GetPhase())
			}
		}
	})

	t.Run("keep passed tiflash write compute and only add learners", func(t *testing.T) {
		components := []*entity.Component{
			{Id: "default", ComponentType: apiv1.ComponentType_TIFLASH_WRITE, NodeQuota: &entity.NodeQuota{NodeCount: 2, MinNodeCount: 0, MaxNodeCount: 4, NodeSpec: &entity.NodeSpec{Cpu: 4, MemoryGi: 8}}},
			{Id: "default", ComponentType: apiv1.ComponentType_TIFLASH_COMPUTE, NodeQuota: &entity.NodeQuota{NodeCount: 3, MinNodeCount: 0, MaxNodeCount: 6, NodeSpec: &entity.NodeSpec{Cpu: 4, MemoryGi: 8}}},
		}

		got, err := ensureTopologyDrivenTiFlashComponents(components, "default", nodeSpecMap, &entity.InitSharedPoolConfig{}, false)
		require.NoError(t, err)

		counts := map[apiv1.ComponentType]int{}
		for _, comp := range got {
			counts[comp.GetComponentType()]++
		}

		assert.Equal(t, 1, counts[apiv1.ComponentType_TIFLASH_WRITE])
		assert.Equal(t, 1, counts[apiv1.ComponentType_TIFLASH_COMPUTE])
		assert.Equal(t, 1, counts[apiv1.ComponentType_TIFLASH_LEARNER])
		assert.Equal(t, 1, counts[apiv1.ComponentType_TIFLASH_COMPUTE_LEARNER])

		for _, comp := range got {
			switch comp.GetComponentType() {
			case apiv1.ComponentType_TIFLASH_WRITE:
				assert.Equal(t, uint32(2), comp.GetNodeQuota().GetNodeCount())
			case apiv1.ComponentType_TIFLASH_COMPUTE:
				assert.Equal(t, uint32(3), comp.GetNodeQuota().GetNodeCount())
			}
		}
	})

	t.Run("columnar omits tiflash write", func(t *testing.T) {
		components := []*entity.Component{
			{Id: "default", ComponentType: apiv1.ComponentType_TIFLASH_COMPUTE, NodeQuota: &entity.NodeQuota{NodeCount: 1, NodeSpec: &entity.NodeSpec{Cpu: 4, MemoryGi: 8}}},
		}

		got, err := ensureTopologyDrivenTiFlashComponents(components, "default", nodeSpecMap, &entity.InitSharedPoolConfig{}, true)
		require.NoError(t, err)

		counts := map[apiv1.ComponentType]int{}
		for _, comp := range got {
			counts[comp.GetComponentType()]++
		}
		assert.Zero(t, counts[apiv1.ComponentType_TIFLASH_WRITE])
		assert.Equal(t, 1, counts[apiv1.ComponentType_TIFLASH_COMPUTE])
		assert.Equal(t, 1, counts[apiv1.ComponentType_TIFLASH_COMPUTE_LEARNER])
	})

	t.Run("columnar rejects explicit tiflash write", func(t *testing.T) {
		components := []*entity.Component{
			{Id: "default", ComponentType: apiv1.ComponentType_TIFLASH_WRITE},
		}

		_, err := ensureTopologyDrivenTiFlashComponents(components, "default", nodeSpecMap, &entity.InitSharedPoolConfig{}, true)
		require.Error(t, err)
		assert.Equal(t, codes.InvalidArgument, status.Code(err))
		assert.Contains(t, err.Error(), "must not contain TIFLASH_WRITE")
	})
}

func TestGenerateTopologyDrivenComponentsColumnar(t *testing.T) {
	nodeSpecMap := map[string][]*entity.NodeSpec{
		"8C16G": {{Cpu: 8, MemoryGi: 16, AvailableCpu: 7, AvailableMemoryMi: 15000}},
		"4C8G":  {{Cpu: 4, MemoryGi: 8, AvailableCpu: 3, AvailableMemoryMi: 7000}},
	}
	component := func(componentType apiv1.ComponentType, nodeCount uint32) *entity.Component {
		return &entity.Component{
			Id:            "default",
			ComponentType: componentType,
			NodeQuota: &entity.NodeQuota{
				NodeCount: nodeCount,
				NodeSpec:  &entity.NodeSpec{Cpu: 8, MemoryGi: 16},
			},
			Storages: []*entity.Storage{{Name: apiv1.Component_Storage_DATA, Type: entity.GP3, SizeGi: 100}},
		}
	}
	sharedPool := &entity.SharedPool{
		SharedPool: apiv1.SharedPool{
			Id:           "topology-columnar",
			CapacityMode: apiv1.CapacityMode_TOPOLOGY_DRIVEN,
			Provider:     &apiv1.Provider{Type: apiv1.ProviderType_AWS},
			TopologySpec: &apiv1.TopologySpec{Components: []*entity.Component{
				component(apiv1.ComponentType_PD, 3),
				component(apiv1.ComponentType_TIKV, 3),
				component(apiv1.ComponentType_TIKV_WORKER, 1),
				component(apiv1.ComponentType_TIDB_WORKER, 1),
				component(apiv1.ComponentType_TIFLASH_COMPUTE, 1),
				{
					ComponentType: apiv1.ComponentType_SCHEMA_MANAGER,
					NodeQuota: &entity.NodeQuota{
						NodeCount: 1,
						NodeSpec:  &entity.NodeSpec{Cpu: 4, MemoryGi: 8},
					},
				},
			}},
		},
		TiflashMode: mysqlschema.SharedPoolTiflashModeColumnar,
	}

	err := (&serverImpl{}).generateTopologyDrivenComponents(
		context.Background(), sharedPool, nil, nodeSpecMap, &entity.InitSharedPoolConfig{},
	)
	require.NoError(t, err)

	componentTypes := lo.Map(sharedPool.GetComponentGroups(), func(comp *entity.Component, _ int) apiv1.ComponentType {
		return comp.GetComponentType()
	})
	assert.NotContains(t, componentTypes, apiv1.ComponentType_TIFLASH_WRITE)
	assert.Contains(t, componentTypes, apiv1.ComponentType_TIFLASH_COMPUTE)
	assert.Contains(t, componentTypes, apiv1.ComponentType_SCHEMA_MANAGER)

	schemaManager, ok := lo.Find(sharedPool.GetComponentGroups(), func(comp *entity.Component) bool {
		return comp.GetComponentType() == apiv1.ComponentType_SCHEMA_MANAGER
	})
	require.True(t, ok)
	assert.Equal(t, uint32(1), schemaManager.GetNodeQuota().GetNodeCount())
	assert.Equal(t, uint32(1), schemaManager.GetNodeQuota().GetMinNodeCount())
	assert.Equal(t, uint32(1), schemaManager.GetNodeQuota().GetMaxNodeCount())
	assert.Equal(t, uint32(4), schemaManager.GetNodeQuota().GetNodeSpec().GetCpu())
	assert.Equal(t, uint32(8), schemaManager.GetNodeQuota().GetNodeSpec().GetMemoryGi())
	require.Len(t, schemaManager.GetStorages(), 1)
	assert.Equal(t, entity.GP3, schemaManager.GetStorages()[0].GetType())
	assert.Equal(t, uint32(20), schemaManager.GetStorages()[0].GetSizeGi())
}
