package gkeheterogeneous

import (
	"context"
	"testing"

	"github.com/stretchr/testify/require"
	regionaldedicatedv1 "github.com/tidbcloud/infra-provider/api/tidbcloud/regional-server/v1"
	"go.uber.org/mock/gomock"

	globalcommonv1 "github.com/tidbcloud/tidb-management-service/api/spec/global/common/v1"
	globaldedicatedv1 "github.com/tidbcloud/tidb-management-service/api/spec/global/dedicated/v1"
	"github.com/tidbcloud/tidb-management-service/dedicated-global-service/domain/adapter/regionserver/mock_regionserver"
	"github.com/tidbcloud/tidb-management-service/dedicated-global-service/domain/entity"
	"github.com/tidbcloud/tidb-management-service/dedicated-global-service/domain/repository/cluster/mock_cluster"
	"github.com/tidbcloud/tidb-management-service/dedicated-global-service/domain/repository/region/mock_region"
	"github.com/tidbcloud/tidb-management-service/utilities/bizerr"
	"github.com/tidbcloud/tidb-management-service/utilities/featuregate"
	"github.com/tidbcloud/tidb-management-service/utilities/featuregate/mock_featuregate"
)

func setupApplyRepoMocks(ctrl *gomock.Controller) (
	*mock_cluster.MockRepo, *mock_region.MockRepo, *mock_regionserver.MockClient,
) {
	clusterID := uint64(12345)
	orgID := uint64(1)
	projectID := uint64(2)

	clusterRepo := mock_cluster.NewMockRepo(ctrl)
	clusterRepo.EXPECT().Get(gomock.Any(), clusterID, false).Return(&entity.Cluster{
		Cluster: &globaldedicatedv1.Cluster{
			ClusterId:       clusterID,
			OrganizationId:  orgID,
			ProjectId:       projectID,
			CloudProviderId: globalcommonv1.CloudProvider_GCP,
			RegionId:        globalcommonv1.Region_GCP_US_WEST2,
			State:           globaldedicatedv1.Cluster_AVAILABLE,
		},
	}, nil)

	regionRepo := mock_region.NewMockRepo(ctrl)
	regionRepo.EXPECT().SelectRegionById(
		gomock.Any(), globalcommonv1.Region_GCP_US_WEST2, orgID,
	).Return(&globalcommonv1.Region{
		Id:    globalcommonv1.Region_GCP_US_WEST2,
		State: globalcommonv1.Region_ACTIVE,
	}, nil)

	regionServerCli := mock_regionserver.NewMockClient(ctrl)
	return clusterRepo, regionRepo, regionServerCli
}

func setupApplyMocks(ctrl *gomock.Controller) (
	*mock_cluster.MockRepo, *mock_region.MockRepo, *mock_regionserver.MockClient,
) {
	clusterRepo, regionRepo, regionServerCli := setupApplyRepoMocks(ctrl)
	regionServerCli.EXPECT().CreateGKEHeterogeneousNodePool(
		gomock.Any(), gomock.Any(), gomock.Any(),
	).DoAndReturn(func(
		_ context.Context,
		_ *entity.Region,
		req *regionaldedicatedv1.CreateGKEHeterogeneousNodePoolRequest,
	) (*regionaldedicatedv1.GKEHeterogeneousNodePool, bizerr.BizError) {
		switch req.GetInstanceType() {
		case "n2-standard-8":
			return &regionaldedicatedv1.GKEHeterogeneousNodePool{
				NodePoolName: "np-tikv-base",
				InstanceType: "n2-standard-8",
			}, nil
		case "n2d-standard-8":
			return &regionaldedicatedv1.GKEHeterogeneousNodePool{
				NodePoolName: "np-tikv-alt",
				InstanceType: "n2d-standard-8",
			}, nil
		default:
			return nil, bizerr.New(bizerr.ErrCodeParams, "unexpected instance type %s", req.GetInstanceType())
		}
	}).Times(2)
	regionServerCli.EXPECT().GetGKEHeterogeneousDeploy(
		gomock.Any(), gomock.Any(), gomock.Any(),
	).Return(nil, bizerr.New(bizerr.ErrCodeRecordNotFound, "not found"))

	return clusterRepo, regionRepo, regionServerCli
}

func applyReq() *globaldedicatedv1.ApplyGKEHeterogeneousDeployRequest {
	return &globaldedicatedv1.ApplyGKEHeterogeneousDeployRequest{
		TenantId:                1,
		ProjectId:               2,
		ClusterId:               12345,
		ComponentType:           globaldedicatedv1.ComponentType_TIKV,
		CurrentInstanceType:     "n2-standard-8",
		AlternativeInstanceType: "n2d-standard-8",
	}
}

func enabledDeploy(instanceTypes, nodePoolNames []string) *regionaldedicatedv1.GKEHeterogeneousDeploy {
	return &regionaldedicatedv1.GKEHeterogeneousDeploy{
		Enabled:       true,
		ComponentType: regionaldedicatedv1.ComponentType_TIKV,
		InstanceTypes: instanceTypes,
		NodePoolNames: nodePoolNames,
	}
}

func enabledDeployWithComputeClass(instanceTypes, nodePoolNames []string, computeClassName string) *regionaldedicatedv1.GKEHeterogeneousDeploy {
	deploy := enabledDeploy(instanceTypes, nodePoolNames)
	deploy.ComputeClassName = computeClassName
	return deploy
}

func computeClass(name string, nodePoolNames, instanceTypes []string) *regionaldedicatedv1.GKEHeterogeneousComputeClass {
	return &regionaldedicatedv1.GKEHeterogeneousComputeClass{
		Name:          name,
		NodePoolNames: nodePoolNames,
		InstanceTypes: instanceTypes,
	}
}

func TestApplyWithNodePoolTargetCreateSuccess(t *testing.T) {
	ctrl := gomock.NewController(t)
	defer ctrl.Finish()

	clusterRepo, regionRepo, regionServerCli := setupApplyMocks(ctrl)

	regionServerCli.EXPECT().CreateGKEHeterogeneousComputeClass(
		gomock.Any(), gomock.Any(), gomock.Any(),
	).Return(&regionaldedicatedv1.GKEHeterogeneousComputeClass{Name: "cc-tikv"}, nil)

	regionServerCli.EXPECT().CreateGKEHeterogeneousDeploy(
		gomock.Any(), gomock.Any(), gomock.Any(),
	).DoAndReturn(func(
		_ context.Context,
		_ *entity.Region,
		req *regionaldedicatedv1.CreateGKEHeterogeneousDeployRequest,
	) (*regionaldedicatedv1.GKEHeterogeneousDeploy, bizerr.BizError) {
		require.Len(t, req.GetInstanceTypes(), 1, "regional deploy expects only extra instance type")
		require.Equal(t, []string{"n2d-standard-8"}, req.GetInstanceTypes())
		require.Equal(t, []string{"np-tikv-base", "np-tikv-alt"}, req.GetNodePoolNames(), "regional deploy expects base+extra node pool names")
		return &regionaldedicatedv1.GKEHeterogeneousDeploy{}, nil
	})

	err := ApplyWithNodePoolTarget(
		context.Background(),
		clusterRepo, regionRepo, regionServerCli,
		applyReq(),
	)
	require.NoError(t, err)
}

func TestApplyWithNodePoolTargetCreateDeployRecordExistMatchesExistingDeploy(t *testing.T) {
	ctrl := gomock.NewController(t)
	defer ctrl.Finish()

	clusterRepo, regionRepo, regionServerCli := setupApplyMocks(ctrl)

	regionServerCli.EXPECT().CreateGKEHeterogeneousComputeClass(
		gomock.Any(), gomock.Any(), gomock.Any(),
	).Return(&regionaldedicatedv1.GKEHeterogeneousComputeClass{Name: "cc-tikv"}, nil)

	regionServerCli.EXPECT().CreateGKEHeterogeneousDeploy(
		gomock.Any(), gomock.Any(), gomock.Any(),
	).Return(nil, bizerr.New(bizerr.ErrCodeRecordExist, "already exists"))

	regionServerCli.EXPECT().GetGKEHeterogeneousDeploy(
		gomock.Any(), gomock.Any(), gomock.Any(),
	).Return(enabledDeployWithComputeClass(
		[]string{"n2-standard-8", "n2d-standard-8"},
		[]string{"np-tikv-base", "np-tikv-alt"},
		"cc-tikv",
	), nil)

	regionServerCli.EXPECT().GetGKEHeterogeneousNodePools(
		gomock.Any(), gomock.Any(), gomock.Any(),
	).Return(&regionaldedicatedv1.ListGKEHeterogeneousNodePoolsResponse{
		NodePools: []*regionaldedicatedv1.GKEHeterogeneousNodePool{
			{
				NodePoolName: "np-tikv-base",
				InstanceType: "n2-standard-8",
				Exists:       true,
			},
			{
				NodePoolName: "np-tikv-alt",
				InstanceType: "n2d-standard-8",
				Exists:       true,
			},
		},
	}, nil)

	err := ApplyWithNodePoolTarget(
		context.Background(),
		clusterRepo, regionRepo, regionServerCli,
		applyReq(),
	)
	require.NoError(t, err)
}

func TestApplyWithNodePoolTargetCreateDeployRecordExistRejectsMismatchedExistingDeploy(t *testing.T) {
	ctrl := gomock.NewController(t)
	defer ctrl.Finish()

	clusterRepo, regionRepo, regionServerCli := setupApplyMocks(ctrl)

	regionServerCli.EXPECT().CreateGKEHeterogeneousComputeClass(
		gomock.Any(), gomock.Any(), gomock.Any(),
	).Return(&regionaldedicatedv1.GKEHeterogeneousComputeClass{Name: "cc-tikv"}, nil)

	regionServerCli.EXPECT().CreateGKEHeterogeneousDeploy(
		gomock.Any(), gomock.Any(), gomock.Any(),
	).Return(nil, bizerr.New(bizerr.ErrCodeRecordExist, "already exists"))

	regionServerCli.EXPECT().GetGKEHeterogeneousDeploy(
		gomock.Any(), gomock.Any(), gomock.Any(),
	).Return(enabledDeployWithComputeClass(
		[]string{"n2-standard-8", "n2d-standard-8"},
		[]string{"np-tikv-base", "np-tikv-alt"},
		"cc-old",
	), nil)

	err := ApplyWithNodePoolTarget(
		context.Background(),
		clusterRepo, regionRepo, regionServerCli,
		applyReq(),
	)
	require.Error(t, err)
	require.Equal(t, bizerr.ErrCodeRegionalAPI, err.ErrCode())
	require.Contains(t, err.Error(), "compute class mismatch")
}

func TestApplyWithNodePoolTargetExistingDeploySuccess(t *testing.T) {
	ctrl := gomock.NewController(t)
	defer ctrl.Finish()

	clusterRepo, regionRepo, regionServerCli := setupApplyRepoMocks(ctrl)

	regionServerCli.EXPECT().GetGKEHeterogeneousDeploy(
		gomock.Any(), gomock.Any(), gomock.Any(),
	).Return(enabledDeploy(
		[]string{"n2-standard-8", "n2d-standard-8"},
		[]string{"np-tikv-base", "np-tikv-alt"},
	), nil)

	regionServerCli.EXPECT().GetGKEHeterogeneousNodePools(
		gomock.Any(), gomock.Any(), gomock.Any(),
	).Return(&regionaldedicatedv1.ListGKEHeterogeneousNodePoolsResponse{
		NodePools: []*regionaldedicatedv1.GKEHeterogeneousNodePool{
			{
				NodePoolName: "np-tikv-base",
				InstanceType: "n2-standard-8",
				Exists:       true,
			},
			{
				NodePoolName: "np-tikv-alt",
				InstanceType: "n2d-standard-8",
				Exists:       true,
			},
		},
	}, nil)

	err := ApplyWithNodePoolTarget(
		context.Background(),
		clusterRepo, regionRepo, regionServerCli,
		applyReq(),
	)
	require.NoError(t, err)
}

func TestApplyWithNodePoolTargetExistingDeployRejectsInstanceTypeMismatch(t *testing.T) {
	ctrl := gomock.NewController(t)
	defer ctrl.Finish()

	clusterRepo, regionRepo, regionServerCli := setupApplyRepoMocks(ctrl)

	regionServerCli.EXPECT().GetGKEHeterogeneousDeploy(
		gomock.Any(), gomock.Any(), gomock.Any(),
	).Return(enabledDeploy(
		[]string{"n2-standard-16", "n2d-standard-8"},
		[]string{"np-tikv-base", "np-tikv-alt"},
	), nil)

	err := ApplyWithNodePoolTarget(
		context.Background(),
		clusterRepo, regionRepo, regionServerCli,
		applyReq(),
	)
	require.Error(t, err)
	require.Equal(t, bizerr.ErrCodeParams, err.ErrCode())
	require.Contains(t, err.Error(), "cannot change instance types")
}

func TestApplyWithNodePoolTargetExistingDeployRequiresBoundNodePool(t *testing.T) {
	ctrl := gomock.NewController(t)
	defer ctrl.Finish()

	clusterRepo, regionRepo, regionServerCli := setupApplyRepoMocks(ctrl)

	regionServerCli.EXPECT().GetGKEHeterogeneousDeploy(
		gomock.Any(), gomock.Any(), gomock.Any(),
	).Return(enabledDeploy(
		[]string{"n2-standard-8", "n2d-standard-8"},
		[]string{"np-tikv-base", "np-tikv-alt"},
	), nil)

	regionServerCli.EXPECT().GetGKEHeterogeneousNodePools(
		gomock.Any(), gomock.Any(), gomock.Any(),
	).Return(&regionaldedicatedv1.ListGKEHeterogeneousNodePoolsResponse{
		NodePools: []*regionaldedicatedv1.GKEHeterogeneousNodePool{
			{
				NodePoolName: "np-tikv-base",
				InstanceType: "n2-standard-8",
				Exists:       true,
			},
		},
	}, nil)

	err := ApplyWithNodePoolTarget(
		context.Background(),
		clusterRepo, regionRepo, regionServerCli,
		applyReq(),
	)
	require.Error(t, err)
	require.Equal(t, bizerr.ErrCodeRecordNotFound, err.ErrCode())
	require.Contains(t, err.Error(), "bound gke heterogeneous node pool np-tikv-alt not found")
}

func TestApplyWithNodePoolTargetExistingDeployRejectsMissingAlternativeNodePool(t *testing.T) {
	ctrl := gomock.NewController(t)
	defer ctrl.Finish()

	clusterRepo, regionRepo, regionServerCli := setupApplyRepoMocks(ctrl)

	regionServerCli.EXPECT().GetGKEHeterogeneousDeploy(
		gomock.Any(), gomock.Any(), gomock.Any(),
	).Return(enabledDeploy(
		[]string{"n2-standard-8", "n2d-standard-8"},
		[]string{"np-tikv-base", "np-tikv-alt"},
	), nil)

	regionServerCli.EXPECT().GetGKEHeterogeneousNodePools(
		gomock.Any(), gomock.Any(), gomock.Any(),
	).Return(&regionaldedicatedv1.ListGKEHeterogeneousNodePoolsResponse{
		NodePools: []*regionaldedicatedv1.GKEHeterogeneousNodePool{
			{
				NodePoolName: "np-tikv-base",
				InstanceType: "n2-standard-8",
				Exists:       true,
			},
			{
				NodePoolName: "np-tikv-alt",
				InstanceType: "n2d-standard-8",
				Exists:       false,
			},
		},
	}, nil)

	err := ApplyWithNodePoolTarget(
		context.Background(),
		clusterRepo, regionRepo, regionServerCli,
		applyReq(),
	)
	require.Error(t, err)
	require.Equal(t, bizerr.ErrCodeRecordNotFound, err.ErrCode())
	require.Contains(t, err.Error(), "bound gke heterogeneous node pool np-tikv-alt does not exist")
}

func TestApplyWithNodePoolTargetExistingDeployRejectsMissingBaseNodePool(t *testing.T) {
	ctrl := gomock.NewController(t)
	defer ctrl.Finish()

	clusterRepo, regionRepo, regionServerCli := setupApplyRepoMocks(ctrl)

	regionServerCli.EXPECT().GetGKEHeterogeneousDeploy(
		gomock.Any(), gomock.Any(), gomock.Any(),
	).Return(enabledDeploy(
		[]string{"n2-standard-8", "n2d-standard-8"},
		[]string{"np-tikv-base", "np-tikv-alt"},
	), nil)

	regionServerCli.EXPECT().GetGKEHeterogeneousNodePools(
		gomock.Any(), gomock.Any(), gomock.Any(),
	).Return(&regionaldedicatedv1.ListGKEHeterogeneousNodePoolsResponse{
		NodePools: []*regionaldedicatedv1.GKEHeterogeneousNodePool{
			{
				NodePoolName: "np-tikv-base",
				InstanceType: "n2-standard-8",
				Exists:       false,
			},
			{
				NodePoolName: "np-tikv-alt",
				InstanceType: "n2d-standard-8",
				Exists:       true,
			},
		},
	}, nil)

	err := ApplyWithNodePoolTarget(
		context.Background(),
		clusterRepo, regionRepo, regionServerCli,
		applyReq(),
	)
	require.Error(t, err)
	require.Equal(t, bizerr.ErrCodeRecordNotFound, err.ErrCode())
	require.Contains(t, err.Error(), "bound gke heterogeneous node pool np-tikv-base does not exist")
}

func TestPreviewApplyExistingDeployRejectsMissingNodePool(t *testing.T) {
	ctrl := gomock.NewController(t)
	defer ctrl.Finish()

	clusterRepo, regionRepo, regionServerCli := setupApplyRepoMocks(ctrl)
	featGateCli := mock_featuregate.NewMockService(ctrl)
	featGateCli.EXPECT().CheckFeatureEnabled(
		featuregate.GKEHeterogeneousDeploy,
		gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any(),
	).Return(true)

	regionServerCli.EXPECT().GetGKEHeterogeneousDeploy(
		gomock.Any(), gomock.Any(), gomock.Any(),
	).Return(enabledDeploy(
		[]string{"n2-standard-8", "n2d-standard-8"},
		[]string{"np-tikv-base", "np-tikv-alt"},
	), nil)

	regionServerCli.EXPECT().GetGKEHeterogeneousNodePools(
		gomock.Any(), gomock.Any(), gomock.Any(),
	).Return(&regionaldedicatedv1.ListGKEHeterogeneousNodePoolsResponse{
		NodePools: []*regionaldedicatedv1.GKEHeterogeneousNodePool{
			{
				NodePoolName: "np-tikv-base",
				InstanceType: "n2-standard-8",
				Exists:       true,
			},
			{
				NodePoolName: "np-tikv-alt",
				InstanceType: "n2d-standard-8",
				Exists:       false,
			},
		},
	}, nil)

	resp, err := PreviewApply(
		context.Background(),
		clusterRepo, regionRepo, featGateCli, regionServerCli,
		applyReq(),
	)
	require.Nil(t, resp)
	require.Error(t, err)
	require.Equal(t, bizerr.ErrCodeRecordNotFound, err.ErrCode())
	require.Contains(t, err.Error(), "bound gke heterogeneous node pool np-tikv-alt does not exist")
}

func TestGetExistingDeployKeepsDisplayWhenNodePoolMissing(t *testing.T) {
	ctrl := gomock.NewController(t)
	defer ctrl.Finish()

	clusterRepo, regionRepo, regionServerCli := setupApplyRepoMocks(ctrl)

	regionServerCli.EXPECT().GetGKEHeterogeneousDeploy(
		gomock.Any(), gomock.Any(), gomock.Any(),
	).Return(&regionaldedicatedv1.GKEHeterogeneousDeploy{
		Enabled:       true,
		ComponentType: regionaldedicatedv1.ComponentType_TIKV,
		InstanceTypes: []string{"n2-standard-8", "n2d-standard-8"},
		NodePoolNames: []string{"np-tikv-base", "np-tikv-alt"},
		Status:        regionaldedicatedv1.GKEHeterogeneousDeployStatus_GKE_HETEROGENEOUS_DEPLOY_STATUS_INTENT_ENABLED_NOT_READY,
	}, nil)

	regionServerCli.EXPECT().GetGKEHeterogeneousNodePools(
		gomock.Any(), gomock.Any(), gomock.Any(),
	).Return(&regionaldedicatedv1.ListGKEHeterogeneousNodePoolsResponse{}, nil)

	resp, err := Get(
		context.Background(),
		clusterRepo, regionRepo, regionServerCli,
		&globaldedicatedv1.GetGKEHeterogeneousDeployRequest{
			TenantId:      1,
			ProjectId:     2,
			ClusterId:     12345,
			ComponentType: globaldedicatedv1.ComponentType_TIKV,
		},
	)
	require.NoError(t, err)
	require.NotNil(t, resp)
	require.True(t, resp.GetEnabled())
	require.Equal(t, "GKE_HETEROGENEOUS_DEPLOY_STATUS_INTENT_ENABLED_NOT_READY", resp.GetStatus())
}

func TestApplyWithNodePoolTargetCreateExistGetOne(t *testing.T) {
	ctrl := gomock.NewController(t)
	defer ctrl.Finish()

	clusterRepo, regionRepo, regionServerCli := setupApplyMocks(ctrl)

	regionServerCli.EXPECT().CreateGKEHeterogeneousComputeClass(
		gomock.Any(), gomock.Any(), gomock.Any(),
	).Return(nil, bizerr.New(bizerr.ErrCodeRecordExist, "already exists"))

	regionServerCli.EXPECT().GetGKEHeterogeneousComputeClass(
		gomock.Any(), gomock.Any(), gomock.Any(),
	).Return(&regionaldedicatedv1.GetGKEHeterogeneousComputeClassResponse{
		ComputeClasses: []*regionaldedicatedv1.GKEHeterogeneousComputeClass{
			computeClass(
				"cc-tikv",
				[]string{"np-tikv-base", "np-tikv-alt"},
				[]string{"n2-standard-8", "n2d-standard-8"},
			),
		},
	}, nil)

	regionServerCli.EXPECT().CreateGKEHeterogeneousDeploy(
		gomock.Any(), gomock.Any(), gomock.Any(),
	).DoAndReturn(func(
		_ context.Context,
		_ *entity.Region,
		req *regionaldedicatedv1.CreateGKEHeterogeneousDeployRequest,
	) (*regionaldedicatedv1.GKEHeterogeneousDeploy, bizerr.BizError) {
		require.Len(t, req.GetInstanceTypes(), 1, "regional deploy expects only extra instance type")
		require.Equal(t, []string{"n2d-standard-8"}, req.GetInstanceTypes())
		require.Equal(t, []string{"np-tikv-base", "np-tikv-alt"}, req.GetNodePoolNames(), "regional deploy expects base+extra node pool names")
		return &regionaldedicatedv1.GKEHeterogeneousDeploy{}, nil
	})

	err := ApplyWithNodePoolTarget(
		context.Background(),
		clusterRepo, regionRepo, regionServerCli,
		applyReq(),
	)
	require.NoError(t, err)
}

func TestApplyWithNodePoolTargetCreateExistGetZero(t *testing.T) {
	ctrl := gomock.NewController(t)
	defer ctrl.Finish()

	clusterRepo, regionRepo, regionServerCli := setupApplyMocks(ctrl)

	regionServerCli.EXPECT().CreateGKEHeterogeneousComputeClass(
		gomock.Any(), gomock.Any(), gomock.Any(),
	).Return(nil, bizerr.New(bizerr.ErrCodeRecordExist, "already exists"))

	regionServerCli.EXPECT().GetGKEHeterogeneousComputeClass(
		gomock.Any(), gomock.Any(), gomock.Any(),
	).Return(&regionaldedicatedv1.GetGKEHeterogeneousComputeClassResponse{
		ComputeClasses: []*regionaldedicatedv1.GKEHeterogeneousComputeClass{},
	}, nil)

	err := ApplyWithNodePoolTarget(
		context.Background(),
		clusterRepo, regionRepo, regionServerCli,
		applyReq(),
	)
	require.Error(t, err)
	require.Equal(t, bizerr.ErrCodeRecordNotFound, err.ErrCode())
	require.Contains(t, err.Error(), "compute class not found")
}

func TestApplyWithNodePoolTargetCreateExistGetManySelectsExactBinding(t *testing.T) {
	ctrl := gomock.NewController(t)
	defer ctrl.Finish()

	clusterRepo, regionRepo, regionServerCli := setupApplyMocks(ctrl)

	regionServerCli.EXPECT().CreateGKEHeterogeneousComputeClass(
		gomock.Any(), gomock.Any(), gomock.Any(),
	).Return(nil, bizerr.New(bizerr.ErrCodeRecordExist, "already exists"))

	regionServerCli.EXPECT().GetGKEHeterogeneousComputeClass(
		gomock.Any(), gomock.Any(), gomock.Any(),
	).Return(&regionaldedicatedv1.GetGKEHeterogeneousComputeClassResponse{
		ComputeClasses: []*regionaldedicatedv1.GKEHeterogeneousComputeClass{
			computeClass(
				"cc-other",
				[]string{"np-other-base", "np-other-alt"},
				[]string{"n2-standard-8", "n2d-standard-8"},
			),
			computeClass(
				"cc-exact",
				[]string{"np-tikv-base", "np-tikv-alt"},
				[]string{"n2-standard-8", "n2d-standard-8"},
			),
		},
	}, nil)

	regionServerCli.EXPECT().CreateGKEHeterogeneousDeploy(
		gomock.Any(), gomock.Any(), gomock.Any(),
	).DoAndReturn(func(
		_ context.Context,
		_ *entity.Region,
		req *regionaldedicatedv1.CreateGKEHeterogeneousDeployRequest,
	) (*regionaldedicatedv1.GKEHeterogeneousDeploy, bizerr.BizError) {
		require.Equal(t, "cc-exact", req.GetComputeClassName())
		return &regionaldedicatedv1.GKEHeterogeneousDeploy{}, nil
	})

	err := ApplyWithNodePoolTarget(
		context.Background(),
		clusterRepo, regionRepo, regionServerCli,
		applyReq(),
	)
	require.NoError(t, err)
}

func TestApplyWithNodePoolTargetCreateExistGetOneRejectsBindingMismatch(t *testing.T) {
	ctrl := gomock.NewController(t)
	defer ctrl.Finish()

	clusterRepo, regionRepo, regionServerCli := setupApplyMocks(ctrl)

	regionServerCli.EXPECT().CreateGKEHeterogeneousComputeClass(
		gomock.Any(), gomock.Any(), gomock.Any(),
	).Return(nil, bizerr.New(bizerr.ErrCodeRecordExist, "already exists"))

	regionServerCli.EXPECT().GetGKEHeterogeneousComputeClass(
		gomock.Any(), gomock.Any(), gomock.Any(),
	).Return(&regionaldedicatedv1.GetGKEHeterogeneousComputeClassResponse{
		ComputeClasses: []*regionaldedicatedv1.GKEHeterogeneousComputeClass{
			computeClass(
				"cc-other",
				[]string{"np-other-base", "np-other-alt"},
				[]string{"n2-standard-8", "n2d-standard-8"},
			),
		},
	}, nil)

	err := ApplyWithNodePoolTarget(
		context.Background(),
		clusterRepo, regionRepo, regionServerCli,
		applyReq(),
	)
	require.Error(t, err)
	require.Equal(t, bizerr.ErrCodeRegionalAPI, err.ErrCode())
	require.Contains(t, err.Error(), "no compute class matches requested binding")
}
