package region

import (
	"context"
	"errors"
	"testing"

	. "github.com/onsi/gomega"
	"go.uber.org/mock/gomock"
	"google.golang.org/grpc"
	"google.golang.org/protobuf/proto"

	globalcommonv1 "github.com/tidbcloud/tidb-management-service/api/spec/global/common/v1"
	globalngv1 "github.com/tidbcloud/tidb-management-service/api/spec/global/nextgen/v1"
	openapicommonv1beta1 "github.com/tidbcloud/tidb-management-service/api/spec/tidb_cloud_open_api/common/v1beta1"
	openapingv1beta2 "github.com/tidbcloud/tidb-management-service/api/spec/tidb_cloud_open_api/nextgen/v1beta2"
	tidbmgmtservicev1 "github.com/tidbcloud/tidb-management-service/api/spec/tidb_mgmt_service/v1"
	"github.com/tidbcloud/tidb-management-service/tidb-mgmt-service/domain/adapter/account/mock_account"
	"github.com/tidbcloud/tidb-management-service/tidb-mgmt-service/domain/adapter/nextgenglobal/mock_nextgenglobal"
	"github.com/tidbcloud/tidb-management-service/tidb-mgmt-service/domain/entity"
	"github.com/tidbcloud/tidb-management-service/utilities/grpc/metadata"
)

const byocSharedComponentScaleVcpusLimitBlockingReason = "scaled vcpus usage exceeds resource pool limit; increase or reset the pool vCPU limit before retrying"

type emptyClusterCustomStatusLister struct{}

func (emptyClusterCustomStatusLister) ListClusterCustomStatus(
	context.Context,
	[]uint64,
	[]tidbmgmtservicev1.CustomStatusType,
	bool,
) (map[uint64][]*entity.ClusterCustomStatus, error) {
	return map[uint64][]*entity.ClusterCustomStatus{}, nil
}

type errorClusterCustomStatusLister struct{}

func (errorClusterCustomStatusLister) ListClusterCustomStatus(
	context.Context,
	[]uint64,
	[]tidbmgmtservicev1.CustomStatusType,
	bool,
) (map[uint64][]*entity.ClusterCustomStatus, error) {
	return nil, errors.New("custom status unavailable")
}

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

	mockNextgen := mock_nextgenglobal.NewMockNextGenGlobal(ctrl)
	mockAccount := mock_account.NewMockClient(ctrl)
	server := NewServer(mockNextgen, mockAccount, emptyClusterCustomStatusLister{})

	ctx := metadata.AppendToIncomingMeta(t.Context(), &metadata.RequestMeta{
		OrgID:             1001,
		UserID:            "user-1",
		DefaultProjectID:  2001,
		ValidProjectList:  []uint64{2001},
		IsCheckPermission: boolPtr(true),
	})

	mockNextgen.EXPECT().ListByocVcpuPools(gomock.Any(), &globalngv1.ListByocVcpuPoolsRequest{
		OrganizationId:  1001,
		CloudProviderId: globalcommonv1.CloudProvider_AWS,
		RegionId:        globalcommonv1.Region_AWS_US_WEST_2,
	}).Return(&globalngv1.ListByocVcpuPoolsResponse{
		Groups: []*globalngv1.ListByocVcpuPoolsResponse_Group{
			{
				Region: &globalcommonv1.Region{
					Id:              globalcommonv1.Region_AWS_US_WEST_2,
					CloudProviderId: globalcommonv1.CloudProvider_AWS,
					DisplayName:     "Oregon",
				},
				TotalVcpusLimit: 30,
				Pools: []*globalngv1.ListByocVcpuPoolsResponse_Pool{
					{
						ResourcePoolId: 21,
						Name:           "default",
						DataplaneId:    "dp-1",
						VcpusLimit:     30,
						VcpusUsed:      18,
						IsDefault:      true,
						State:          globalngv1.ListByocVcpuPoolsResponse_MODIFYING,
						TidbInstances: []*globalngv1.ListByocVcpuPoolsResponse_TidbInstance{
							{ClusterId: 1, BaseState: globalngv1.Cluster_AVAILABLE},
							{ClusterId: 2, BaseState: globalngv1.Cluster_AVAILABLE},
							{ClusterId: 3, BaseState: globalngv1.Cluster_AVAILABLE},
							{ClusterId: 4, BaseState: globalngv1.Cluster_CREATING},
							{ClusterId: 5, BaseState: globalngv1.Cluster_AVAILABLE, Deleting: true},
							{ClusterId: 6, BaseState: globalngv1.Cluster_MODIFYING},
							{ClusterId: 7, BaseState: globalngv1.Cluster_UNAVAILABLE},
						},
					},
				},
			},
			{
				Region: &globalcommonv1.Region{
					Id:              globalcommonv1.Region_GCP_US_EAST1,
					CloudProviderId: globalcommonv1.CloudProvider_GCP,
					DisplayName:     "Virginia",
				},
				TotalVcpusLimit: 10,
				Pools: []*globalngv1.ListByocVcpuPoolsResponse_Pool{
					{ResourcePoolId: 31, Name: "hidden", DataplaneId: "dp-2", VcpusLimit: 10, VcpusUsed: 9},
				},
			},
		},
	}, nil)
	mockAccount.EXPECT().GetTenantThirdPartyAccountProviders(gomock.Any(), uint64(1001)).
		Return([]openapicommonv1beta1.Region_CloudProvider{openapicommonv1beta1.Region_aws}, nil)

	resp, err := server.ListByocVcpuPools(ctx, &openapingv1beta2.ListByocVcpuPoolsRequest{
		CloudProvider: "aws",
		RegionId:      "aws-us-west-2",
	})
	g.Expect(err).Should(BeNil())
	g.Expect(resp.GetGroups()).To(HaveLen(1))
	g.Expect(resp.GetGroups()[0].GetRegion().GetRegionId()).To(Equal("aws-us-west-2"))
	g.Expect(resp.GetGroups()[0].GetPools()).To(HaveLen(1))
	g.Expect(resp.GetGroups()[0].GetPools()[0].GetResourcePoolId()).To(Equal("21"))
	g.Expect(resp.GetGroups()[0].GetPools()[0].GetVcpusUsed()).To(Equal(int64(18)))
	g.Expect(resp.GetGroups()[0].GetPools()[0].GetState()).To(Equal(openapingv1beta2.ByocVcpuPool_MODIFYING))
	g.Expect(resp.GetGroups()[0].GetPools()[0].GetTidbInstancesState().GetActive()).To(Equal(int32(3)))
	g.Expect(resp.GetGroups()[0].GetPools()[0].GetTidbInstancesState().GetCreating()).To(Equal(int32(1)))
	g.Expect(resp.GetGroups()[0].GetPools()[0].GetTidbInstancesState().GetDeleting()).To(Equal(int32(1)))
	g.Expect(resp.GetGroups()[0].GetPools()[0].GetTidbInstancesState().GetModifying()).To(Equal(int32(2)))
}

func TestAggregateByocVcpuPoolTidbInstancesState(t *testing.T) {
	g := NewWithT(t)
	restoring := openapicommonv1beta1.ClusterState_RESTORING

	state := aggregateByocVcpuPoolTidbInstancesState(
		[]*globalngv1.ListByocVcpuPoolsResponse_TidbInstance{
			{ClusterId: 1, BaseState: globalngv1.Cluster_AVAILABLE},
			{ClusterId: 2, BaseState: globalngv1.Cluster_AVAILABLE},
			{ClusterId: 3, BaseState: globalngv1.Cluster_CREATING},
			{ClusterId: 4, BaseState: globalngv1.Cluster_AVAILABLE, Deleting: true},
			{ClusterId: 5, BaseState: globalngv1.Cluster_UNAVAILABLE},
		},
		map[uint64][]*entity.ClusterCustomStatus{
			2: {{ClusterId: 2, OverrideState: &restoring}},
		},
	)

	g.Expect(state).To(Equal(&openapingv1beta2.ByocVcpuPool_TidbInstancesState{
		Active:    1,
		Modifying: 2,
		Creating:  1,
		Deleting:  1,
	}))
	g.Expect(aggregateByocVcpuPoolTidbInstancesState(nil, nil)).To(Equal(
		&openapingv1beta2.ByocVcpuPool_TidbInstancesState{},
	))
}

func TestListByocVcpuPoolCustomStatuses_FailureReturnsError(t *testing.T) {
	g := NewWithT(t)
	server := serverImpl{customStatusSvc: errorClusterCustomStatusLister{}}

	_, err := server.listByocVcpuPoolCustomStatuses(t.Context(), &globalngv1.ListByocVcpuPoolsResponse{
		Groups: []*globalngv1.ListByocVcpuPoolsResponse_Group{{
			Pools: []*globalngv1.ListByocVcpuPoolsResponse_Pool{{
				TidbInstances: []*globalngv1.ListByocVcpuPoolsResponse_TidbInstance{{ClusterId: 1}},
			}},
		}},
	})

	g.Expect(err).NotTo(BeNil())
	g.Expect(err.Error()).To(ContainSubstring("failed to get cluster custom status record"))
}

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

	mockNextgen := mock_nextgenglobal.NewMockNextGenGlobal(ctrl)
	mockAccount := mock_account.NewMockClient(ctrl)
	server := NewServer(mockNextgen, mockAccount, emptyClusterCustomStatusLister{})

	ctx := metadata.AppendToIncomingMeta(t.Context(), &metadata.RequestMeta{
		OrgID:             1001,
		UserID:            "user-1",
		DefaultProjectID:  2001,
		ValidProjectList:  []uint64{2001},
		IsCheckPermission: boolPtr(true),
	})

	resp, err := server.ListByocVcpuPools(ctx, &openapingv1beta2.ListByocVcpuPoolsRequest{
		RegionId: "aws-not-a-real-region",
	})
	g.Expect(err).Should(BeNil())
	g.Expect(resp.GetGroups()).To(BeEmpty())
}

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

	mockNextgen := mock_nextgenglobal.NewMockNextGenGlobal(ctrl)
	mockAccount := mock_account.NewMockClient(ctrl)
	server := NewServer(mockNextgen, mockAccount, emptyClusterCustomStatusLister{})

	ctx := metadata.AppendToIncomingMeta(t.Context(), &metadata.RequestMeta{
		OrgID:             1001,
		UserID:            "user-1",
		DefaultProjectID:  2001,
		ValidProjectList:  []uint64{2001},
		IsCheckPermission: boolPtr(true),
	})

	mockNextgen.EXPECT().ListByocVcpuPools(gomock.Any(), &globalngv1.ListByocVcpuPoolsRequest{
		OrganizationId: 1001,
	}).Return(&globalngv1.ListByocVcpuPoolsResponse{
		Groups: []*globalngv1.ListByocVcpuPoolsResponse_Group{
			{
				Region: &globalcommonv1.Region{
					Id:              globalcommonv1.Region_AWS_US_WEST_2,
					CloudProviderId: globalcommonv1.CloudProvider_AWS,
					DisplayName:     "Oregon",
				},
				TotalVcpusLimit: 30,
				Pools: []*globalngv1.ListByocVcpuPoolsResponse_Pool{
					{ResourcePoolId: 21, Name: "default", DataplaneId: "dp-1", VcpusLimit: 30, VcpusUsed: 18, IsDefault: true},
				},
			},
		},
	}, nil)
	mockAccount.EXPECT().GetTenantThirdPartyAccountProviders(gomock.Any(), uint64(1001)).
		Return([]openapicommonv1beta1.Region_CloudProvider{}, nil)

	resp, err := server.ListByocVcpuPools(ctx, &openapingv1beta2.ListByocVcpuPoolsRequest{})
	g.Expect(err).Should(BeNil())
	g.Expect(resp.GetGroups()).To(HaveLen(1))
	g.Expect(resp.GetGroups()[0].GetRegion().GetCloudProvider()).To(Equal(openapicommonv1beta1.Region_aws))
	g.Expect(resp.GetGroups()[0].GetPools()).To(HaveLen(1))
	g.Expect(resp.GetGroups()[0].GetPools()[0].GetResourcePoolId()).To(Equal("21"))
}

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

	mockNextgen := mock_nextgenglobal.NewMockNextGenGlobal(ctrl)
	mockAccount := mock_account.NewMockClient(ctrl)
	server := NewServer(mockNextgen, mockAccount, emptyClusterCustomStatusLister{})

	ctx := metadata.AppendToIncomingMeta(t.Context(), &metadata.RequestMeta{
		OrgID:             1001,
		UserID:            "user-1",
		DefaultProjectID:  2001,
		ValidProjectList:  []uint64{2001},
		IsCheckPermission: boolPtr(true),
	})

	_, err := server.ListByocVcpuPools(ctx, &openapingv1beta2.ListByocVcpuPoolsRequest{
		CloudProvider: "gcp",
		RegionId:      "aws-us-west-2",
	})
	g.Expect(err).ShouldNot(BeNil())
}

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

	mockNextgen := mock_nextgenglobal.NewMockNextGenGlobal(ctrl)
	mockAccount := mock_account.NewMockClient(ctrl)
	server := NewServer(mockNextgen, mockAccount, emptyClusterCustomStatusLister{})

	ctx := metadata.AppendToIncomingMeta(t.Context(), &metadata.RequestMeta{
		OrgID:             1001,
		UserID:            "user-1",
		DefaultProjectID:  2001,
		ValidProjectList:  []uint64{2001},
		IsCheckPermission: boolPtr(true),
	})

	mockNextgen.EXPECT().ListByocVcpuPools(gomock.Any(), &globalngv1.ListByocVcpuPoolsRequest{
		OrganizationId: 1001,
	}).Return(nil, errors.New("boom"))

	_, err := server.ListByocVcpuPools(ctx, &openapingv1beta2.ListByocVcpuPoolsRequest{})
	g.Expect(err).ShouldNot(BeNil())
}

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

	mockNextgen := mock_nextgenglobal.NewMockNextGenGlobal(ctrl)
	mockAccount := mock_account.NewMockClient(ctrl)
	server := NewServer(mockNextgen, mockAccount, emptyClusterCustomStatusLister{})

	ctx := metadata.AppendToIncomingMeta(t.Context(), &metadata.RequestMeta{
		OrgID:             1001,
		UserID:            "user-1",
		DefaultProjectID:  2001,
		ValidProjectList:  []uint64{2001},
		IsCheckPermission: boolPtr(true),
	})

	mockNextgen.EXPECT().ListByocVcpuPools(gomock.Any(), &globalngv1.ListByocVcpuPoolsRequest{
		OrganizationId: 1001,
	}).Return(&globalngv1.ListByocVcpuPoolsResponse{}, nil)
	mockAccount.EXPECT().GetTenantThirdPartyAccountProviders(gomock.Any(), uint64(1001)).
		Return(nil, errors.New("boom"))

	_, err := server.ListByocVcpuPools(ctx, &openapingv1beta2.ListByocVcpuPoolsRequest{})
	g.Expect(err).ShouldNot(BeNil())
}

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

	mockNextgen := mock_nextgenglobal.NewMockNextGenGlobal(ctrl)
	mockAccount := mock_account.NewMockClient(ctrl)
	server := NewServer(mockNextgen, mockAccount, emptyClusterCustomStatusLister{})

	ctx := metadata.AppendToIncomingMeta(t.Context(), &metadata.RequestMeta{
		OrgID:             1001,
		UserID:            "user-1",
		DefaultProjectID:  2001,
		ValidProjectList:  []uint64{2001},
		IsCheckPermission: boolPtr(true),
	})

	mockNextgen.EXPECT().GetResourcePool(gomock.Any(), &globalngv1.GetResourcePoolRequest{
		ResourcePoolId: 21,
	}).Return(&globalngv1.ResourcePoolProfile{
		ResourcePool: &globalngv1.ResourcePool{
			Id:          21,
			OwnerId:     1001,
			OwnerType:   globalngv1.ResourcePool_ORGANIZATION,
			ServicePlan: globalcommonv1.ServicePlan_BYOC,
			ProviderId:  globalcommonv1.CloudProvider_AWS,
			RegionId:    globalcommonv1.Region_AWS_US_WEST_2,
		},
	}, nil)
	mockNextgen.EXPECT().UpdateResourcePoolInstanceLimit(gomock.Any(), &globalngv1.UpdateResourcePoolInstanceLimitRequest{
		ResourcePoolId: 21,
		VcpusLimit:     proto.Uint64(64),
	}).Return(&globalngv1.ResourcePool{
		Id:         21,
		ProviderId: globalcommonv1.CloudProvider_AWS,
		RegionId:   globalcommonv1.Region_AWS_US_WEST_2,
	}, nil)
	mockNextgen.EXPECT().ListByocVcpuPools(gomock.Any(), &globalngv1.ListByocVcpuPoolsRequest{
		OrganizationId:  1001,
		CloudProviderId: globalcommonv1.CloudProvider_AWS,
		RegionId:        globalcommonv1.Region_AWS_US_WEST_2,
	}).Return(&globalngv1.ListByocVcpuPoolsResponse{
		Groups: []*globalngv1.ListByocVcpuPoolsResponse_Group{
			{
				Region: &globalcommonv1.Region{
					Id:              globalcommonv1.Region_AWS_US_WEST_2,
					CloudProviderId: globalcommonv1.CloudProvider_AWS,
				},
				Pools: []*globalngv1.ListByocVcpuPoolsResponse_Pool{
					{
						ResourcePoolId: 21,
						Name:           "default",
						DataplaneId:    "dp-1",
						VcpusLimit:     64,
						VcpusUsed:      18,
						IsDefault:      true,
						State:          globalngv1.ListByocVcpuPoolsResponse_AVAILABLE,
					},
				},
			},
		},
	}, nil).Times(2)

	resp, err := server.UpdateByocVcpuPool(ctx, &openapingv1beta2.UpdateByocVcpuPoolRequest{
		ResourcePoolId: "21",
		ByocVcpuPool: &openapingv1beta2.ByocVcpuPool{
			VcpusLimit: proto.Int64(64),
		},
	})
	g.Expect(err).Should(BeNil())
	g.Expect(resp.GetResourcePoolId()).To(Equal("21"))
	g.Expect(resp.GetVcpusLimit()).To(Equal(int64(64)))
	g.Expect(resp.GetVcpusUsed()).To(Equal(int64(18)))
	g.Expect(resp.GetIsDefault()).To(BeTrue())
	g.Expect(resp.GetState()).To(Equal(openapingv1beta2.ByocVcpuPool_AVAILABLE))
}

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

	mockNextgen := mock_nextgenglobal.NewMockNextGenGlobal(ctrl)
	mockAccount := mock_account.NewMockClient(ctrl)
	server := NewServer(mockNextgen, mockAccount, emptyClusterCustomStatusLister{})

	ctx := metadata.AppendToIncomingMeta(t.Context(), &metadata.RequestMeta{
		OrgID:             1001,
		UserID:            "user-1",
		DefaultProjectID:  2001,
		ValidProjectList:  []uint64{2001},
		IsCheckPermission: boolPtr(true),
	})

	mockNextgen.EXPECT().GetResourcePool(gomock.Any(), &globalngv1.GetResourcePoolRequest{
		ResourcePoolId: 21,
	}).Return(&globalngv1.ResourcePoolProfile{
		ResourcePool: &globalngv1.ResourcePool{
			Id:          21,
			OwnerId:     1001,
			OwnerType:   globalngv1.ResourcePool_ORGANIZATION,
			ServicePlan: globalcommonv1.ServicePlan_BYOC,
			ProviderId:  globalcommonv1.CloudProvider_AWS,
			RegionId:    globalcommonv1.Region_AWS_US_WEST_2,
		},
	}, nil)
	mockNextgen.EXPECT().ListByocVcpuPools(gomock.Any(), &globalngv1.ListByocVcpuPoolsRequest{
		OrganizationId:  1001,
		CloudProviderId: globalcommonv1.CloudProvider_AWS,
		RegionId:        globalcommonv1.Region_AWS_US_WEST_2,
	}).Return(&globalngv1.ListByocVcpuPoolsResponse{
		Groups: []*globalngv1.ListByocVcpuPoolsResponse_Group{
			{
				Pools: []*globalngv1.ListByocVcpuPoolsResponse_Pool{
					{
						ResourcePoolId: 21,
						State:          globalngv1.ListByocVcpuPoolsResponse_MODIFYING,
					},
				},
			},
		},
	}, nil)

	resp, err := server.UpdateByocVcpuPool(ctx, &openapingv1beta2.UpdateByocVcpuPoolRequest{
		ResourcePoolId: "21",
		ByocVcpuPool: &openapingv1beta2.ByocVcpuPool{
			VcpusLimit: proto.Int64(64),
		},
	})
	g.Expect(resp).To(BeNil())
	g.Expect(err).ShouldNot(BeNil())
	g.Expect(err.Error()).To(ContainSubstring("byoc vcpu pool is modifying"))
}

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

	mockNextgen := mock_nextgenglobal.NewMockNextGenGlobal(ctrl)
	mockAccount := mock_account.NewMockClient(ctrl)
	server := NewServer(mockNextgen, mockAccount, emptyClusterCustomStatusLister{})

	ctx := metadata.AppendToIncomingMeta(t.Context(), &metadata.RequestMeta{
		OrgID:             1001,
		UserID:            "user-1",
		DefaultProjectID:  2001,
		ValidProjectList:  []uint64{2001},
		IsCheckPermission: boolPtr(true),
	})

	resp, err := server.UpdateByocVcpuPool(ctx, &openapingv1beta2.UpdateByocVcpuPoolRequest{
		ResourcePoolId: "21",
	})
	g.Expect(resp).To(BeNil())
	g.Expect(err).ShouldNot(BeNil())
	g.Expect(err.Error()).To(ContainSubstring("byoc_vcpu_pool is required"))
}

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

	mockNextgen := mock_nextgenglobal.NewMockNextGenGlobal(ctrl)
	mockAccount := mock_account.NewMockClient(ctrl)
	server := NewServer(mockNextgen, mockAccount, emptyClusterCustomStatusLister{})

	ctx := metadata.AppendToIncomingMeta(t.Context(), &metadata.RequestMeta{
		OrgID:             1001,
		UserID:            "user-1",
		DefaultProjectID:  2001,
		ValidProjectList:  []uint64{2001},
		IsCheckPermission: boolPtr(true),
	})

	resp, err := server.UpdateByocVcpuPool(ctx, &openapingv1beta2.UpdateByocVcpuPoolRequest{
		ResourcePoolId: "21",
		ByocVcpuPool:   &openapingv1beta2.ByocVcpuPool{},
	})
	g.Expect(resp).To(BeNil())
	g.Expect(err).ShouldNot(BeNil())
	g.Expect(err.Error()).To(ContainSubstring("vcpus_limit is required"))
}

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

	mockNextgen := mock_nextgenglobal.NewMockNextGenGlobal(ctrl)
	mockAccount := mock_account.NewMockClient(ctrl)
	server := NewServer(mockNextgen, mockAccount, emptyClusterCustomStatusLister{})

	ctx := metadata.AppendToIncomingMeta(t.Context(), &metadata.RequestMeta{
		OrgID:             1001,
		UserID:            "user-1",
		DefaultProjectID:  2001,
		ValidProjectList:  []uint64{2001},
		IsCheckPermission: boolPtr(true),
	})

	resp, err := server.UpdateByocVcpuPool(ctx, &openapingv1beta2.UpdateByocVcpuPoolRequest{
		ResourcePoolId: "21",
		ByocVcpuPool: &openapingv1beta2.ByocVcpuPool{
			VcpusLimit: proto.Int64(-5),
		},
	})
	g.Expect(resp).To(BeNil())
	g.Expect(err).ShouldNot(BeNil())
	g.Expect(err.Error()).To(ContainSubstring("vcpus_limit must be greater than or equal to 0"))
}

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

	mockNextgen := mock_nextgenglobal.NewMockNextGenGlobal(ctrl)
	mockAccount := mock_account.NewMockClient(ctrl)
	server := NewServer(mockNextgen, mockAccount, emptyClusterCustomStatusLister{})

	ctx := metadata.AppendToIncomingMeta(t.Context(), &metadata.RequestMeta{
		OrgID:             1001,
		UserID:            "user-1",
		DefaultProjectID:  2001,
		ValidProjectList:  []uint64{2001},
		IsCheckPermission: boolPtr(true),
	})

	mockNextgen.EXPECT().GetResourcePool(gomock.Any(), &globalngv1.GetResourcePoolRequest{
		ResourcePoolId: 21,
	}).Return(&globalngv1.ResourcePoolProfile{
		ResourcePool: &globalngv1.ResourcePool{
			Id:          21,
			OwnerId:     1001,
			OwnerType:   globalngv1.ResourcePool_ORGANIZATION,
			ServicePlan: globalcommonv1.ServicePlan_BYOC,
			ProviderId:  globalcommonv1.CloudProvider_AWS,
			RegionId:    globalcommonv1.Region_AWS_US_WEST_2,
		},
	}, nil)
	mockNextgen.EXPECT().ListByocVcpuPools(gomock.Any(), &globalngv1.ListByocVcpuPoolsRequest{
		OrganizationId:  1001,
		CloudProviderId: globalcommonv1.CloudProvider_AWS,
		RegionId:        globalcommonv1.Region_AWS_US_WEST_2,
	}).Return(&globalngv1.ListByocVcpuPoolsResponse{
		Groups: []*globalngv1.ListByocVcpuPoolsResponse_Group{
			{
				Pools: []*globalngv1.ListByocVcpuPoolsResponse_Pool{
					{
						ResourcePoolId: 21,
						State:          globalngv1.ListByocVcpuPoolsResponse_AVAILABLE,
					},
				},
			},
		},
	}, nil)
	mockNextgen.EXPECT().UpdateResourcePoolInstanceLimit(gomock.Any(), &globalngv1.UpdateResourcePoolInstanceLimitRequest{
		ResourcePoolId: 21,
		VcpusLimit:     proto.Uint64(64),
	}).Return(nil, errors.New("boom"))

	resp, err := server.UpdateByocVcpuPool(ctx, &openapingv1beta2.UpdateByocVcpuPoolRequest{
		ResourcePoolId: "21",
		ByocVcpuPool: &openapingv1beta2.ByocVcpuPool{
			VcpusLimit: proto.Int64(64),
		},
	})
	g.Expect(resp).To(BeNil())
	g.Expect(err).ShouldNot(BeNil())
	g.Expect(err.Error()).To(ContainSubstring("boom"))
}

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

	mockNextgen := mock_nextgenglobal.NewMockNextGenGlobal(ctrl)
	mockAccount := mock_account.NewMockClient(ctrl)
	server := NewServer(mockNextgen, mockAccount, emptyClusterCustomStatusLister{})

	ctx := metadata.AppendToIncomingMeta(t.Context(), &metadata.RequestMeta{
		OrgID:             1001,
		UserID:            "user-1",
		DefaultProjectID:  2001,
		ValidProjectList:  []uint64{2001},
		IsCheckPermission: boolPtr(true),
	})

	mockNextgen.EXPECT().GetResourcePool(gomock.Any(), &globalngv1.GetResourcePoolRequest{
		ResourcePoolId: 21,
	}).Return(&globalngv1.ResourcePoolProfile{
		ResourcePool: &globalngv1.ResourcePool{
			Id:          21,
			OwnerId:     1001,
			OwnerType:   globalngv1.ResourcePool_ORGANIZATION,
			ServicePlan: globalcommonv1.ServicePlan_BYOC,
			ProviderId:  globalcommonv1.CloudProvider_AWS,
			RegionId:    globalcommonv1.Region_AWS_US_WEST_2,
		},
	}, nil)
	mockNextgen.EXPECT().ListByocVcpuPools(gomock.Any(), &globalngv1.ListByocVcpuPoolsRequest{
		OrganizationId:  1001,
		CloudProviderId: globalcommonv1.CloudProvider_AWS,
		RegionId:        globalcommonv1.Region_AWS_US_WEST_2,
	}).Return(nil, errors.New("boom"))

	resp, err := server.UpdateByocVcpuPool(ctx, &openapingv1beta2.UpdateByocVcpuPoolRequest{
		ResourcePoolId: "21",
		ByocVcpuPool: &openapingv1beta2.ByocVcpuPool{
			VcpusLimit: proto.Int64(64),
		},
	})
	g.Expect(resp).To(BeNil())
	g.Expect(err).ShouldNot(BeNil())
	g.Expect(err.Error()).To(ContainSubstring("failed to load byoc vcpu pool state"))
}

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

	mockNextgen := mock_nextgenglobal.NewMockNextGenGlobal(ctrl)
	mockAccount := mock_account.NewMockClient(ctrl)
	server := NewServer(mockNextgen, mockAccount, emptyClusterCustomStatusLister{})

	ctx := metadata.AppendToIncomingMeta(t.Context(), &metadata.RequestMeta{
		OrgID:             1001,
		UserID:            "user-1",
		DefaultProjectID:  2001,
		ValidProjectList:  []uint64{2001},
		IsCheckPermission: boolPtr(true),
	})

	mockNextgen.EXPECT().GetResourcePool(gomock.Any(), &globalngv1.GetResourcePoolRequest{
		ResourcePoolId: 21,
	}).Return(&globalngv1.ResourcePoolProfile{
		ResourcePool: &globalngv1.ResourcePool{
			Id:          21,
			OwnerId:     9999, // different organization
			OwnerType:   globalngv1.ResourcePool_ORGANIZATION,
			ServicePlan: globalcommonv1.ServicePlan_BYOC,
		},
	}, nil)

	resp, err := server.UpdateByocVcpuPool(ctx, &openapingv1beta2.UpdateByocVcpuPoolRequest{
		ResourcePoolId: "21",
		ByocVcpuPool: &openapingv1beta2.ByocVcpuPool{
			VcpusLimit: proto.Int64(64),
		},
	})
	g.Expect(resp).To(BeNil())
	g.Expect(err).ShouldNot(BeNil())
	g.Expect(err.Error()).To(ContainSubstring("resource pool not found"))
}

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

	mockNextgen := mock_nextgenglobal.NewMockNextGenGlobal(ctrl)
	mockAccount := mock_account.NewMockClient(ctrl)
	server := NewServer(mockNextgen, mockAccount, emptyClusterCustomStatusLister{})

	ctx := metadata.AppendToIncomingMeta(t.Context(), &metadata.RequestMeta{
		OrgID:             1001,
		UserID:            "user-1",
		DefaultProjectID:  2001,
		ValidProjectList:  []uint64{2001},
		IsCheckPermission: boolPtr(true),
	})

	mockNextgen.EXPECT().GetResourcePool(gomock.Any(), &globalngv1.GetResourcePoolRequest{
		ResourcePoolId: 21,
	}).Return(&globalngv1.ResourcePoolProfile{
		ResourcePool: &globalngv1.ResourcePool{
			Id:          21,
			OwnerId:     1001,
			OwnerType:   globalngv1.ResourcePool_ORGANIZATION,
			ServicePlan: globalcommonv1.ServicePlan_BYOC,
			VcpusLimit:  80,
		},
	}, nil)
	mockNextgen.EXPECT().GetByocSharedComponentScaleConstraints(gomock.Any(), &globalngv1.GetByocSharedComponentScaleConstraintsRequest{
		ResourcePoolId: 21,
		OrganizationId: 1001,
	}).Return(&globalngv1.ByocSharedComponentScaleConstraints{
		ResourcePoolId: 21,
		VcpusLimit:     80,
		Preview: &globalngv1.ByocSharedComponentScalePreviewSummary{
			CurrentVcpusUsed: 36,
			ScalingVcpusUsed: 36,
			MaxVcpusLimit:    80,
			Allowed:          true,
		},
		Components: []*globalngv1.ByocSharedComponentScaleConstraint{
			{
				ComponentType: globalngv1.ByocSharedComponentType_BYOC_SHARED_COMPONENT_TYPE_TIKV,
				ComponentId:   "cg-tikv",
				Exists:        true,
				Phase:         "AVAILABLE",
				Current: &globalngv1.ByocSharedComponentScaleTarget{
					NodeCount: proto.Uint32(3),
					Cpu:       proto.Uint32(4),
					MemoryGi:  proto.Uint32(16),
				},
			},
		},
	}, nil)

	resp, err := server.GetByocSharedComponentScaleConstraints(ctx, &openapingv1beta2.GetByocSharedComponentScaleConstraintsRequest{
		ResourcePoolId: "21",
	})
	g.Expect(err).Should(BeNil())
	g.Expect(resp.GetResourcePoolId()).To(Equal("21"))
	g.Expect(resp.GetVcpusLimit()).To(Equal(int64(80)))
	g.Expect(resp.GetPreview().GetCurrentVcpusUsed()).To(Equal(int64(36)))
	g.Expect(resp.GetComponents()).To(HaveLen(1))
	g.Expect(resp.GetComponents()[0].GetComponentType()).To(Equal(openapingv1beta2.ByocSharedComponentType_BYOC_SHARED_COMPONENT_TYPE_TIKV))
	g.Expect(resp.GetComponents()[0].GetCurrent().GetNodeCount()).To(Equal(int64(3)))
}

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

	mockNextgen := mock_nextgenglobal.NewMockNextGenGlobal(ctrl)
	mockAccount := mock_account.NewMockClient(ctrl)
	server := NewServer(mockNextgen, mockAccount, emptyClusterCustomStatusLister{})

	ctx := metadata.AppendToIncomingMeta(t.Context(), &metadata.RequestMeta{
		OrgID:             1001,
		UserID:            "user-1",
		DefaultProjectID:  2001,
		ValidProjectList:  []uint64{2001},
		IsCheckPermission: boolPtr(true),
	})

	mockNextgen.EXPECT().GetResourcePool(gomock.Any(), &globalngv1.GetResourcePoolRequest{
		ResourcePoolId: 21,
	}).Return(&globalngv1.ResourcePoolProfile{
		ResourcePool: &globalngv1.ResourcePool{
			Id:          21,
			OwnerId:     1001,
			OwnerType:   globalngv1.ResourcePool_ORGANIZATION,
			ServicePlan: globalcommonv1.ServicePlan_Premium,
		},
	}, nil)

	resp, err := server.GetByocSharedComponentScaleConstraints(ctx, &openapingv1beta2.GetByocSharedComponentScaleConstraintsRequest{
		ResourcePoolId: "21",
	})
	g.Expect(resp).To(BeNil())
	g.Expect(err).ToNot(BeNil())
	g.Expect(err.Error()).To(ContainSubstring("resource pool not found"))
}

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

	mockNextgen := mock_nextgenglobal.NewMockNextGenGlobal(ctrl)
	mockAccount := mock_account.NewMockClient(ctrl)
	server := NewServer(mockNextgen, mockAccount, emptyClusterCustomStatusLister{})

	ctx := metadata.AppendToIncomingMeta(t.Context(), &metadata.RequestMeta{
		OrgID:             1001,
		UserID:            "user-1",
		DefaultProjectID:  2001,
		ValidProjectList:  []uint64{2001},
		IsCheckPermission: boolPtr(true),
	})

	mockNextgen.EXPECT().GetResourcePool(gomock.Any(), &globalngv1.GetResourcePoolRequest{
		ResourcePoolId: 21,
	}).Return(&globalngv1.ResourcePoolProfile{
		ResourcePool: &globalngv1.ResourcePool{
			Id:          21,
			OwnerId:     1001,
			OwnerType:   globalngv1.ResourcePool_ORGANIZATION,
			ServicePlan: globalcommonv1.ServicePlan_BYOC,
			VcpusLimit:  80,
		},
	}, nil)
	mockNextgen.EXPECT().
		ScaleByocSharedComponents(gomock.Any(), gomock.Any()).
		DoAndReturn(func(_ context.Context, req *globalngv1.ScaleByocSharedComponentsRequest, _ ...grpc.CallOption) (*globalngv1.ScaleByocSharedComponentsResponse, error) {
			g.Expect(req.GetResourcePoolId()).To(Equal(uint64(21)))
			g.Expect(req.GetOrganizationId()).To(Equal(uint64(1001)))
			g.Expect(req.GetValidateOnly()).To(BeTrue())
			g.Expect(req.GetTikv().GetNodeCount()).To(Equal(uint32(4)))
			return &globalngv1.ScaleByocSharedComponentsResponse{
				OperationId: "op-123",
				Preview: &globalngv1.ByocSharedComponentScalePreviewSummary{
					CurrentVcpusUsed: 36,
					ScalingVcpusUsed: 40,
					MaxVcpusLimit:    80,
					Allowed:          true,
				},
				Components: []*globalngv1.ByocSharedComponentScalePlanComponent{
					{
						ComponentType: globalngv1.ByocSharedComponentType_BYOC_SHARED_COMPONENT_TYPE_TIKV,
						ComponentId:   "cg-tikv",
						Current: &globalngv1.ByocSharedComponentScaleTarget{
							NodeCount: proto.Uint32(3),
							Cpu:       proto.Uint32(4),
						},
						Target: &globalngv1.ByocSharedComponentScaleTarget{
							NodeCount: proto.Uint32(4),
							Cpu:       proto.Uint32(4),
						},
						Allowed: true,
					},
				},
			}, nil
		})

	resp, err := server.ScaleByocSharedComponents(ctx, &openapingv1beta2.ScaleByocSharedComponentsRequest{
		ResourcePoolId: "21",
		Tikv: &openapingv1beta2.ByocSharedComponentScaleTarget{
			NodeCount: proto.Int64(4),
		},
		ValidateOnly: true,
	})
	g.Expect(err).Should(BeNil())
	g.Expect(resp.GetPreview().GetScalingVcpusUsed()).To(Equal(int64(40)))
	g.Expect(resp.GetOperationId()).To(Equal("op-123"))
	g.Expect(resp.GetComponents()).To(HaveLen(1))
	g.Expect(resp.GetComponents()[0].GetTarget().GetNodeCount()).To(Equal(int64(4)))
}

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

	mockNextgen := mock_nextgenglobal.NewMockNextGenGlobal(ctrl)
	mockAccount := mock_account.NewMockClient(ctrl)
	server := NewServer(mockNextgen, mockAccount, emptyClusterCustomStatusLister{})

	ctx := metadata.AppendToIncomingMeta(t.Context(), &metadata.RequestMeta{
		OrgID:             1001,
		UserID:            "user-1",
		DefaultProjectID:  2001,
		ValidProjectList:  []uint64{2001},
		IsCheckPermission: boolPtr(true),
	})

	mockNextgen.EXPECT().GetResourcePool(gomock.Any(), &globalngv1.GetResourcePoolRequest{
		ResourcePoolId: 21,
	}).Return(&globalngv1.ResourcePoolProfile{
		ResourcePool: &globalngv1.ResourcePool{
			Id:          21,
			OwnerId:     1001,
			OwnerType:   globalngv1.ResourcePool_ORGANIZATION,
			ServicePlan: globalcommonv1.ServicePlan_BYOC,
		},
	}, nil)
	mockNextgen.EXPECT().ScaleByocSharedComponents(gomock.Any(), gomock.Any()).Return(&globalngv1.ScaleByocSharedComponentsResponse{
		Preview: &globalngv1.ByocSharedComponentScalePreviewSummary{
			Allowed:         false,
			BlockingReasons: []string{byocSharedComponentScaleVcpusLimitBlockingReason},
		},
		Components: []*globalngv1.ByocSharedComponentScalePlanComponent{
			{
				ComponentType:   globalngv1.ByocSharedComponentType_BYOC_SHARED_COMPONENT_TYPE_TIKV,
				Allowed:         false,
				BlockingReasons: []string{byocSharedComponentScaleVcpusLimitBlockingReason},
			},
		},
	}, nil)

	resp, err := server.ScaleByocSharedComponents(ctx, &openapingv1beta2.ScaleByocSharedComponentsRequest{
		ResourcePoolId: "21",
		Tikv: &openapingv1beta2.ByocSharedComponentScaleTarget{
			NodeCount: proto.Int64(4),
		},
		ValidateOnly: true,
	})
	g.Expect(err).Should(BeNil())
	g.Expect(resp.GetPreview().GetAllowed()).To(BeFalse())
	g.Expect(resp.GetPreview().GetBlockingReasons()).To(ContainElement(byocSharedComponentScaleVcpusLimitBlockingReason))
}

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

	mockNextgen := mock_nextgenglobal.NewMockNextGenGlobal(ctrl)
	mockAccount := mock_account.NewMockClient(ctrl)
	server := NewServer(mockNextgen, mockAccount, emptyClusterCustomStatusLister{})

	ctx := metadata.AppendToIncomingMeta(t.Context(), &metadata.RequestMeta{
		OrgID:             1001,
		UserID:            "user-1",
		DefaultProjectID:  2001,
		ValidProjectList:  []uint64{2001},
		IsCheckPermission: boolPtr(true),
	})

	mockNextgen.EXPECT().GetResourcePool(gomock.Any(), &globalngv1.GetResourcePoolRequest{
		ResourcePoolId: 21,
	}).Return(&globalngv1.ResourcePoolProfile{
		ResourcePool: &globalngv1.ResourcePool{
			Id:          21,
			OwnerId:     1001,
			OwnerType:   globalngv1.ResourcePool_ORGANIZATION,
			ServicePlan: globalcommonv1.ServicePlan_BYOC,
		},
	}, nil)

	resp, err := server.ScaleByocSharedComponents(ctx, &openapingv1beta2.ScaleByocSharedComponentsRequest{
		ResourcePoolId: "21",
		Tikv: &openapingv1beta2.ByocSharedComponentScaleTarget{
			Cpu: proto.Int64(-1),
		},
		ValidateOnly: true,
	})
	g.Expect(resp).To(BeNil())
	g.Expect(err).ToNot(BeNil())
	g.Expect(err.Error()).To(ContainSubstring("cpu out of range"))
}

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

	mockNextgen := mock_nextgenglobal.NewMockNextGenGlobal(ctrl)
	mockAccount := mock_account.NewMockClient(ctrl)
	server := NewServer(mockNextgen, mockAccount, emptyClusterCustomStatusLister{})

	ctx := metadata.AppendToIncomingMeta(t.Context(), &metadata.RequestMeta{
		OrgID:             1001,
		UserID:            "user-1",
		DefaultProjectID:  2001,
		ValidProjectList:  []uint64{2001},
		IsCheckPermission: boolPtr(true),
	})

	mockNextgen.EXPECT().GetResourcePool(gomock.Any(), &globalngv1.GetResourcePoolRequest{
		ResourcePoolId: 21,
	}).Return(&globalngv1.ResourcePoolProfile{
		ResourcePool: &globalngv1.ResourcePool{
			Id:          21,
			OwnerId:     1001,
			OwnerType:   globalngv1.ResourcePool_ORGANIZATION,
			ServicePlan: globalcommonv1.ServicePlan_BYOC,
		},
	}, nil)

	resp, err := server.ScaleByocSharedComponents(ctx, &openapingv1beta2.ScaleByocSharedComponentsRequest{
		ResourcePoolId: "21",
		Tikv:           &openapingv1beta2.ByocSharedComponentScaleTarget{},
		ValidateOnly:   true,
	})
	g.Expect(resp).To(BeNil())
	g.Expect(err).ToNot(BeNil())
	g.Expect(err.Error()).To(ContainSubstring("tikv target must specify at least one field"))
}

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

	mockNextgen := mock_nextgenglobal.NewMockNextGenGlobal(ctrl)
	mockAccount := mock_account.NewMockClient(ctrl)
	server := NewServer(mockNextgen, mockAccount, emptyClusterCustomStatusLister{})

	ctx := metadata.AppendToIncomingMeta(t.Context(), &metadata.RequestMeta{
		OrgID:             1001,
		UserID:            "user-1",
		DefaultProjectID:  2001,
		ValidProjectList:  []uint64{2001},
		IsCheckPermission: boolPtr(true),
	})

	mockNextgen.EXPECT().GetResourcePool(gomock.Any(), &globalngv1.GetResourcePoolRequest{
		ResourcePoolId: 21,
	}).Return(&globalngv1.ResourcePoolProfile{
		ResourcePool: &globalngv1.ResourcePool{
			Id:          21,
			OwnerId:     1001,
			OwnerType:   globalngv1.ResourcePool_ORGANIZATION,
			ServicePlan: globalcommonv1.ServicePlan_BYOC,
		},
	}, nil)

	resp, err := server.ScaleByocSharedComponents(ctx, &openapingv1beta2.ScaleByocSharedComponentsRequest{
		ResourcePoolId: "21",
		Tikv:           &openapingv1beta2.ByocSharedComponentScaleTarget{},
		TiflashWrite: &openapingv1beta2.ByocSharedComponentScaleTarget{
			NodeCount: proto.Int64(2),
		},
		ValidateOnly: true,
	})
	g.Expect(resp).To(BeNil())
	g.Expect(err).ToNot(BeNil())
	g.Expect(err.Error()).To(ContainSubstring("tikv target must specify at least one field"))
}

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

	mockNextgen := mock_nextgenglobal.NewMockNextGenGlobal(ctrl)
	mockAccount := mock_account.NewMockClient(ctrl)
	server := NewServer(mockNextgen, mockAccount, emptyClusterCustomStatusLister{})

	ctx := metadata.AppendToIncomingMeta(t.Context(), &metadata.RequestMeta{
		OrgID:             1001,
		UserID:            "user-1",
		DefaultProjectID:  2001,
		ValidProjectList:  []uint64{2001},
		IsCheckPermission: boolPtr(true),
	})

	mockNextgen.EXPECT().GetResourcePool(gomock.Any(), &globalngv1.GetResourcePoolRequest{
		ResourcePoolId: 21,
	}).Return(&globalngv1.ResourcePoolProfile{
		ResourcePool: &globalngv1.ResourcePool{
			Id:          21,
			OwnerId:     1001,
			OwnerType:   globalngv1.ResourcePool_ORGANIZATION,
			ServicePlan: globalcommonv1.ServicePlan_BYOC,
		},
	}, nil)

	resp, err := server.ScaleByocSharedComponents(ctx, &openapingv1beta2.ScaleByocSharedComponentsRequest{
		ResourcePoolId: "21",
		Tikv: &openapingv1beta2.ByocSharedComponentScaleTarget{
			Storages: []*openapingv1beta2.ByocSharedComponentStorage{
				{StorageName: "DATA", SizeGi: proto.Int64(100)},
				{StorageName: "data", SizeGi: proto.Int64(200)},
			},
		},
		ValidateOnly: true,
	})
	g.Expect(resp).To(BeNil())
	g.Expect(err).ToNot(BeNil())
	g.Expect(err.Error()).To(ContainSubstring("duplicate storage_name data"))
}

func boolPtr(v bool) *bool {
	return &v
}
