package server

import (
	"context"
	"fmt"
	"reflect"

	"github.com/samber/lo"
	apiv1 "github.com/tidbcloud/cluster-service-ng/api/cluster-service-ng/v1beta"
	"github.com/tidbcloud/cluster-service-ng/domain/entity"
	"github.com/tidbcloud/cluster-service-ng/pkg/utils"
	"github.com/tidbcloud/pkgv2/log"
	"github.com/tidbcloud/tiinf/monitor/tilog"
	"go.uber.org/zap"
	"google.golang.org/grpc/codes"
	"google.golang.org/grpc/status"
	"google.golang.org/protobuf/proto"
)

const (
	tiproxyPerCpuRu = 15000
	tidbPerCpuRu    = 5000
	tikvPerCpuRu    = 2500
	tiflashPerCpuRu = 9000

	// when creating a shared pool, to reduce costs,
	// we need to limit the maximum count of each component during initialization.
	maxInitTiDBCount    = 6
	maxInitTiProxyCount = 2
	maxInitTiKVCount    = 9
	maxInitTiFlashCount = 1
)

type schemaManagerComponentGenerationOptions struct {
	startOnCreate bool
}

// generatedShardPoolComponent is used to generate the component config for the shared pool
// only generate the default component group
// default component group means the component id is hash(shared pool id)
func (s *serverImpl) generatedSharedPoolComponent(
	ctx context.Context,
	sharedPool *entity.SharedPool,
	ru *entity.RequestUnit,
	nodeSpecMap map[string][]*entity.NodeSpec,
	ruConfig *entity.RuConfig,
	initConfig *entity.InitSharedPoolConfig,
	force bool,
	bootstrapList ...*SharedPoolBootstrapCapacity,
) error {
	var bootstrap *SharedPoolBootstrapCapacity
	if len(bootstrapList) > 0 {
		bootstrap = bootstrapList[0]
	}
	if bootstrap != nil {
		if ru != nil {
			return resourcePoolStatusError(codes.InvalidArgument, reasonInvalidBootstrapCapacityMode,
				"bootstrap_capacity must not be combined with request_unit")
		}
		inferredRange, err := deriveBootstrapRURange(bootstrap, ruConfig)
		if err != nil {
			return err
		}
		ru = &entity.RequestUnit{Min: inferredRange.Min, Max: inferredRange.Max}
		tilog.Info(ctx, "derived bootstrap RCU range from TiKV capacity",
			zap.String("sharedPoolId", sharedPool.GetId()),
			zap.Uint32("minRequestUnit", inferredRange.Min),
			zap.Uint32("maxRequestUnit", inferredRange.Max),
			zap.Bool("unbounded", inferredRange.Unbounded),
		)
	}
	if ru == nil {
		return status.Error(codes.InvalidArgument, "request unit is required to generate RCU-driven shared-pool components")
	}
	// Build the component map
	defaultComponentMap := make(map[entity.ComponentType]*entity.Component)
	for _, comp := range sharedPool.GetComponentGroups() {
		if comp.GetId() == utils.FnvHash(sharedPool.GetId()) {
			defaultComponentMap[comp.GetComponentType()] = comp
		}
	}

	// gen TiKV
	tikvComp, ok := defaultComponentMap[apiv1.ComponentType_TIKV]
	if !ok {
		// if tikv component is not found, create a new one
		tikvComp = &entity.Component{
			Id:            utils.FnvHash(sharedPool.GetId()),
			ComponentType: apiv1.ComponentType_TIKV,
			Phase:         apiv1.Component_CREATING,
		}
		sharedPool.ComponentGroups = append(sharedPool.ComponentGroups, tikvComp)
	}

	var tikvBootstrap *BootstrapComponentCapacity
	if bootstrap != nil {
		tikvBootstrap = bootstrap.TiKV
	}
	err := s.genDefaultTiKVComponent(ctx, sharedPool, tikvComp, ru, nodeSpecMap, ruConfig, initConfig, force, tikvBootstrap)
	if err != nil {
		tilog.Error(ctx, "failed to generate tikv component", zap.Error(err))
		return err
	}

	// gen TiFlash compute
	tiflashCNComp, ok := defaultComponentMap[apiv1.ComponentType_TIFLASH_COMPUTE]
	if !ok {
		// if tiflash cn component is not found, create a new one
		tiflashCNComp = &entity.Component{
			Id:            utils.FnvHash(sharedPool.GetId()),
			ComponentType: apiv1.ComponentType_TIFLASH_COMPUTE,
			Phase:         apiv1.Component_CREATING,
		}
		sharedPool.ComponentGroups = append(sharedPool.ComponentGroups, tiflashCNComp)
	}

	var computeBootstrap *BootstrapComponentCapacity
	if bootstrap != nil {
		computeBootstrap = bootstrap.TiFlashCompute
		if computeBootstrap == nil {
			computeBootstrap = &BootstrapComponentCapacity{NodeCount: 0}
		}
	}
	err = s.genDefaultTiFlashComponent(ctx, sharedPool, tiflashCNComp, ru, nodeSpecMap, ruConfig, initConfig, force, computeBootstrap)
	if err != nil {
		tilog.Error(ctx, "failed to generate tiflash component", zap.Error(err))
		return err
	}

	// gen TiFlash write
	// The columnar architecture does not need tiflash-write.
	if !entity.UsesTiFlashColumnar(sharedPool) {
		tiflashWNComp, ok := defaultComponentMap[apiv1.ComponentType_TIFLASH_WRITE]
		if !ok {
			// if tiflash wn component is not found, create a new one
			tiflashWNComp = &entity.Component{
				Id:            utils.FnvHash(sharedPool.GetId()),
				ComponentType: apiv1.ComponentType_TIFLASH_WRITE,
				Phase:         apiv1.Component_CREATING,
			}
			sharedPool.ComponentGroups = append(sharedPool.ComponentGroups, tiflashWNComp)
		}

		err = s.genDefaultTiFlashComponent(ctx, sharedPool, tiflashWNComp, ru, nodeSpecMap, ruConfig, initConfig, force)
		if err != nil {
			tilog.Error(ctx, "failed to generate tiflash component", zap.Error(err))
			return err
		}
	}

	// gen TiFlash learner
	_, ok = defaultComponentMap[apiv1.ComponentType_TIFLASH_LEARNER]
	if !ok {
		// if tiflash learner component is not found, create a new one
		// if found, do nothing
		tiflashLearnerComp := &entity.Component{
			Id:            utils.FnvHash(sharedPool.GetId()),
			ComponentType: apiv1.ComponentType_TIFLASH_LEARNER,
			Phase:         apiv1.Component_UNAVAILABLE,
		}
		sharedPool.ComponentGroups = append(sharedPool.ComponentGroups, tiflashLearnerComp)
	}

	// gen TiFlash compute learner
	_, ok = defaultComponentMap[apiv1.ComponentType_TIFLASH_COMPUTE_LEARNER]
	if !ok {
		// if tiflash compute learner component is not found, create a new one
		tiflashComputeLearnerComp := &entity.Component{
			Id:            utils.FnvHash(sharedPool.GetId()),
			ComponentType: apiv1.ComponentType_TIFLASH_COMPUTE_LEARNER,
			Phase:         apiv1.Component_UNAVAILABLE,
		}
		sharedPool.ComponentGroups = append(sharedPool.ComponentGroups, tiflashComputeLearnerComp)
	}

	// genTikvWorker
	tikvWorkerComp, ok := defaultComponentMap[apiv1.ComponentType_TIKV_WORKER]
	if !ok {
		// if tikv worker component is not found, create a new one
		tikvWorkerComp = &entity.Component{
			Id:            utils.FnvHash(sharedPool.GetId()),
			ComponentType: apiv1.ComponentType_TIKV_WORKER,
			Phase:         apiv1.Component_CREATING,
		}
		sharedPool.ComponentGroups = append(sharedPool.ComponentGroups, tikvWorkerComp)
	}

	err = s.genDefaultTiKVWorkerComponent(ctx, sharedPool, tikvWorkerComp, ru, nodeSpecMap, ruConfig, nil, force)
	if err != nil {
		tilog.Error(ctx, "failed to generate tikv worker component", zap.Error(err))
		return err
	}

	if entity.HasEnabledTiKVRemoteWorker(sharedPool.GetEnabledTikvRemoteWorkers(), apiv1.ComponentType_COPROCESSOR_WORKER) {
		// genCopWorker
		copWorkerComp, ok := defaultComponentMap[apiv1.ComponentType_COPROCESSOR_WORKER]
		if !ok {
			// if cop worker component is not found, create a new one
			copWorkerComp = &entity.Component{
				Id:            utils.FnvHash(sharedPool.GetId()),
				ComponentType: apiv1.ComponentType_COPROCESSOR_WORKER,
				Phase:         apiv1.Component_CREATING,
			}
			sharedPool.ComponentGroups = append(sharedPool.ComponentGroups, copWorkerComp)
		}

		err = s.genDefaultTiKVWorkerComponent(ctx, sharedPool, copWorkerComp, ru, nodeSpecMap, ruConfig, nil, force)
		if err != nil {
			tilog.Error(ctx, "failed to generate cop worker component", zap.Error(err))
			return err
		}
	}
	// When COPROCESSOR_WORKER is not enabled, TiKV still routes coprocessor work
	// to the default TiKV worker when rendering the TiKV group.

	// gen schema manager (columnar architecture only): a tikv-worker variant
	// that runs schema-manager and serves compaction.
	if entity.UsesTiFlashColumnar(sharedPool) {
		schemaManagerComp, ok := defaultComponentMap[apiv1.ComponentType_SCHEMA_MANAGER]
		if !ok {
			schemaManagerComp = &entity.Component{
				Id:            utils.FnvHash(sharedPool.GetId()),
				ComponentType: apiv1.ComponentType_SCHEMA_MANAGER,
				Phase:         apiv1.Component_CREATING,
			}
			sharedPool.ComponentGroups = append(sharedPool.ComponentGroups, schemaManagerComp)
		}

		schemaManagerOptions := &schemaManagerComponentGenerationOptions{
			startOnCreate: tiflashCNComp.GetNodeQuota().GetNodeCount() > 0,
		}
		err = s.genDefaultTiKVWorkerComponent(ctx, sharedPool, schemaManagerComp, ru, nodeSpecMap, ruConfig, schemaManagerOptions, force)
		if err != nil {
			tilog.Error(ctx, "failed to generate schema manager component", zap.Error(err))
			return err
		}
	}

	// last gen pd component
	pdComp, ok := defaultComponentMap[apiv1.ComponentType_PD]
	if !ok {
		// if pd component is not found, create a new one
		pdComp = &entity.Component{
			Id:            utils.FnvHash(sharedPool.GetId()),
			ComponentType: apiv1.ComponentType_PD,
			Phase:         apiv1.Component_CREATING,
		}
		sharedPool.ComponentGroups = append(sharedPool.ComponentGroups, pdComp)
	}

	err = s.genDefaultPDComponent(ctx, sharedPool, pdComp, ru, nodeSpecMap, ruConfig, force)
	if err != nil {
		tilog.Error(ctx, "failed to generate pd component", zap.Error(err))
		return err
	}

	if entity.GetEffectivePDDeploymentMode(sharedPool.GetPdDeploymentMode()) == apiv1.PDDeploymentMode_PD_DEPLOYMENT_MODE_MONOLITHIC {
		return s.generatedSharedPoolNonPDComponent(ctx, sharedPool, defaultComponentMap, ru, nodeSpecMap, ruConfig, force)
	}

	return s.generatedSharedPoolMicroservicePDComponent(ctx, sharedPool, defaultComponentMap, ru, nodeSpecMap, ruConfig, force)
}

func (s *serverImpl) generatedSharedPoolMicroservicePDComponent(
	ctx context.Context,
	sharedPool *entity.SharedPool,
	defaultComponentMap map[entity.ComponentType]*entity.Component,
	ru *entity.RequestUnit,
	nodeSpecMap map[string][]*entity.NodeSpec,
	ruConfig *entity.RuConfig,
	force bool,
) error {
	// gen pd tso component
	pdTsoComp, ok := defaultComponentMap[apiv1.ComponentType_PD_TSO]
	if !ok {
		// if pd component is not found, create a new one
		pdTsoComp = &entity.Component{
			Id:            utils.FnvHash(sharedPool.GetId()),
			ComponentType: apiv1.ComponentType_PD_TSO,
			Phase:         apiv1.Component_CREATING,
		}
		sharedPool.ComponentGroups = append(sharedPool.ComponentGroups, pdTsoComp)
	}

	err := s.genDefaultPDTsoComponent(ctx, sharedPool, pdTsoComp, ru, nodeSpecMap, ruConfig, force)
	if err != nil {
		tilog.Error(ctx, "failed to generate pd tso component", zap.Error(err))
		return err
	}

	// gen pd scheduling component
	pdScheComp, ok := defaultComponentMap[apiv1.ComponentType_PD_SCHEDULING]
	if !ok {
		// if pd component is not found, create a new one
		pdScheComp = &entity.Component{
			Id:            utils.FnvHash(sharedPool.GetId()),
			ComponentType: apiv1.ComponentType_PD_SCHEDULING,
			Phase:         apiv1.Component_CREATING,
		}
		sharedPool.ComponentGroups = append(sharedPool.ComponentGroups, pdScheComp)
	}

	err = s.genDefaultPDSchedulingComponent(ctx, sharedPool, pdScheComp, ru, nodeSpecMap, ruConfig, force)
	if err != nil {
		tilog.Error(ctx, "failed to generate pd scheduling component", zap.Error(err))
		return err
	}

	// gen default resource manager component
	// only create new component if feature flag is enabled, but always process existing component
	resourceManagerComp, ok := defaultComponentMap[apiv1.ComponentType_RESOURCE_MANAGER]
	if !ok && ruConfig.GetFeatureFlags().GetEnableResourceManager() {
		// if resource manager component is not found and feature flag is enabled, create a new one
		resourceManagerComp = &entity.Component{
			Id:            utils.FnvHash(sharedPool.GetId()),
			ComponentType: apiv1.ComponentType_RESOURCE_MANAGER,
			Phase:         apiv1.Component_CREATING,
		}
		sharedPool.ComponentGroups = append(sharedPool.ComponentGroups, resourceManagerComp)
	}

	if resourceManagerComp != nil {
		err = s.genDefaultResourceManagerComponent(ctx, sharedPool, resourceManagerComp, ru, nodeSpecMap, ruConfig, force)
		if err != nil {
			tilog.Error(ctx, "failed to generate resource manager component", zap.Error(err))
			return err
		}
	}

	return s.generatedSharedPoolNonPDComponent(ctx, sharedPool, defaultComponentMap, ru, nodeSpecMap, ruConfig, force)
}

func (s *serverImpl) generatedSharedPoolNonPDComponent(
	ctx context.Context,
	sharedPool *entity.SharedPool,
	defaultComponentMap map[entity.ComponentType]*entity.Component,
	ru *entity.RequestUnit,
	nodeSpecMap map[string][]*entity.NodeSpec,
	ruConfig *entity.RuConfig,
	force bool,
) error {
	// gen default tidb worker component
	tidbWorkerComp, ok := defaultComponentMap[apiv1.ComponentType_TIDB_WORKER]
	if !ok {
		// if tidb worker component is not found, create a new one
		tidbWorkerComp = &entity.Component{
			Id:            utils.FnvHash(sharedPool.GetId()),
			ComponentType: apiv1.ComponentType_TIDB_WORKER,
			Phase:         apiv1.Component_CREATING,
		}
		sharedPool.ComponentGroups = append(sharedPool.ComponentGroups, tidbWorkerComp)
	}

	err := s.genDefaultTiDBWorkerComponent(ctx, sharedPool, tidbWorkerComp, ru, nodeSpecMap, ruConfig, force)
	if err != nil {
		tilog.Error(ctx, "failed to generate tidb worker component", zap.Error(err))
		return err
	}

	if entity.HasSharedTiProxy(sharedPool) {
		tiproxyComp, ok := defaultComponentMap[apiv1.ComponentType_TIPROXY]
		if !ok {
			tiproxyComp = &entity.Component{
				Id:            utils.FnvHash(sharedPool.GetId()),
				ComponentType: apiv1.ComponentType_TIPROXY,
				Phase:         apiv1.Component_CREATING,
			}
			sharedPool.ComponentGroups = append(sharedPool.ComponentGroups, tiproxyComp)
		}

		err = s.genDefaultTiProxyComponent(ctx, tiproxyComp, ru, nodeSpecMap, ruConfig, force)
		if err != nil {
			tilog.Error(ctx, "failed to generate shared tiproxy component", zap.Error(err))
			return err
		}
	}

	for _, component := range sharedPool.GetComponentGroups() {
		err := s.checkComponent(component)
		if err != nil {
			return err
		}
	}

	return nil
}

// ru: per tidb group request unit
// totalRu: per cluster request unit
func (s *serverImpl) genDefaultTiDBComponent(ctx context.Context, sharedPool *entity.SharedPool, tidbComp *entity.Component, ru *entity.RequestUnit, nodeSpecMap map[string][]*entity.NodeSpec, ruConfig *entity.RuConfig, force bool) error {
	// set tidb node quota
	newNodeQuota, err := s.genDefaultTiDBQuota(ctx, ru, nodeSpecMap, ruConfig, tidbComp.GetPhase())
	if err != nil {
		tilog.Error(ctx, "failed to generate tidb node quota", zap.Error(err))
		return err
	}

	oldNodeQuota := tidbComp.GetNodeQuota()
	tidbComp.NodeQuota = newNodeQuota
	// defaultNodeCount gives each RU tier an initial baseline. After creation,
	// node count changes are owned by autoscale; re-generation only fills TiDB
	// up to the baseline and keeps an existing larger count.
	tidbComp.GetNodeQuota().NodeCount = lo.Max([]uint32{oldNodeQuota.GetNodeCount(), newNodeQuota.GetNodeCount()})

	tilog.Info(ctx, "tidb component node quota",
		zap.String("component", entity.GetComponentGroupName(tidbComp)),
		zap.Any("select NodeQuota", tidbComp.GetNodeQuota()),
		zap.Any("oldNodeQuota", oldNodeQuota),
		zap.Any("newNodeQuota", newNodeQuota),
	)

	if tidbComp.GetPhase() == apiv1.Component_CREATING && len(tidbComp.GetStorages()) == 0 {
		// set tidb storage
		storages, err := s.genDefaultTiDBStorages(sharedPool.GetProvider().GetType(), ru)
		if err != nil {
			tilog.Error(ctx, "failed to generate tidb storages", zap.Error(err))
			return err
		}
		tidbComp.Storages = storages
		tilog.Info(ctx, "tidb component storages",
			zap.String("component", entity.GetComponentGroupName(tidbComp)),
			zap.Any("storages", tidbComp.GetStorages()),
		)
	}

	if tidbComp.GetPhase() != apiv1.Component_CREATING && !reflect.DeepEqual(tidbComp.GetNodeQuota(), oldNodeQuota) {
		if tidbComp.GetPhase() != apiv1.Component_AVAILABLE {
			if !force {
				tilog.Warn(ctx, "tidb component is not available",
					zap.String("component", entity.GetComponentGroupName(tidbComp)),
				)
				return status.Errorf(codes.FailedPrecondition, "tidb %s is not available ", entity.GetComponentGroupName(tidbComp))
			}
			tilog.Warn(ctx, "force to modify tidb component which is not available",
				zap.String("component", entity.GetComponentGroupName(tidbComp)),
			)
		}

		// if tidb node quota is changed, we need to update the tidb phase
		tidbComp.Phase = apiv1.Component_MODIFYING
		tilog.Info(ctx, "tidb component use new node quota or storages",
			zap.String("component", entity.GetComponentGroupName(tidbComp)),
		)
	}

	return nil
}

// totalRu: all tidbgroup ru in the shared pool
func (s *serverImpl) genDefaultTiDBWorkerComponent(ctx context.Context, _ *entity.SharedPool, tidbWorkerComp *entity.Component, totalRu *entity.RequestUnit, nodeSpecMap map[string][]*entity.NodeSpec, ruConfig *entity.RuConfig, force bool) error {
	// set tidb node quota
	newNodeQuota, err := s.genDefaultTiDBWorkerQuota(ctx, totalRu, nodeSpecMap, ruConfig, tidbWorkerComp.GetPhase())
	if err != nil {
		tilog.Error(ctx, "failed to generate tidb node quota", zap.Error(err))
		return err
	}

	oldNodeQuota := tidbWorkerComp.GetNodeQuota()
	tidbWorkerComp.NodeQuota = compareNodeQuota(tidbWorkerComp.GetNodeQuota(), newNodeQuota)

	tilog.Info(ctx, "tidb worker component node quota",
		zap.String("component", entity.GetComponentGroupName(tidbWorkerComp)),
		zap.Any("select NodeQuota", tidbWorkerComp.GetNodeQuota()),
		zap.Any("oldNodeQuota", oldNodeQuota),
		zap.Any("newNodeQuota", newNodeQuota),
	)

	if tidbWorkerComp.GetPhase() != apiv1.Component_CREATING && !reflect.DeepEqual(tidbWorkerComp.GetNodeQuota(), oldNodeQuota) {
		if tidbWorkerComp.GetPhase() != apiv1.Component_AVAILABLE {
			if !force {
				tilog.Warn(ctx, "tidb worker component is not available",
					zap.String("component", entity.GetComponentGroupName(tidbWorkerComp)),
				)
				return status.Errorf(codes.FailedPrecondition, "tidb worker %s is not available ", entity.GetComponentGroupName(tidbWorkerComp))
			}
			tilog.Warn(ctx, "force to modify tidb worker component which is not available",
				zap.String("component", entity.GetComponentGroupName(tidbWorkerComp)),
			)
		}

		// if tidb node quota is changed, we need to update the tidb phase
		tidbWorkerComp.Phase = apiv1.Component_MODIFYING
		tilog.Info(ctx, "tidb worker component use new node quota or storages",
			zap.String("component", entity.GetComponentGroupName(tidbWorkerComp)),
		)
	}

	return nil
}

// totalRu: per cluster request unit
func (s *serverImpl) genDefaultTiProxyComponent(ctx context.Context, tiproxyComp *entity.Component, totalRu *entity.RequestUnit, nodeSpecMap map[string][]*entity.NodeSpec, ruConfig *entity.RuConfig, force bool) error {
	// set tidb node quota
	newNodeQuota, err := s.genDefaultTiProxyQuota(ctx, totalRu, nodeSpecMap, ruConfig, tiproxyComp.GetPhase())
	if err != nil {
		tilog.Error(ctx, "failed to generate tiproxy node quota", zap.Error(err))
		return err
	}

	var oldNodeQuota *entity.NodeQuota
	if tiproxyComp.GetNodeQuota() != nil {
		// TiProxy update submission depends on detecting count-only baseline
		// changes. compareNodeQuota may mutate the current quota in-place, so
		// keep an immutable snapshot for change detection.
		oldNodeQuota = proto.Clone(tiproxyComp.GetNodeQuota()).(*entity.NodeQuota)
	}
	// defaultNodeCount gives each RU tier an initial baseline. After creation,
	// node count changes are owned by autoscale; compareNodeQuota only fills
	// TiProxy up to the baseline and keeps an existing larger count.
	tiproxyComp.NodeQuota = compareNodeQuota(tiproxyComp.GetNodeQuota(), newNodeQuota)

	tilog.Info(ctx, "tidb tiproxy component node quota",
		zap.String("component", entity.GetComponentGroupName(tiproxyComp)),
		zap.Any("select NodeQuota", tiproxyComp.GetNodeQuota()),
		zap.Any("oldNodeQuota", oldNodeQuota),
		zap.Any("newNodeQuota", newNodeQuota),
	)

	if tiproxyComp.GetPhase() != apiv1.Component_CREATING && !reflect.DeepEqual(tiproxyComp.GetNodeQuota(), oldNodeQuota) {
		if tiproxyComp.GetPhase() != apiv1.Component_AVAILABLE {
			if !force {
				tilog.Warn(ctx, "tiproxy component is not available",
					zap.String("component", entity.GetComponentGroupName(tiproxyComp)),
				)
				return status.Errorf(codes.FailedPrecondition, "tiproxy %s is not available ", entity.GetComponentGroupName(tiproxyComp))
			}
			tilog.Warn(ctx, "force to modify tiproxy component which is not available",
				zap.String("component", entity.GetComponentGroupName(tiproxyComp)),
			)
		}

		// if tidb node quota is changed, we need to update the tidb phase
		tiproxyComp.Phase = apiv1.Component_MODIFYING
		tilog.Info(ctx, "tiproxy component use new node quota",
			zap.String("component", entity.GetComponentGroupName(tiproxyComp)),
		)
	}

	return nil
}

// totalRu: sum of all tidbgroup ru in the shared pool
func (s *serverImpl) genDefaultPDComponent(ctx context.Context, sharedPool *entity.SharedPool, pdComp *entity.Component, totalRu *entity.RequestUnit, nodeSpecMap map[string][]*entity.NodeSpec, ruConfig *entity.RuConfig, force bool) error {
	// set pd node quota
	newNodeQuota, err := s.genDefaultPDQuota(ctx, totalRu, nodeSpecMap, ruConfig, pdComp.GetPhase())
	if err != nil {
		tilog.Error(ctx, "failed to generate pd node quota", zap.Error(err))
		return err
	}
	oldNodeQuota := pdComp.GetNodeQuota()

	pdComp.NodeQuota = compareNodeQuota(pdComp.GetNodeQuota(), newNodeQuota)

	tilog.Info(ctx, "pd tiproxy component node quota",
		zap.String("component", entity.GetComponentGroupName(pdComp)),
		zap.Any("select NodeQuota", pdComp.GetNodeQuota()),
		zap.Any("oldNodeQuota", oldNodeQuota),
		zap.Any("newNodeQuota", newNodeQuota),
	)

	// pd not need gen new storage
	// only first time to set storage
	if pdComp.GetPhase() == apiv1.Component_CREATING && len(pdComp.GetStorages()) == 0 {
		// set pd storage
		storages, err := s.genDefaultPDStorages(sharedPool.GetProvider().GetType(), totalRu)
		if err != nil {
			tilog.Error(ctx, "failed to generate pd storages", zap.Error(err))
			return err
		}
		pdComp.Storages = storages
		tilog.Info(ctx, "pd component storages",
			zap.String("component", entity.GetComponentGroupName(pdComp)),
			zap.Any("storages", pdComp.GetStorages()),
		)
	}

	tilog.Info(ctx, "pd component storages",
		zap.String("component", entity.GetComponentGroupName(pdComp)),
		zap.Any("select Storages", pdComp.GetStorages()),
		zap.Any("oldStorages", pdComp.GetStorages()),
		zap.Any("newStorages by ru", pdComp.GetStorages()),
	)

	if pdComp.GetPhase() != apiv1.Component_CREATING && !reflect.DeepEqual(pdComp.GetNodeQuota(), oldNodeQuota) {
		if pdComp.GetPhase() != apiv1.Component_AVAILABLE {
			if !force {
				tilog.Warn(ctx, "pd component is not available",
					zap.String("component", entity.GetComponentGroupName(pdComp)),
				)
				return status.Errorf(codes.FailedPrecondition, "pd %s is not available ", entity.GetComponentGroupName(pdComp))
			}
			tilog.Warn(ctx, "force to modify pd component which is not available",
				zap.String("component", entity.GetComponentGroupName(pdComp)),
			)
		}

		// if pd node quota is changed, we need to update the pd phase
		pdComp.Phase = apiv1.Component_MODIFYING
		tilog.Info(ctx, "pd component use new node quota or storages",
			zap.String("component", entity.GetComponentGroupName(pdComp)),
		)
	}

	return nil
}

// totalRu: sum of all tidbgroup ru in the shared pool
func (s *serverImpl) genDefaultPDTsoComponent(ctx context.Context, _ *entity.SharedPool, pdTsoComp *entity.Component, totalRu *entity.RequestUnit, nodeSpecMap map[string][]*entity.NodeSpec, ruConfig *entity.RuConfig, force bool) error {
	// set pd node quota
	newNodeQuota, err := s.genDefaultPDTsoQuota(ctx, totalRu, nodeSpecMap, ruConfig, pdTsoComp.GetPhase())
	if err != nil {
		tilog.Error(ctx, "failed to generate pd node quota", zap.Error(err))
		return err
	}
	oldNodeQuota := pdTsoComp.GetNodeQuota()

	pdTsoComp.NodeQuota = compareNodeQuota(pdTsoComp.GetNodeQuota(), newNodeQuota)
	if oldNodeQuota != nil {
		// The new default min only applies to newly created components.
		// Existing pools may still regenerate node spec/count through this path.
		pdTsoComp.GetNodeQuota().MinNodeCount = oldNodeQuota.GetMinNodeCount()
	}

	tilog.Info(ctx, "pd tso component node quota",
		zap.String("component", entity.GetComponentGroupName(pdTsoComp)),
		zap.Any("select NodeQuota", pdTsoComp.GetNodeQuota()),
		zap.Any("oldNodeQuota", oldNodeQuota),
		zap.Any("newNodeQuota", newNodeQuota),
	)

	// pd tso not need not need gen new storage

	if pdTsoComp.GetPhase() != apiv1.Component_CREATING && !reflect.DeepEqual(pdTsoComp.GetNodeQuota(), oldNodeQuota) {

		if pdTsoComp.GetPhase() != apiv1.Component_AVAILABLE {
			if !force {
				tilog.Warn(ctx, "pd tso component is not available",
					zap.String("component", entity.GetComponentGroupName(pdTsoComp)),
				)
				return status.Errorf(codes.FailedPrecondition, "pd tso %s is not available ", entity.GetComponentGroupName(pdTsoComp))
			}
			tilog.Warn(ctx, "force to modify pd tso component which is not available",
				zap.String("component", entity.GetComponentGroupName(pdTsoComp)),
			)
		}

		// if pd node quota is changed, we need to update the pd phase
		pdTsoComp.Phase = apiv1.Component_MODIFYING
		tilog.Info(ctx, "pd tso component use new node quota",
			zap.String("component", entity.GetComponentGroupName(pdTsoComp)),
		)
	}

	return nil
}

// totalRu: sum of all tidbgroup ru in the shared pool
func (s *serverImpl) genDefaultPDSchedulingComponent(ctx context.Context, _ *entity.SharedPool, pdScheComp *entity.Component, totalRu *entity.RequestUnit, nodeSpecMap map[string][]*entity.NodeSpec, ruConfig *entity.RuConfig, force bool) error {
	// set pd node quota
	newNodeQuota, err := s.genDefaultPDSchedulingQuota(ctx, totalRu, nodeSpecMap, ruConfig, pdScheComp.GetPhase())
	if err != nil {
		tilog.Error(ctx, "failed to generate pd node quota", zap.Error(err))
		return err
	}
	oldNodeQuota := pdScheComp.GetNodeQuota()

	pdScheComp.NodeQuota = compareNodeQuota(pdScheComp.GetNodeQuota(), newNodeQuota)
	if oldNodeQuota != nil {
		// The new default min only applies to newly created components.
		// Existing pools may still regenerate node spec/count through this path.
		pdScheComp.GetNodeQuota().MinNodeCount = oldNodeQuota.GetMinNodeCount()
	}

	tilog.Info(ctx, "pd scheduling component node quota",
		zap.String("component", entity.GetComponentGroupName(pdScheComp)),
		zap.Any("select NodeQuota", pdScheComp.GetNodeQuota()),
		zap.Any("oldNodeQuota", oldNodeQuota),
		zap.Any("newNodeQuota", newNodeQuota),
	)

	// pd Scheduling not need not need gen new storage

	if pdScheComp.GetPhase() != apiv1.Component_CREATING && !reflect.DeepEqual(pdScheComp.GetNodeQuota(), oldNodeQuota) {

		if pdScheComp.GetPhase() != apiv1.Component_AVAILABLE {
			if !force {
				tilog.Warn(ctx, "pd scheduling component is not available",
					zap.String("component", entity.GetComponentGroupName(pdScheComp)),
				)
				return status.Errorf(codes.FailedPrecondition, "pd scheduling %s is not available ", entity.GetComponentGroupName(pdScheComp))
			}
			tilog.Warn(ctx, "force to modify pd scheduling component which is not available",
				zap.String("component", entity.GetComponentGroupName(pdScheComp)),
			)
		}
		// if pd node quota is changed, we need to update the pd phase
		pdScheComp.Phase = apiv1.Component_MODIFYING
		tilog.Info(ctx, "pd scheduling component use new node quota",
			zap.String("component", entity.GetComponentGroupName(pdScheComp)),
		)
	}

	return nil
}

// totalRu: sum of all tidbgroup ru in the shared pool
func (s *serverImpl) genDefaultTiKVComponent(
	ctx context.Context,
	sharedPool *entity.SharedPool,
	tikvComp *entity.Component,
	totalRu *entity.RequestUnit,
	nodeSpecMap map[string][]*entity.NodeSpec,
	ruConfig *entity.RuConfig,
	initConfig *entity.InitSharedPoolConfig,
	force bool,
	bootstrapList ...*BootstrapComponentCapacity,
) error {
	// set tikv node quota

	// get tikv data disk type
	// only aws/alicloud support local ssd now
	dataWithLocalDisk := initConfig != nil && initConfig.GetTikvDataStorage() != nil && initConfig.GetTikvDataStorage().GetType() == entity.LocalSSD

	for _, storage := range tikvComp.GetStorages() {
		if storage.GetName() == apiv1.Component_Storage_DATA && storage.GetType() == entity.LocalSSD {
			dataWithLocalDisk = true
			break
		}
	}

	newNodeQuota, err := s.genDefaultTiKVQuota(ctx, totalRu, nodeSpecMap, ruConfig, dataWithLocalDisk, tikvComp.GetPhase())
	if err != nil {
		tilog.Error(ctx, "failed to generate tikv node quota", zap.Error(err))
		return err
	}

	oldNodeQuota := tikvComp.GetNodeQuota()

	tikvComp.NodeQuota = compareNodeQuota(tikvComp.GetNodeQuota(), newNodeQuota)
	if tikvComp.GetPhase() == apiv1.Component_CREATING && len(bootstrapList) > 0 {
		applyBootstrapComponentCapacity(tikvComp.NodeQuota, bootstrapList[0])
	}

	tilog.Info(ctx, "tikv component node quota",
		zap.String("component", entity.GetComponentGroupName(tikvComp)),
		zap.Any("select NodeQuota", tikvComp.GetNodeQuota()),
		zap.Any("oldNodeQuota", oldNodeQuota),
		zap.Any("newNodeQuota by ru", newNodeQuota),
	)

	// if new tikv and storages not set yet
	if tikvComp.GetPhase() == apiv1.Component_CREATING && len(tikvComp.GetStorages()) == 0 {
		if initConfig == nil {
			return status.Errorf(codes.FailedPrecondition, "init config is required to generate tikv storages for new component")
		}
		// gen new storage
		newStorages, err := s.genDefaultTiKVStorages(
			sharedPool.GetProvider().GetType(),
			tikvComp.GetNodeQuota().GetNodeSpec(),
			initConfig,
		)
		if err != nil {
			tilog.Error(ctx, "failed to generate tikv storages", zap.Error(err))
			return err
		}
		tikvComp.Storages = newStorages

		tilog.Info(ctx, "tikv component storages",
			zap.String("component", entity.GetComponentGroupName(tikvComp)),
			zap.Any("storages", tikvComp.GetStorages()),
		)
	}

	if tikvComp.GetPhase() != apiv1.Component_CREATING &&
		(!reflect.DeepEqual(tikvComp.GetNodeQuota(), oldNodeQuota)) {

		if tikvComp.GetPhase() != apiv1.Component_AVAILABLE {
			if !force {
				tilog.Warn(ctx, "tikv component is not available",
					zap.String("component", entity.GetComponentGroupName(tikvComp)),
				)
				return status.Errorf(codes.FailedPrecondition, "tikv %s is not available ", entity.GetComponentGroupName(tikvComp))
			}
			tilog.Warn(ctx, "force to modify tikv component which is not available",
				zap.String("component", entity.GetComponentGroupName(tikvComp)),
			)
		}

		tikvComp.Phase = apiv1.Component_MODIFYING
		tilog.Info(ctx, "tikv component use new node quota or storages",
			zap.String("component", entity.GetComponentGroupName(tikvComp)))
	}

	return nil
}

// totalRu: sum of all tidbgroup ru in the shared pool
func (s *serverImpl) genDefaultTiFlashComponent(
	ctx context.Context,
	sharedPool *entity.SharedPool,
	tiflashComp *entity.Component,
	totalRu *entity.RequestUnit,
	nodeSpecMap map[string][]*entity.NodeSpec,
	ruConfig *entity.RuConfig,
	initConfig *entity.InitSharedPoolConfig,
	force bool,
	bootstrapList ...*BootstrapComponentCapacity,
) error {
	var (
		newNodeQuota *entity.NodeQuota
		err          error
	)
	// set tiflash node quota
	switch tiflashComp.GetComponentType() {
	case apiv1.ComponentType_TIFLASH_COMPUTE:

		// get tiflash cn data disk type
		// only aws support local ssd now
		dataWithLocalDisk := false
		if initConfig != nil && initConfig.GetTiflashCnStorage() != nil && initConfig.GetTiflashCnStorage().GetType() == entity.LocalSSD {
			dataWithLocalDisk = true
		}

		for _, storage := range tiflashComp.GetStorages() {
			if storage.GetName() == apiv1.Component_Storage_DATA && storage.GetType() == entity.LocalSSD {
				dataWithLocalDisk = true
				break
			}
		}

		newNodeQuota, err = s.genDefaultTiFlashComputeQuota(ctx, totalRu, nodeSpecMap, ruConfig, dataWithLocalDisk, tiflashComp.GetPhase())
		if err != nil {
			tilog.Error(ctx, "failed to generate tiflash compute node quota", zap.Error(err))
			return err
		}
	case apiv1.ComponentType_TIFLASH_WRITE:
		newNodeQuota, err = s.genDefaultTiFlashWriteQuota(ctx, totalRu, nodeSpecMap, ruConfig, tiflashComp.GetPhase())
		if err != nil {
			tilog.Error(ctx, "failed to generate tiflash write node quota", zap.Error(err))
			return err
		}
	default:
		return status.Errorf(codes.InvalidArgument, "unsupported tiflash component type %s", tiflashComp.GetComponentType())
	}

	oldNodeQuota := tiflashComp.GetNodeQuota()

	tiflashComp.NodeQuota = compareNodeQuota(tiflashComp.GetNodeQuota(), newNodeQuota)
	if tiflashComp.GetPhase() == apiv1.Component_CREATING && len(bootstrapList) > 0 {
		applyBootstrapComponentCapacity(tiflashComp.NodeQuota, bootstrapList[0])
	}
	tilog.Info(ctx, "tiflash component node quota",
		zap.String("component", entity.GetComponentGroupName(tiflashComp)),
		zap.Any("select NodeQuota", tiflashComp.GetNodeQuota()),
		zap.Any("oldNodeQuota", oldNodeQuota),
		zap.Any("newNodeQuota by ru", newNodeQuota),
	)

	if tiflashComp.GetPhase() == apiv1.Component_CREATING && len(tiflashComp.GetStorages()) == 0 {
		if initConfig == nil {
			return status.Errorf(codes.FailedPrecondition, "init config is required to generate tiflash storages for new component")
		}
		// set tiflash storage

		var storages []*entity.Storage

		switch tiflashComp.GetComponentType() {
		case apiv1.ComponentType_TIFLASH_COMPUTE:

			storages, err = s.genDefaultTiFlashCNStorages(
				sharedPool.GetProvider().GetType(),
				tiflashComp.GetNodeQuota().GetNodeSpec(),
				initConfig,
			)
			if err != nil {
				tilog.Error(ctx, "failed to generate tiflash compute node storages", zap.Error(err))
				return err
			}
		case apiv1.ComponentType_TIFLASH_WRITE:
			storages, err = s.genDefaultTiFlashWNStorages(
				sharedPool.GetProvider().GetType(),
				initConfig,
			)
			if err != nil {
				tilog.Error(ctx, "failed to generate tiflash write node storages", zap.Error(err))
				return err
			}
		default:
			return status.Errorf(codes.InvalidArgument, "unsupported tiflash component type %s", tiflashComp.GetComponentType())
		}

		tiflashComp.Storages = storages
		tilog.Info(ctx, "tiflash component storages",
			zap.String("component", entity.GetComponentGroupName(tiflashComp)),
			zap.Any("storages", tiflashComp.GetStorages()),
		)
	}

	if tiflashComp.GetPhase() != apiv1.Component_CREATING &&
		(!reflect.DeepEqual(tiflashComp.GetNodeQuota(), oldNodeQuota)) {

		if tiflashComp.GetPhase() != apiv1.Component_AVAILABLE {
			if !force {
				tilog.Warn(ctx, "tiflash component is not available",
					zap.String("component", entity.GetComponentGroupName(tiflashComp)),
				)
				return status.Errorf(codes.FailedPrecondition, "tiflash %s is not available ", entity.GetComponentGroupName(tiflashComp))
			}
			tilog.Warn(ctx, "force to modify tiflash component which is not available",
				zap.String("component", entity.GetComponentGroupName(tiflashComp)),
			)
		}

		tiflashComp.Phase = apiv1.Component_MODIFYING
		tilog.Info(ctx, "tiflash component use new node quota or storages",
			zap.String("component", entity.GetComponentGroupName(tiflashComp)),
		)
	}

	return nil
}

func applyBootstrapComponentCapacity(quota *entity.NodeQuota, bootstrap *BootstrapComponentCapacity) {
	if quota == nil || bootstrap == nil {
		return
	}
	if bootstrap.NodeSpec != nil {
		quota.NodeSpec = proto.Clone(bootstrap.NodeSpec).(*entity.NodeSpec)
	}
	quota.NodeCount = bootstrap.NodeCount
}

// totalRu: sum of all tidbgroup ru in the shared pool
func (s *serverImpl) genDefaultTiKVWorkerComponent(ctx context.Context, sharedPool *entity.SharedPool, tikvWorkerComp *entity.Component, totalRu *entity.RequestUnit, nodeSpecMap map[string][]*entity.NodeSpec, ruConfig *entity.RuConfig, schemaManagerOptions *schemaManagerComponentGenerationOptions, force bool) error {
	// set tikvWorker node quota
	var (
		newNodeQuota *entity.NodeQuota
		err          error
	)
	switch tikvWorkerComp.GetComponentType() {

	case apiv1.ComponentType_TIKV_WORKER:
		newNodeQuota, err = s.genDefaultTiKVWorkerQuota(ctx, totalRu, nodeSpecMap, ruConfig, tikvWorkerComp.GetPhase())
		if err != nil {
			tilog.Error(ctx, "failed to generate tikvWorker node quota", zap.Error(err))
			return err
		}
	case apiv1.ComponentType_COPROCESSOR_WORKER:
		newNodeQuota, err = s.genDefaultCoprocessorWorkerQuota(ctx, totalRu, nodeSpecMap, ruConfig, tikvWorkerComp.GetPhase())
		if err != nil {
			tilog.Error(ctx, "failed to generate copWorker node quota", zap.Error(err))
			return err
		}
	case apiv1.ComponentType_SCHEMA_MANAGER:
		startSchemaManagerOnCreate := schemaManagerOptions != nil && schemaManagerOptions.startOnCreate
		newNodeQuota, err = s.genDefaultSchemaManagerQuota(ctx, nodeSpecMap, tikvWorkerComp.GetPhase(), startSchemaManagerOnCreate)
		if err != nil {
			tilog.Error(ctx, "failed to generate schema manager node quota", zap.Error(err))
			return err
		}
	default:
		return status.Errorf(codes.InvalidArgument, "unsupported tikv worker component type %s", tikvWorkerComp.GetComponentType())
	}

	oldNodeQuota := tikvWorkerComp.GetNodeQuota()

	tikvWorkerComp.NodeQuota = compareNodeQuota(tikvWorkerComp.GetNodeQuota(), newNodeQuota)

	tilog.Info(ctx, "tikv worker component node quota",
		zap.String("component", entity.GetComponentGroupName(tikvWorkerComp)),
		zap.Any("select NodeQuota", tikvWorkerComp.GetNodeQuota()),
		zap.Any("oldNodeQuota", oldNodeQuota),
		zap.Any("newNodeQuota by ru", newNodeQuota),
	)

	if tikvWorkerComp.GetPhase() == apiv1.Component_CREATING && len(tikvWorkerComp.GetStorages()) == 0 {
		switch tikvWorkerComp.GetComponentType() {
		case apiv1.ComponentType_COPROCESSOR_WORKER:
			storages, err := s.genDefaultCoprocessorWorkerStorages(sharedPool.GetProvider().GetType())
			if err != nil {
				tilog.Error(ctx, "failed to generate coprocessor worker storages", zap.Error(err))
				return err
			}
			tikvWorkerComp.Storages = storages
		case apiv1.ComponentType_SCHEMA_MANAGER:
			storages, err := s.genDefaultSchemaManagerStorages(sharedPool.GetProvider().GetType())
			if err != nil {
				tilog.Error(ctx, "failed to generate schema manager storages", zap.Error(err))
				return err
			}
			tikvWorkerComp.Storages = storages
		}
		tilog.Info(ctx, "tikv worker component storages",
			zap.String("component", entity.GetComponentGroupName(tikvWorkerComp)),
			zap.Any("storages", tikvWorkerComp.GetStorages()),
		)
	}

	if tikvWorkerComp.GetPhase() != apiv1.Component_CREATING &&
		!reflect.DeepEqual(tikvWorkerComp.GetNodeQuota(), oldNodeQuota) {

		if tikvWorkerComp.GetPhase() != apiv1.Component_AVAILABLE {
			if !force {
				tilog.Warn(ctx, "tikv worker component is not available",
					zap.String("component", entity.GetComponentGroupName(tikvWorkerComp)),
				)
				return status.Errorf(codes.FailedPrecondition, "tikv worker %s is not available ", entity.GetComponentGroupName(tikvWorkerComp))
			}
			tilog.Warn(ctx, "force to modify tikv worker component which is not available",
				zap.String("component", entity.GetComponentGroupName(tikvWorkerComp)),
			)
		}

		tikvWorkerComp.Phase = apiv1.Component_MODIFYING
		tilog.Info(ctx, "tikv worker component use new node quota",
			zap.String("component", entity.GetComponentGroupName(tikvWorkerComp)),
		)
	}
	return nil
}

// NodeQuotaConfig holds configuration for building a NodeQuota.
// This struct simplifies the buildNodeQuota function parameters.
type NodeQuotaConfig struct {
	// CalculatedCount is the node count calculated based on RU
	CalculatedCount uint32
	// DefaultNodeCount is the optional per-RU-tier initial baseline. buildNodeQuota
	// applies it to the candidate quota; component merge logic decides whether
	// that candidate is a create-time override or a post-create baseline floor.
	DefaultNodeCount *uint32
	// MinNodeCount is the minimum node count required (nil means use calculated value, non-nil overrides to specified value)
	MinNodeCount *uint32
	// MaxNodeCount is the maximum node count allowed
	MaxNodeCount uint32
	// NodeSpec is the node specification
	NodeSpec *entity.NodeSpec
	// InitLimit is the initial limit during CREATING phase (0 means no limit)
	InitLimit uint32
	// IsCreating indicates whether the component is in CREATING phase
	IsCreating bool
	// NodeCountTransform is an optional function to transform node count (e.g., TiKV's multiple-of-3)
	NodeCountTransform func(uint32) uint32
}

// buildNodeQuota creates a NodeQuota from RU-derived count and component limits.
// When IsCreating is true and InitLimit > 0, InitLimit caps the RU-derived
// initial count. If DefaultNodeCount is set, it overrides the computed count
// after applying InitLimit, component-specific transforms, and MinNodeCount.
func buildNodeQuota(cfg NodeQuotaConfig) *entity.NodeQuota {
	var nodeCount, minCount uint32
	maxQuota := cfg.MaxNodeCount

	if cfg.IsCreating && cfg.InitLimit > 0 {
		// Apply initial limit during CREATING phase
		nodeCount = lo.Min([]uint32{maxQuota, cfg.CalculatedCount, cfg.InitLimit})
		minCount = lo.Min([]uint32{maxQuota, cfg.CalculatedCount, cfg.InitLimit})
	} else {
		// No initial limit for other phases
		nodeCount = lo.Min([]uint32{maxQuota, cfg.CalculatedCount})
		minCount = lo.Min([]uint32{maxQuota, cfg.CalculatedCount})
	}

	// Apply node count transformation if provided (e.g., TiKV multiple-of-3)
	if cfg.NodeCountTransform != nil {
		nodeCount = cfg.NodeCountTransform(nodeCount)
		minCount = cfg.NodeCountTransform(minCount)
		maxQuota = cfg.NodeCountTransform(maxQuota)
	}

	// Override minCount if explicitly specified
	if cfg.MinNodeCount != nil {
		minCount = *cfg.MinNodeCount
	}

	applyDefaultNodeCount(&nodeCount, &minCount, cfg.DefaultNodeCount)

	return &entity.NodeQuota{
		NodeCount:    nodeCount,
		MaxNodeCount: maxQuota,
		MinNodeCount: minCount,
		NodeSpec:     cfg.NodeSpec,
	}
}

// Helper function to create a pointer to uint32
func uint32Ptr(v uint32) *uint32 {
	return &v
}

func applyDefaultNodeCount(nodeCount, minCount, defaultNodeCount *uint32) {
	if defaultNodeCount == nil {
		return
	}

	*nodeCount = *defaultNodeCount
	if *minCount > *nodeCount {
		*minCount = *nodeCount
	}
}

func applyDefaultNodeCountToQuota(quota *entity.NodeQuota, defaultNodeCount *uint32) *entity.NodeQuota {
	if quota == nil {
		return nil
	}
	applyDefaultNodeCount(&quota.NodeCount, &quota.MinNodeCount, defaultNodeCount)
	return quota
}

func validateDefaultNodeCount(componentType apiv1.ComponentType, defaultNodeCount *uint32, maxNodeCount uint32) error {
	if err := entity.ValidateDefaultNodeCount(componentType, defaultNodeCount, maxNodeCount); err != nil {
		return status.Error(codes.InvalidArgument, err.Error())
	}
	return nil
}

func defaultNodeCountForPhase(defaultNodeCount *uint32, phase apiv1.Component_Phase) *uint32 {
	if phase == apiv1.Component_CREATING {
		return defaultNodeCount
	}
	return nil
}

// TODO: because the tidb worker need use the same node spec as tidb
// so we use set the same node spec to tidb with same cluster id
// ru: per tidb group request unit
// totalRu: per cluster request unit
func (s *serverImpl) genDefaultTiDBQuota(ctx context.Context, ru *entity.RequestUnit, nodeSpecMap map[string][]*entity.NodeSpec, ruConfig *entity.RuConfig, phase apiv1.Component_Phase) (*entity.NodeQuota, error) {
	desiredCNodeSec, defaultNodeCount, err := ruConfig.GetNodeSpecAndDefaultNodeCount(apiv1.ComponentType_TIDB, ru.GetMax())
	if err != nil {
		return nil, status.Errorf(codes.Internal, "%v", err)
	}

	nodeSpec, err := getNodeSpec(ctx, desiredCNodeSec, nodeSpecMap, false)
	if err != nil {
		return nil, err
	}

	// set tidb node quota
	// use ru to calculate the node count
	calculatedCount := lo.Max([]uint32{ru.GetMin() / (nodeSpec.GetCpu() * tidbPerCpuRu), 1})
	maxNodeCount := ruConfig.GetNodeCountQuota(apiv1.ComponentType_TIDB)
	if err := validateDefaultNodeCount(apiv1.ComponentType_TIDB, defaultNodeCount, maxNodeCount); err != nil {
		return nil, err
	}

	return buildNodeQuota(NodeQuotaConfig{
		CalculatedCount:  calculatedCount,
		DefaultNodeCount: defaultNodeCount,
		MaxNodeCount:     maxNodeCount,
		NodeSpec:         nodeSpec,
		IsCreating:       phase == apiv1.Component_CREATING,
		InitLimit:        maxInitTiDBCount,
	}), nil
}

// ru: all tidb group request unit
func (s *serverImpl) genDefaultTiDBWorkerQuota(ctx context.Context, ru *entity.RequestUnit, nodeSpecMap map[string][]*entity.NodeSpec, ruConfig *entity.RuConfig, phase apiv1.Component_Phase) (*entity.NodeQuota, error) {
	desiredCNodeSec, defaultNodeCount, err := ruConfig.GetNodeSpecAndDefaultNodeCount(apiv1.ComponentType_TIDB_WORKER, ru.GetMax())
	if err != nil {
		return nil, status.Errorf(codes.Internal, "%v", err)
	}

	nodeSpec, err := getNodeSpec(ctx, desiredCNodeSec, nodeSpecMap, false)
	if err != nil {
		return nil, err
	}

	// set tidb worker node quota
	// half of tidb node quota
	maxNodeCount := ruConfig.GetNodeCountQuota(apiv1.ComponentType_TIDB_WORKER)
	if err := validateDefaultNodeCount(apiv1.ComponentType_TIDB_WORKER, defaultNodeCount, maxNodeCount); err != nil {
		return nil, err
	}

	effectiveDefaultNodeCount := defaultNodeCountForPhase(defaultNodeCount, phase)
	return applyDefaultNodeCountToQuota(&entity.NodeQuota{
		NodeCount:    1, // min 1
		MaxNodeCount: maxNodeCount,
		MinNodeCount: 1, // tidb worker min node count is 1
		NodeSpec:     nodeSpec,
	}, effectiveDefaultNodeCount), nil
}

// ru: per tidb group request unit
func (s *serverImpl) genDefaultTiProxyQuota(ctx context.Context, ru *entity.RequestUnit, nodeSpecMap map[string][]*entity.NodeSpec, ruConfig *entity.RuConfig, phase apiv1.Component_Phase) (*entity.NodeQuota, error) {
	desiredCNodeSec, defaultNodeCount, err := ruConfig.GetNodeSpecAndDefaultNodeCount(apiv1.ComponentType_TIPROXY, ru.GetMax())
	if err != nil {
		return nil, status.Errorf(codes.Internal, "%v", err)
	}

	nodeSpec, err := getNodeSpec(ctx, desiredCNodeSec, nodeSpecMap, false)
	if err != nil {
		return nil, err
	}

	// set tidb tiproxy node quota
	// half of tidb node quota
	minCalculatedCount := uint32(2)
	minNodeCount := uint32Ptr(2)
	if defaultNodeCount != nil {
		// A configured defaultNodeCount owns the TiProxy baseline. Drop the
		// legacy min=2 override so buildNodeQuota uses the RU-derived min, then
		// clamps it down to the explicit default when needed.
		minCalculatedCount = 1
		minNodeCount = nil
	}
	calculatedCount := lo.Max([]uint32{ru.GetMin() / (nodeSpec.GetCpu() * tiproxyPerCpuRu), minCalculatedCount})
	maxNodeCount := ruConfig.GetNodeCountQuota(apiv1.ComponentType_TIPROXY)
	if err := validateDefaultNodeCount(apiv1.ComponentType_TIPROXY, defaultNodeCount, maxNodeCount); err != nil {
		return nil, err
	}

	return buildNodeQuota(NodeQuotaConfig{
		CalculatedCount:  calculatedCount,
		DefaultNodeCount: defaultNodeCount,
		MinNodeCount:     minNodeCount,
		MaxNodeCount:     maxNodeCount,
		NodeSpec:         nodeSpec,
		IsCreating:       phase == apiv1.Component_CREATING,
		InitLimit:        maxInitTiProxyCount,
	}), nil
}

func (s *serverImpl) genDefaultPDQuota(ctx context.Context, totalRu *entity.RequestUnit, nodeSpecMap map[string][]*entity.NodeSpec, ruConfig *entity.RuConfig, phase apiv1.Component_Phase) (*entity.NodeQuota, error) {
	desiredCNodeSec, defaultNodeCount, err := ruConfig.GetNodeSpecAndDefaultNodeCount(apiv1.ComponentType_PD, totalRu.GetMax())
	if err != nil {
		return nil, status.Errorf(codes.Internal, "%v", err)
	}

	nodeSpec, err := getNodeSpec(ctx, desiredCNodeSec, nodeSpecMap, false)
	if err != nil {
		return nil, err
	}

	// set pd node quota
	nodeQuota := &entity.NodeQuota{
		NodeCount:    3,
		MaxNodeCount: 3,
		MinNodeCount: 3,
		NodeSpec:     nodeSpec,
	}
	if err := validateDefaultNodeCount(apiv1.ComponentType_PD, defaultNodeCount, nodeQuota.GetMaxNodeCount()); err != nil {
		return nil, err
	}

	return applyDefaultNodeCountToQuota(nodeQuota, defaultNodeCountForPhase(defaultNodeCount, phase)), nil
}

func (s *serverImpl) genDefaultPDTsoQuota(ctx context.Context, totalRu *entity.RequestUnit, nodeSpecMap map[string][]*entity.NodeSpec, ruConfig *entity.RuConfig, phase apiv1.Component_Phase) (*entity.NodeQuota, error) {
	desiredCNodeSec, defaultNodeCount, err := ruConfig.GetNodeSpecAndDefaultNodeCount(apiv1.ComponentType_PD_TSO, totalRu.GetMax())
	if err != nil {
		return nil, status.Errorf(codes.Internal, "%v", err)
	}

	nodeSpec, err := getNodeSpec(ctx, desiredCNodeSec, nodeSpecMap, false)
	if err != nil {
		return nil, err
	}

	// set pd tso node quota
	nodeQuota := &entity.NodeQuota{
		NodeCount:    2,
		MaxNodeCount: 2, // tso always 2
		MinNodeCount: 1,
		NodeSpec:     nodeSpec,
	}
	if err := validateDefaultNodeCount(apiv1.ComponentType_PD_TSO, defaultNodeCount, nodeQuota.GetMaxNodeCount()); err != nil {
		return nil, err
	}

	return applyDefaultNodeCountToQuota(nodeQuota, defaultNodeCountForPhase(defaultNodeCount, phase)), nil
}

func (s *serverImpl) genDefaultPDSchedulingQuota(ctx context.Context, totalRu *entity.RequestUnit, nodeSpecMap map[string][]*entity.NodeSpec, ruConfig *entity.RuConfig, phase apiv1.Component_Phase) (*entity.NodeQuota, error) {
	desiredCNodeSec, defaultNodeCount, err := ruConfig.GetNodeSpecAndDefaultNodeCount(apiv1.ComponentType_PD_SCHEDULING, totalRu.GetMax())
	if err != nil {
		return nil, status.Errorf(codes.Internal, "%v", err)
	}

	nodeSpec, err := getNodeSpec(ctx, desiredCNodeSec, nodeSpecMap, false)
	if err != nil {
		return nil, err
	}

	// set pd scheduling node quota
	maxNodeCount := ruConfig.GetNodeCountQuota(apiv1.ComponentType_PD_SCHEDULING)
	nodeQuota := &entity.NodeQuota{
		NodeCount:    2,
		MaxNodeCount: maxNodeCount,
		MinNodeCount: 1,
		NodeSpec:     nodeSpec,
	}
	if err := validateDefaultNodeCount(apiv1.ComponentType_PD_SCHEDULING, defaultNodeCount, maxNodeCount); err != nil {
		return nil, err
	}

	return applyDefaultNodeCountToQuota(nodeQuota, defaultNodeCountForPhase(defaultNodeCount, phase)), nil
}

// now tikv node quota is hard code, need to be calculated by request unit in future
// Default tikv max node count is 6
func (s *serverImpl) genDefaultTiKVQuota(ctx context.Context, totalRu *entity.RequestUnit, nodeSpecMap map[string][]*entity.NodeSpec, ruConfig *entity.RuConfig, withLocalDisk bool, phase apiv1.Component_Phase) (*entity.NodeQuota, error) {
	desiredCNodeSec, defaultNodeCount, err := ruConfig.GetNodeSpecAndDefaultNodeCount(apiv1.ComponentType_TIKV, totalRu.GetMax())
	if err != nil {
		return nil, status.Errorf(codes.Internal, "%v", err)
	}

	nodeSpec, err := getNodeSpec(ctx, desiredCNodeSec, nodeSpecMap, withLocalDisk)
	if err != nil {
		return nil, err
	}

	// set tikv node quota
	calculatedCount := totalRu.GetMin() / (nodeSpec.GetCpu() * tikvPerCpuRu)
	maxNodeCount := ruConfig.GetNodeCountQuota(apiv1.ComponentType_TIKV)
	if err := validateDefaultNodeCount(apiv1.ComponentType_TIKV, defaultNodeCount, genTiKVNodeCount(maxNodeCount)); err != nil {
		return nil, err
	}

	return buildNodeQuota(NodeQuotaConfig{
		CalculatedCount:    calculatedCount,
		DefaultNodeCount:   defaultNodeCountForPhase(defaultNodeCount, phase),
		MaxNodeCount:       maxNodeCount,
		NodeSpec:           nodeSpec,
		IsCreating:         phase == apiv1.Component_CREATING,
		InitLimit:          maxInitTiKVCount,
		NodeCountTransform: genTiKVNodeCount, // Apply TiKV-specific rounding to multiple of 3
	}), nil
}

func genTiKVNodeCount(count uint32) uint32 {
	return entity.EffectiveMaxDefaultNodeCount(apiv1.ComponentType_TIKV, count)
}

func (s *serverImpl) genDefaultTiFlashWriteQuota(ctx context.Context, totalRu *entity.RequestUnit, nodeSpecMap map[string][]*entity.NodeSpec, ruConfig *entity.RuConfig, phase apiv1.Component_Phase) (*entity.NodeQuota, error) {
	desiredCNodeSec, defaultNodeCount, err := ruConfig.GetNodeSpecAndDefaultNodeCount(apiv1.ComponentType_TIFLASH_WRITE, totalRu.GetMax())
	if err != nil {
		return nil, status.Errorf(codes.Internal, "%v", err)
	}

	nodeSpec, err := getNodeSpec(ctx, desiredCNodeSec, nodeSpecMap, false)
	if err != nil {
		return nil, err
	}

	// set tiflash node quota
	calculatedCount := lo.Max([]uint32{totalRu.GetMin() / (nodeSpec.GetCpu() * tiflashPerCpuRu), 1})
	maxNodeCount := ruConfig.GetNodeCountQuota(apiv1.ComponentType_TIFLASH_WRITE)
	if err := validateDefaultNodeCount(apiv1.ComponentType_TIFLASH_WRITE, defaultNodeCount, maxNodeCount); err != nil {
		return nil, err
	}

	return buildNodeQuota(NodeQuotaConfig{
		CalculatedCount:  calculatedCount,
		DefaultNodeCount: defaultNodeCountForPhase(defaultNodeCount, phase),
		MinNodeCount:     uint32Ptr(0), // tiflash default min node count must be 0
		MaxNodeCount:     maxNodeCount,
		NodeSpec:         nodeSpec,
		IsCreating:       phase == apiv1.Component_CREATING,
		InitLimit:        maxInitTiFlashCount,
	}), nil
}

func (s *serverImpl) genDefaultTiFlashComputeQuota(ctx context.Context, totalRu *entity.RequestUnit, nodeSpecMap map[string][]*entity.NodeSpec, ruConfig *entity.RuConfig, computeNodeWithLocalDisk bool, phase apiv1.Component_Phase) (*entity.NodeQuota, error) {
	desiredCNodeSec, defaultNodeCount, err := ruConfig.GetNodeSpecAndDefaultNodeCount(apiv1.ComponentType_TIFLASH_COMPUTE, totalRu.GetMax())
	if err != nil {
		return nil, status.Errorf(codes.Internal, "%v", err)
	}

	nodeSpec, err := getNodeSpec(ctx, desiredCNodeSec, nodeSpecMap, computeNodeWithLocalDisk)
	if err != nil {
		return nil, err
	}

	// set tiflash node quota
	calculatedCount := lo.Max([]uint32{totalRu.GetMin() / (nodeSpec.GetCpu() * tiflashPerCpuRu), 1})
	maxNodeCount := ruConfig.GetNodeCountQuota(apiv1.ComponentType_TIFLASH_COMPUTE)
	if err := validateDefaultNodeCount(apiv1.ComponentType_TIFLASH_COMPUTE, defaultNodeCount, maxNodeCount); err != nil {
		return nil, err
	}

	return buildNodeQuota(NodeQuotaConfig{
		CalculatedCount:  calculatedCount,
		DefaultNodeCount: defaultNodeCountForPhase(defaultNodeCount, phase),
		MinNodeCount:     uint32Ptr(0), // tiflash default min node count must be 0
		MaxNodeCount:     maxNodeCount,
		NodeSpec:         nodeSpec,
		IsCreating:       phase == apiv1.Component_CREATING,
		InitLimit:        maxInitTiFlashCount,
	}), nil
}

func (s *serverImpl) genDefaultTiKVWorkerQuota(ctx context.Context, totalRu *entity.RequestUnit, nodeSpecMap map[string][]*entity.NodeSpec, ruConfig *entity.RuConfig, phase apiv1.Component_Phase) (*entity.NodeQuota, error) {
	desiredCNodeSec, defaultNodeCount, err := ruConfig.GetNodeSpecAndDefaultNodeCount(apiv1.ComponentType_TIKV_WORKER, totalRu.GetMax())
	if err != nil {
		return nil, status.Errorf(codes.Internal, "%v", err)
	}

	nodeSpec, err := getNodeSpec(ctx, desiredCNodeSec, nodeSpecMap, false)
	if err != nil {
		return nil, err
	}

	// set tikv worker node quota
	// half of tikv node quota
	maxNodeCount := ruConfig.GetNodeCountQuota(apiv1.ComponentType_TIKV_WORKER)
	if err := validateDefaultNodeCount(apiv1.ComponentType_TIKV_WORKER, defaultNodeCount, maxNodeCount); err != nil {
		return nil, err
	}

	return applyDefaultNodeCountToQuota(&entity.NodeQuota{
		NodeCount:    1, // min 1
		MaxNodeCount: maxNodeCount,
		MinNodeCount: 1, // tikv worker min node count is 1
		NodeSpec:     nodeSpec,
	}, defaultNodeCountForPhase(defaultNodeCount, phase)), nil
}

func (s *serverImpl) genDefaultCoprocessorWorkerQuota(ctx context.Context, totalRu *entity.RequestUnit, nodeSpecMap map[string][]*entity.NodeSpec, ruConfig *entity.RuConfig, phase apiv1.Component_Phase) (*entity.NodeQuota, error) {
	desiredCNodeSec, defaultNodeCount, err := ruConfig.GetNodeSpecAndDefaultNodeCount(apiv1.ComponentType_COPROCESSOR_WORKER, totalRu.GetMax())
	if err != nil {
		return nil, status.Errorf(codes.Internal, "%v", err)
	}

	nodeSpec, err := getNodeSpec(ctx, desiredCNodeSec, nodeSpecMap, false)
	if err != nil {
		return nil, err
	}

	// set copr worker node quota
	// half of tikv node quota
	maxNodeCount := ruConfig.GetNodeCountQuota(apiv1.ComponentType_COPROCESSOR_WORKER)
	if err := validateDefaultNodeCount(apiv1.ComponentType_COPROCESSOR_WORKER, defaultNodeCount, maxNodeCount); err != nil {
		return nil, err
	}

	return applyDefaultNodeCountToQuota(&entity.NodeQuota{
		NodeCount:    1, // min 1
		MaxNodeCount: maxNodeCount,
		MinNodeCount: 1, // tikv worker min node count is 1
		NodeSpec:     nodeSpec,
	}, defaultNodeCountForPhase(defaultNodeCount, phase)), nil
}

// Schema Manager uses a fixed 2C8G node spec. On creation, it starts when the
// Columnar TiFlash compute component has at least one replica. After creation,
// it remains independently managed by explicit scale operations.
func (s *serverImpl) genDefaultSchemaManagerQuota(ctx context.Context, nodeSpecMap map[string][]*entity.NodeSpec, phase apiv1.Component_Phase, startSchemaManager bool) (*entity.NodeQuota, error) {
	desiredNodeSpec := &entity.NodeSpec{
		Cpu:      2,
		MemoryGi: 8,
	}

	nodeSpec, err := getNodeSpec(ctx, desiredNodeSpec, nodeSpecMap, false)
	if err != nil {
		return nil, err
	}

	initialNodeCount := uint32(0)
	if phase == apiv1.Component_CREATING && startSchemaManager {
		initialNodeCount = 1
	}

	return &entity.NodeQuota{
		NodeCount:    initialNodeCount,
		MaxNodeCount: 1,
		MinNodeCount: 0,
		NodeSpec:     nodeSpec,
	}, nil
}

func (s *serverImpl) genDefaultResourceManagerComponent(ctx context.Context, _ *entity.SharedPool, resourceManagerComp *entity.Component, totalRu *entity.RequestUnit, nodeSpecMap map[string][]*entity.NodeSpec, ruConfig *entity.RuConfig, force bool) error {
	// set resource manager node quota
	newNodeQuota, err := s.genDefaultResourceManagerQuota(ctx, totalRu, nodeSpecMap, ruConfig, resourceManagerComp.GetPhase())
	if err != nil {
		tilog.Error(ctx, "failed to generate resource manager node quota", zap.Error(err))
		return err
	}

	oldNodeQuota := resourceManagerComp.GetNodeQuota()
	resourceManagerComp.NodeQuota = compareNodeQuota(resourceManagerComp.GetNodeQuota(), newNodeQuota)

	tilog.Info(ctx, "resource manager component node quota",
		zap.String("component", entity.GetComponentGroupName(resourceManagerComp)),
		zap.Any("select NodeQuota", resourceManagerComp.GetNodeQuota()),
		zap.Any("oldNodeQuota", oldNodeQuota),
		zap.Any("newNodeQuota", newNodeQuota),
	)

	// resource manager does not need storage

	if resourceManagerComp.GetPhase() != apiv1.Component_CREATING && !reflect.DeepEqual(resourceManagerComp.GetNodeQuota(), oldNodeQuota) {
		if resourceManagerComp.GetPhase() != apiv1.Component_AVAILABLE {
			if !force {
				tilog.Warn(ctx, "resource manager component is not available",
					zap.String("component", entity.GetComponentGroupName(resourceManagerComp)),
				)
				return status.Errorf(codes.FailedPrecondition, "resource manager %s is not available ", entity.GetComponentGroupName(resourceManagerComp))
			}
			tilog.Warn(ctx, "force to modify resource manager component which is not available",
				zap.String("component", entity.GetComponentGroupName(resourceManagerComp)),
			)
		}

		// if resource manager node quota is changed, we need to update the phase
		resourceManagerComp.Phase = apiv1.Component_MODIFYING
		tilog.Info(ctx, "resource manager component use new node quota",
			zap.String("component", entity.GetComponentGroupName(resourceManagerComp)),
		)
	}

	return nil
}

func (s *serverImpl) genDefaultResourceManagerQuota(ctx context.Context, totalRu *entity.RequestUnit, nodeSpecMap map[string][]*entity.NodeSpec, ruConfig *entity.RuConfig, phase apiv1.Component_Phase) (*entity.NodeQuota, error) {
	// resource manager always uses fixed 2C4G spec, provide default if not in ruConfig
	desiredCNodeSec, defaultNodeCount, err := ruConfig.GetNodeSpecAndDefaultNodeCount(apiv1.ComponentType_RESOURCE_MANAGER, totalRu.GetMax())
	if err != nil {
		return nil, status.Errorf(codes.Internal, "%v", err)
	}

	nodeSpec, err := getNodeSpec(ctx, desiredCNodeSec, nodeSpecMap, false)
	if err != nil {
		return nil, err
	}

	// set resource manager node quota
	// resource manager always has 2 replicas with fixed 2C4G spec
	if err := validateDefaultNodeCount(apiv1.ComponentType_RESOURCE_MANAGER, defaultNodeCount, 2); err != nil {
		return nil, err
	}

	return applyDefaultNodeCountToQuota(&entity.NodeQuota{
		NodeCount:    2, // fixed count is 2
		MaxNodeCount: 2, // max is also 2
		MinNodeCount: 2, // min is also 2
		NodeSpec:     nodeSpec,
	}, defaultNodeCountForPhase(defaultNodeCount, phase)), nil
}

// compareNodeQuota keeps the historical merge behavior for component quota
// regeneration. When the new spec is not larger, it mutates currNodeQuota
// in-place to preserve the current spec while raising count/max-count only.
// Callers that must detect count-only changes need to clone the current quota
// before calling. Non-TiProxy callers intentionally keep their legacy behavior
// until their count-only update semantics are reviewed with component owners.
func compareNodeQuota(currNodeQuota, newNodeQuota *entity.NodeQuota) *entity.NodeQuota {
	if currNodeQuota == nil {
		return newNodeQuota
	}

	// if new node spec is more than old node spec
	// we will use new node quota
	if newNodeQuota.GetNodeSpec().GetCpu() > currNodeQuota.GetNodeSpec().GetCpu() ||
		newNodeQuota.GetNodeSpec().GetMemoryGi() > currNodeQuota.GetNodeSpec().GetMemoryGi() {

		// ensure node count not less than old node count
		// hpa or autoscaler will scale down the node count
		newNodeQuota.NodeCount = lo.Max([]uint32{newNodeQuota.GetNodeCount(), currNodeQuota.GetNodeCount()})
		if newNodeQuota.GetNodeCount() > newNodeQuota.GetMaxNodeCount() {
			// ensure node count not more than max node count
			newNodeQuota.MaxNodeCount = newNodeQuota.GetNodeCount()
		}

		return newNodeQuota
	}

	// if new node quota is more than old node quota
	// use new node quota
	currNodeQuota.NodeCount = lo.Max([]uint32{currNodeQuota.GetNodeCount(), newNodeQuota.GetNodeCount()})
	if currNodeQuota.GetNodeCount() > currNodeQuota.GetMaxNodeCount() {
		// ensure node count not more than max node count
		currNodeQuota.MaxNodeCount = currNodeQuota.GetNodeCount()
	}
	return currNodeQuota
}

func (s *serverImpl) genDefaultTiDBStorages(providerType entity.ProviderType, _ *entity.RequestUnit) ([]*entity.Storage, error) {
	// set tidb storage
	// now only support aws
	switch providerType {
	case apiv1.ProviderType_AWS:
		slowStorage := &entity.Storage{
			Name:          apiv1.Component_Storage_SLOWLOG,
			SizeGi:        30,
			Type:          entity.GP3,
			Iops:          3000,
			ThroughputMbs: 144,
			MountPath:     entity.AuditAndSlowLogPath,
		}
		return []*entity.Storage{slowStorage}, nil
	case apiv1.ProviderType_AZURE:
		slowStorage := &entity.Storage{
			Name:          apiv1.Component_Storage_SLOWLOG,
			SizeGi:        30,
			Type:          entity.PremiumSSDv2,
			Iops:          3000,
			ThroughputMbs: 144,
			MountPath:     entity.AuditAndSlowLogPath,
		}
		return []*entity.Storage{slowStorage}, nil
	case apiv1.ProviderType_ALICLOUD:
		slowStorage := &entity.Storage{
			Name:          apiv1.Component_Storage_SLOWLOG,
			SizeGi:        30,
			Type:          entity.CloudAuto,
			Iops:          3000,
			ThroughputMbs: 0,
			MountPath:     entity.AuditAndSlowLogPath,
		}
		return []*entity.Storage{slowStorage}, nil
	}
	return nil, status.Errorf(codes.FailedPrecondition, "provider type %s not supported", providerType)
}

func (s *serverImpl) genDefaultPDStorages(providerType entity.ProviderType, _ *entity.RequestUnit) ([]*entity.Storage, error) {
	// set pd storage
	switch providerType {
	case apiv1.ProviderType_AWS:
		storage := &entity.Storage{
			Name:          apiv1.Component_Storage_DATA,
			SizeGi:        30,
			Iops:          4000,
			ThroughputMbs: 400,
			MountPath:     "",
			Type:          entity.GP3,
		}
		return []*entity.Storage{storage}, nil
	case apiv1.ProviderType_AZURE:
		storage := &entity.Storage{
			Name:          apiv1.Component_Storage_DATA,
			SizeGi:        30,
			Iops:          4000,
			ThroughputMbs: 400,
			MountPath:     "",
			Type:          entity.PremiumSSDv2,
		}
		return []*entity.Storage{storage}, nil
	case apiv1.ProviderType_ALICLOUD:
		storage := &entity.Storage{
			Name:          apiv1.Component_Storage_DATA,
			SizeGi:        30,
			Iops:          4000,
			ThroughputMbs: 0,
			MountPath:     "",
			Type:          entity.CloudAuto,
		}
		return []*entity.Storage{storage}, nil
	}
	return nil, status.Errorf(codes.FailedPrecondition, "provider type %s not supported", providerType)
}

func (s *serverImpl) genDefaultCoprocessorWorkerStorages(providerType entity.ProviderType) ([]*entity.Storage, error) {
	switch providerType {
	case apiv1.ProviderType_AWS, apiv1.ProviderType_PROVIDER_TYPE_UNSPECIFIED:
		storage := &entity.Storage{
			Name:          apiv1.Component_Storage_DATA,
			SizeGi:        200,
			Iops:          6000,
			ThroughputMbs: 625,
			Type:          entity.GP3,
		}
		return []*entity.Storage{storage}, nil
	case apiv1.ProviderType_AZURE:
		storage := &entity.Storage{
			Name:          apiv1.Component_Storage_DATA,
			SizeGi:        200,
			Iops:          6000,
			ThroughputMbs: 625,
			Type:          entity.PremiumSSDv2,
		}
		return []*entity.Storage{storage}, nil
	case apiv1.ProviderType_ALICLOUD:
		storage := &entity.Storage{
			Name:          apiv1.Component_Storage_DATA,
			SizeGi:        200,
			Iops:          6000,
			ThroughputMbs: 0,
			Type:          entity.CloudAuto,
		}
		return []*entity.Storage{storage}, nil
	}
	return nil, status.Errorf(codes.FailedPrecondition, "provider type %s not supported", providerType)
}

func (s *serverImpl) genDefaultSchemaManagerStorages(providerType entity.ProviderType) ([]*entity.Storage, error) {
	switch providerType {
	case apiv1.ProviderType_AWS, apiv1.ProviderType_PROVIDER_TYPE_UNSPECIFIED:
		storage := &entity.Storage{
			Name:          apiv1.Component_Storage_DATA,
			SizeGi:        20,
			Iops:          3000,
			ThroughputMbs: 125,
			Type:          entity.GP3,
		}
		return []*entity.Storage{storage}, nil
	case apiv1.ProviderType_AZURE:
		storage := &entity.Storage{
			Name:          apiv1.Component_Storage_DATA,
			SizeGi:        20,
			Iops:          3000,
			ThroughputMbs: 125,
			Type:          entity.PremiumSSDv2,
		}
		return []*entity.Storage{storage}, nil
	case apiv1.ProviderType_ALICLOUD:
		storage := &entity.Storage{
			Name:          apiv1.Component_Storage_DATA,
			SizeGi:        20,
			Iops:          3000,
			ThroughputMbs: 0,
			Type:          entity.CloudAuto,
		}
		return []*entity.Storage{storage}, nil
	}
	return nil, status.Errorf(codes.FailedPrecondition, "provider type %s not supported", providerType)
}

// only creating tikv need gen storages
// the default type is gp3
func (s *serverImpl) genDefaultTiKVStorages(
	providerType entity.ProviderType,
	nodeSpec *entity.NodeSpec,
	initConfig *entity.InitSharedPoolConfig,
) ([]*entity.Storage, error) {
	if initConfig == nil {
		return nil, status.Errorf(codes.InvalidArgument, "init config is nil")
	}

	if initConfig.GetTikvDataStorage() == nil {
		return nil, status.Errorf(codes.InvalidArgument, "tikv data storage config is nil")
	}

	if initConfig.GetTikvRaftlogStorage() == nil {
		return nil, status.Errorf(codes.InvalidArgument, "tikv raftlog storage config is nil")
	}

	provider := providerType.String()

	// Validate tikv data storage
	dataParams := entity.StorageParamsFromInitConfig(initConfig.GetTikvDataStorage())
	dataOpts := &entity.StorageValidationOptions{AllowZeroSize: dataParams.Type == entity.LocalSSD}
	if err := entity.ValidateStorageParams(dataParams, provider, "tikv data", dataOpts); err != nil {
		return nil, status.Errorf(codes.InvalidArgument, "%s", err.Error())
	}

	// Handle LocalSSD special case: get size from nodeSpec
	if initConfig.GetTikvDataStorage().GetType() == entity.LocalSSD {
		if nodeSpec.LocalDiskSizeGi <= 0 {
			return nil, status.Errorf(codes.InvalidArgument, "invalid local disk size for %d-%d", nodeSpec.GetCpu(), nodeSpec.GetMemoryGi())
		}
		initConfig.GetTikvDataStorage().SizeGi = nodeSpec.LocalDiskSizeGi
	}

	// Validate tikv raftlog storage (LocalSSD not allowed for raftlog)
	raftlogParams := entity.StorageParamsFromInitConfig(initConfig.GetTikvRaftlogStorage())
	if raftlogParams.Type == entity.LocalSSD {
		return nil, status.Errorf(codes.InvalidArgument, "tikv raftlog local-ssd storage is not supported")
	}
	if err := entity.ValidateStorageParams(raftlogParams, provider, "tikv raftlog", nil); err != nil {
		return nil, status.Errorf(codes.InvalidArgument, "%s", err.Error())
	}

	var storages []*entity.Storage
	raftlogStorage := &entity.Storage{
		Name:          apiv1.Component_Storage_RAFTLOG,
		SizeGi:        initConfig.GetTikvRaftlogStorage().GetSizeGi(),
		Iops:          initConfig.GetTikvRaftlogStorage().GetIops(),
		ThroughputMbs: initConfig.GetTikvRaftlogStorage().GetThroughputMbps(),
		MountPath:     entity.DefaultRaftlogMountPath,
		Type:          initConfig.GetTikvRaftlogStorage().GetType(),
	}

	dataStorage := &entity.Storage{
		Name:          apiv1.Component_Storage_DATA,
		SizeGi:        initConfig.GetTikvDataStorage().GetSizeGi(),
		Iops:          initConfig.GetTikvDataStorage().GetIops(),
		ThroughputMbs: initConfig.GetTikvDataStorage().GetThroughputMbps(),
		MountPath:     "",
		Type:          initConfig.GetTikvDataStorage().GetType(),
	}

	storages = append(storages, dataStorage, raftlogStorage)
	return storages, nil
}

func (s *serverImpl) genDefaultTiFlashCNStorages(
	providerType entity.ProviderType,
	nodeSpec *apiv1.NodeSpec,
	initConfig *entity.InitSharedPoolConfig,
) ([]*entity.Storage, error) {
	if initConfig == nil {
		return nil, status.Errorf(codes.InvalidArgument, "init config is nil")
	}
	if initConfig.GetTiflashCnStorage() == nil {
		return nil, status.Errorf(codes.InvalidArgument, "tiflash cn storage config is nil")
	}

	provider := providerType.String()

	// Validate tiflash cn storage
	params := entity.StorageParamsFromInitConfig(initConfig.GetTiflashCnStorage())
	opts := &entity.StorageValidationOptions{AllowZeroSize: params.Type == entity.LocalSSD}
	if err := entity.ValidateStorageParams(params, provider, "tiflash cn", opts); err != nil {
		return nil, status.Errorf(codes.InvalidArgument, "%s", err.Error())
	}

	// Handle LocalSSD special case: get size from nodeSpec
	if initConfig.GetTiflashCnStorage().GetType() == entity.LocalSSD {
		if nodeSpec.LocalDiskSizeGi <= 0 {
			return nil, status.Errorf(codes.InvalidArgument, "invalid local disk size for %d-%d", nodeSpec.GetCpu(), nodeSpec.GetMemoryGi())
		}
		initConfig.GetTiflashCnStorage().SizeGi = nodeSpec.LocalDiskSizeGi
	}

	storage := &entity.Storage{
		Name:          apiv1.Component_Storage_DATA,
		SizeGi:        initConfig.GetTiflashCnStorage().GetSizeGi(),
		Iops:          initConfig.GetTiflashCnStorage().GetIops(),
		ThroughputMbs: initConfig.GetTiflashCnStorage().GetThroughputMbps(),
		MountPath:     "",
		Type:          initConfig.GetTiflashCnStorage().GetType(),
	}

	return []*entity.Storage{storage}, nil
}

func (s *serverImpl) genDefaultTiFlashWNStorages(
	providerType entity.ProviderType,
	initConfig *entity.InitSharedPoolConfig,
) ([]*entity.Storage, error) {
	if initConfig == nil {
		return nil, status.Errorf(codes.InvalidArgument, "init config is nil")
	}
	if initConfig.GetTiflashWnStorage() == nil {
		return nil, status.Errorf(codes.InvalidArgument, "tiflash wn storage config is nil")
	}

	provider := providerType.String()

	// Validate tiflash wn storage (LocalSSD not allowed for write node)
	params := entity.StorageParamsFromInitConfig(initConfig.GetTiflashWnStorage())
	if params.Type == entity.LocalSSD {
		return nil, status.Errorf(codes.InvalidArgument, "tiflash wn local-ssd storage is not supported")
	}
	if err := entity.ValidateStorageParams(params, provider, "tiflash wn", nil); err != nil {
		return nil, status.Errorf(codes.InvalidArgument, "%s", err.Error())
	}

	storage := &entity.Storage{
		Name:          apiv1.Component_Storage_DATA,
		SizeGi:        initConfig.GetTiflashWnStorage().GetSizeGi(),
		Iops:          initConfig.GetTiflashWnStorage().GetIops(),
		ThroughputMbs: initConfig.GetTiflashWnStorage().GetThroughputMbps(),
		MountPath:     "",
		Type:          initConfig.GetTiflashWnStorage().GetType(),
	}

	return []*entity.Storage{storage}, nil
}

// TODO: if will not macth key, but we need chose one can use
// e.g.  8C15G -> 8C16G
func getNodeSpec(ctx context.Context, nodeSpec *entity.NodeSpec, nodeSpecMap map[string][]*entity.NodeSpec, withLocalDisk bool) (*entity.NodeSpec, error) {
	if nodeSpec == nil {
		return nil, status.Errorf(codes.InvalidArgument, "node spec is required")
	}

	nodeSpecObjs, ok := nodeSpecMap[entity.GetComponentNodeSpec(nodeSpec)]
	if !ok {
		return nil, status.Errorf(codes.InvalidArgument, "node spec %s not found", entity.GetComponentNodeSpec(nodeSpec))
	}

	isValid := func(spec *entity.NodeSpec) bool {
		if spec.GetAvailableCpu() <= 0 || spec.GetAvailableMemoryMi() <= 0 {
			log.Warn(ctx, "node spec is not available", zap.String("spec", entity.GetComponentNodeSpec(nodeSpec)), zap.Uint32("available cpu", spec.GetAvailableCpu()), zap.Uint32("available memory", spec.GetAvailableMemoryMi()))
			return false
		}
		if spec.WithLocalDisk {
			if spec.LocalDiskSizeGi <= 0 {
				log.Warn(ctx, "invalid local disk size", zap.String("spec", entity.GetComponentNodeSpec(nodeSpec)), zap.Uint32("available cpu", spec.GetAvailableCpu()), zap.Uint32("available memory", spec.GetAvailableMemoryMi()))
				return false
			}
		}

		return true
	}

	for _, obj := range nodeSpecObjs {
		if withLocalDisk != obj.WithLocalDisk {
			continue
		}

		if isValid(obj) {
			return obj, nil
		}
	}

	return nil, fmt.Errorf("not found the node spec %s", entity.GetComponentNodeSpec(nodeSpec))
}
