package cluster

import (
	"context"
	"testing"

	"github.com/stretchr/testify/require"
	clusterngv1beta "github.com/tidbcloud/cluster-service-ng/api/cluster-service-ng/v1beta"
	"go.uber.org/mock/gomock"
	"google.golang.org/grpc/codes"

	globalcommonv1 "github.com/tidbcloud/tidb-management-service/api/spec/global/common/v1"
	globalngv1 "github.com/tidbcloud/tidb-management-service/api/spec/global/nextgen/v1"
	"github.com/tidbcloud/tidb-management-service/nextgen-global-service/domain/adapter/regionalserver/mock_regionalserver"
	"github.com/tidbcloud/tidb-management-service/nextgen-global-service/domain/entity"
	clusterrepo "github.com/tidbcloud/tidb-management-service/nextgen-global-service/domain/repository/cluster"
	"github.com/tidbcloud/tidb-management-service/nextgen-global-service/domain/repository/cluster/mock_cluster"
	"github.com/tidbcloud/tidb-management-service/nextgen-global-service/domain/repository/resourcepool/mock_resourcepool"
	"github.com/tidbcloud/tidb-management-service/utilities/bizerr"
	"github.com/tidbcloud/tidb-management-service/utilities/idgenerator/mock_idgenerator"
)

func TestValidateResourcePoolSelectionRequest(t *testing.T) {
	poolID := uint64(42)
	tests := []struct {
		name    string
		request *globalngv1.CreateClusterRequest
		wantErr string
	}{
		{
			name: "byoc restore",
			request: &globalngv1.CreateClusterRequest{
				ResourcePoolId: &poolID,
				Cluster: &globalngv1.Cluster{
					ServicePlan:     globalcommonv1.ServicePlan_BYOC,
					SourceClusterId: 1,
				},
			},
		},
		{
			name: "byoc without resource pool",
			request: &globalngv1.CreateClusterRequest{
				Cluster: &globalngv1.Cluster{
					ServicePlan: globalcommonv1.ServicePlan_BYOC,
				},
			},
			wantErr: "resource_pool_id is required for BYOC",
		},
		{
			name: "non byoc",
			request: &globalngv1.CreateClusterRequest{
				ResourcePoolId: &poolID,
				Cluster: &globalngv1.Cluster{
					ServicePlan: globalcommonv1.ServicePlan_Premium,
				},
			},
			wantErr: "only supported for BYOC",
		},
		{
			name: "zero",
			request: &globalngv1.CreateClusterRequest{
				ResourcePoolId: new(uint64),
				Cluster: &globalngv1.Cluster{
					ServicePlan: globalcommonv1.ServicePlan_BYOC,
				},
			},
			wantErr: "greater than zero",
		},
	}

	for _, test := range tests {
		t.Run(test.name, func(t *testing.T) {
			err := validateResourcePoolSelectionRequest(test.request)
			if test.wantErr == "" {
				require.Nil(t, err)
				return
			}
			require.ErrorContains(t, err, test.wantErr)
		})
	}
}

func TestValidateSharedResourcePoolForCreate(t *testing.T) {
	tests := []struct {
		name       string
		cluster    *globalngv1.Cluster
		mutatePool func(*entity.ResourcePool)
		wantErr    string
	}{
		{
			name: "organization pool",
			cluster: &globalngv1.Cluster{
				OrganizationId:  200,
				CloudProviderId: globalcommonv1.CloudProvider_AWS,
				RegionId:        globalcommonv1.Region_AWS_US_EAST_1,
				ServicePlan:     globalcommonv1.ServicePlan_Premium,
			},
		},
		{
			name: "shared essential pool",
			cluster: &globalngv1.Cluster{
				OrganizationId:  200,
				CloudProviderId: globalcommonv1.CloudProvider_AWS,
				RegionId:        globalcommonv1.Region_AWS_US_EAST_1,
				ServicePlan:     globalcommonv1.ServicePlan_Essential_V2,
			},
			mutatePool: func(pool *entity.ResourcePool) {
				pool.OwnerType = globalngv1.ResourcePool_SHARED
				pool.OwnerId = 0
				pool.ServicePlan = globalcommonv1.ServicePlan_Essential_V2
			},
		},
		{
			name: "organization mismatch",
			cluster: &globalngv1.Cluster{
				OrganizationId:  200,
				CloudProviderId: globalcommonv1.CloudProvider_AWS,
				RegionId:        globalcommonv1.Region_AWS_US_EAST_1,
				ServicePlan:     globalcommonv1.ServicePlan_Premium,
			},
			mutatePool: func(pool *entity.ResourcePool) {
				pool.OwnerId = 201
			},
			wantErr: "resource pool not found",
		},
		{
			name: "service plan mismatch",
			cluster: &globalngv1.Cluster{
				OrganizationId:  200,
				CloudProviderId: globalcommonv1.CloudProvider_AWS,
				RegionId:        globalcommonv1.Region_AWS_US_EAST_1,
				ServicePlan:     globalcommonv1.ServicePlan_Premium,
			},
			mutatePool: func(pool *entity.ResourcePool) {
				pool.ServicePlan = globalcommonv1.ServicePlan_Essential_V2
			},
			wantErr: "resource pool not found",
		},
		{
			name: "provider or region mismatch",
			cluster: &globalngv1.Cluster{
				OrganizationId:  200,
				CloudProviderId: globalcommonv1.CloudProvider_AWS,
				RegionId:        globalcommonv1.Region_AWS_US_EAST_1,
				ServicePlan:     globalcommonv1.ServicePlan_Premium,
			},
			mutatePool: func(pool *entity.ResourcePool) {
				pool.ProviderId = globalcommonv1.CloudProvider_GCP
			},
			wantErr: "provider and region must match",
		},
	}

	for _, test := range tests {
		t.Run(test.name, func(t *testing.T) {
			pool := selectedResourcePool(42, 200)
			pool.ServicePlan = test.cluster.GetServicePlan()
			if test.mutatePool != nil {
				test.mutatePool(pool)
			}

			err := validateSharedResourcePoolForCreate(&globalngv1.CreateClusterRequest{Cluster: test.cluster}, pool)
			if test.wantErr == "" {
				require.NoError(t, err)
				return
			}
			require.ErrorContains(t, err, test.wantErr)
		})
	}
}

func TestValidateByocClusterRequiresPositiveMaxRcu(t *testing.T) {
	svc := &serviceImpl{}

	err := svc.validateCluster(t.Context(), &globalngv1.Cluster{
		OrganizationId: 200,
		ServicePlan:    globalcommonv1.ServicePlan_BYOC,
		MaxRcu:         0,
	})

	require.ErrorContains(t, err, "greater than zero")
}

func TestCreateValidateOnlyValidatesSelectedPoolAndReturnsAssociation(t *testing.T) {
	poolID := uint64(42)
	ctrl := gomock.NewController(t)
	resourcePoolRepo := mock_resourcepool.NewMockRepo(ctrl)
	clusterRepo := &selectedValidateOnlyClusterRepo{}
	pool := selectedResourcePool(poolID, 200)
	pool.HighAvailabilityType = globalngv1.ResourcePool_ZONAL
	resourcePoolRepo.EXPECT().Load(gomock.Any(), poolID, false).Return(pool, nil)
	svc := &serviceImpl{
		clusterRepo:      clusterRepo,
		regionRepo:       &validateOnlyRegionRepo{},
		rcuQuotaRepo:     &validateOnlyRcuQuotaRepo{},
		resourcePoolRepo: resourcePoolRepo,
		billingCli:       &validateOnlyBillingClient{},
		encryptor:        &validateOnlyEncryptor{},
	}

	created, err := svc.Create(t.Context(), &globalngv1.CreateClusterRequest{
		ValidateOnly:   true,
		ResourcePoolId: &poolID,
		Cluster: &globalngv1.Cluster{
			ClusterId:            100,
			OrganizationId:       200,
			CloudProviderId:      globalcommonv1.CloudProvider_AWS,
			RegionId:             globalcommonv1.Region_AWS_US_EAST_1,
			ServicePlan:          globalcommonv1.ServicePlan_BYOC,
			HighAvailabilityType: globalngv1.Cluster_REGIONAL,
			MaxRcu:               10_000,
		},
	})

	require.Nil(t, err)
	require.NotNil(t, created)
	require.Equal(t, "42", created.GetSharedPoolId())
	require.Equal(t, "orders", created.GetResourcePoolDisplayName())
	require.Equal(t, globalngv1.Cluster_ZONAL, created.GetHighAvailabilityType())
	require.Equal(t, 1, clusterRepo.countCalls)
}

func TestSetHighAvailabilityTypeFromResourcePool(t *testing.T) {
	t.Parallel()

	tests := []struct {
		name     string
		poolType globalngv1.ResourcePool_HighAvailabilityType
		want     globalngv1.Cluster_HighAvailabilityType
		valid    bool
	}{
		{
			name:  "unspecified pool defaults cluster to regional",
			want:  globalngv1.Cluster_REGIONAL,
			valid: true,
		},
		{
			name:     "regional pool overrides cluster",
			poolType: globalngv1.ResourcePool_REGIONAL,
			want:     globalngv1.Cluster_REGIONAL,
			valid:    true,
		},
		{
			name:     "zonal pool overrides cluster",
			poolType: globalngv1.ResourcePool_ZONAL,
			want:     globalngv1.Cluster_ZONAL,
			valid:    true,
		},
		{
			name:     "invalid pool type",
			poolType: globalngv1.ResourcePool_HighAvailabilityType(99),
		},
	}

	for _, test := range tests {
		t.Run(test.name, func(t *testing.T) {
			t.Parallel()
			cluster := &globalngv1.Cluster{HighAvailabilityType: globalngv1.Cluster_ZONAL}
			pool := selectedResourcePool(42, 200)
			pool.HighAvailabilityType = test.poolType

			err := setHighAvailabilityTypeFromResourcePool(cluster, pool)
			if !test.valid {
				require.Error(t, err)
				return
			}
			require.Nil(t, err)
			require.Equal(t, test.want, cluster.GetHighAvailabilityType())
		})
	}
}

func TestCreateValidateOnlyRejectsSelectedPoolWhenVcpusUsageExceedsLimit(t *testing.T) {
	poolID := uint64(42)
	ctrl := gomock.NewController(t)
	resourcePoolRepo := mock_resourcepool.NewMockRepo(ctrl)
	regionalCli := mock_regionalserver.NewMockClient(ctrl)
	clusterRepo := &selectedValidateOnlyClusterRepo{}
	pool := selectedResourcePool(poolID, 200)
	pool.VcpusLimit = 10
	resourcePoolRepo.EXPECT().Load(gomock.Any(), poolID, false).Return(pool, nil)
	regionalCli.EXPECT().GetSharedPoolVcpusUsage(
		gomock.Any(),
		gomock.Any(),
		&clusterngv1beta.GetSharedPoolVcpusUsageRequest{SharedPoolId: "42"},
	).Return(&clusterngv1beta.GetSharedPoolVcpusUsageResponse{VcpusUsed: 11}, nil)
	svc := &serviceImpl{
		clusterRepo:      clusterRepo,
		regionRepo:       &validateOnlyRegionRepo{},
		rcuQuotaRepo:     &validateOnlyRcuQuotaRepo{},
		resourcePoolRepo: resourcePoolRepo,
		regionalCli:      regionalCli,
		billingCli:       &validateOnlyBillingClient{},
		encryptor:        &validateOnlyEncryptor{},
	}

	created, err := svc.Create(t.Context(), &globalngv1.CreateClusterRequest{
		ValidateOnly:   true,
		ResourcePoolId: &poolID,
		Cluster: &globalngv1.Cluster{
			ClusterId:       100,
			OrganizationId:  200,
			CloudProviderId: globalcommonv1.CloudProvider_AWS,
			RegionId:        globalcommonv1.Region_AWS_US_EAST_1,
			ServicePlan:     globalcommonv1.ServicePlan_BYOC,
			MaxRcu:          10_000,
		},
	})

	require.Nil(t, created)
	require.Equal(t, bizerr.ErrCode(codes.ResourceExhausted), err.ErrCode())
	require.ErrorContains(t, err, "vCPU usage (11) reaches or exceeds its limit (10)")
}

func TestLoadSelectedResourcePoolMasksInaccessiblePools(t *testing.T) {
	poolID := uint64(42)
	request := selectedResourcePoolRequest(poolID)

	tests := []struct {
		name string
		pool *entity.ResourcePool
	}{
		{
			name: "other organization",
			pool: selectedResourcePool(poolID, 999),
		},
		{
			name: "shared pool",
			pool: func() *entity.ResourcePool {
				pool := selectedResourcePool(poolID, 200)
				pool.OwnerType = globalngv1.ResourcePool_SHARED
				return pool
			}(),
		},
		{
			name: "other plan",
			pool: func() *entity.ResourcePool {
				pool := selectedResourcePool(poolID, 200)
				pool.ServicePlan = globalcommonv1.ServicePlan_Premium
				return pool
			}(),
		},
	}

	for _, test := range tests {
		t.Run(test.name, func(t *testing.T) {
			ctrl := gomock.NewController(t)
			resourcePoolRepo := mock_resourcepool.NewMockRepo(ctrl)
			resourcePoolRepo.EXPECT().Load(gomock.Any(), poolID, false).Return(test.pool, nil)
			svc := &serviceImpl{resourcePoolRepo: resourcePoolRepo}

			pool, err := svc.loadSelectedResourcePool(t.Context(), request, false)

			require.Nil(t, pool)
			require.NotNil(t, err)
			require.Equal(t, bizerr.ErrCodeRecordNotFound, err.ErrCode())
		})
	}
}

func TestLoadSelectedResourcePoolValidatesPlacementAndUsesRowLock(t *testing.T) {
	poolID := uint64(42)
	request := selectedResourcePoolRequest(poolID)
	ctrl := gomock.NewController(t)
	resourcePoolRepo := mock_resourcepool.NewMockRepo(ctrl)
	expected := selectedResourcePool(poolID, 200)
	resourcePoolRepo.EXPECT().LoadForUpdate(gomock.Any(), poolID, false).Return(expected, nil)
	svc := &serviceImpl{resourcePoolRepo: resourcePoolRepo}

	pool, err := svc.loadSelectedResourcePool(t.Context(), request, true)

	require.Nil(t, err)
	require.Same(t, expected, pool)
}

func TestLoadSelectedResourcePoolAcceptsUsableLifecycleStates(t *testing.T) {
	poolID := uint64(42)
	request := selectedResourcePoolRequest(poolID)
	for _, lifecycle := range []globalngv1.ResourcePool_LifecycleState{
		globalngv1.ResourcePool_LIFECYCLE_STATE_UNSPECIFIED,
		globalngv1.ResourcePool_CREATING,
		globalngv1.ResourcePool_ACTIVE,
		globalngv1.ResourcePool_MODIFYING,
		globalngv1.ResourcePool_PAUSED,
		globalngv1.ResourcePool_RESUMING,
	} {
		t.Run(lifecycle.String(), func(t *testing.T) {
			ctrl := gomock.NewController(t)
			resourcePoolRepo := mock_resourcepool.NewMockRepo(ctrl)
			pool := selectedResourcePool(poolID, 200)
			pool.LifecycleState = lifecycle
			resourcePoolRepo.EXPECT().Load(gomock.Any(), poolID, false).Return(pool, nil)
			svc := &serviceImpl{resourcePoolRepo: resourcePoolRepo}

			got, err := svc.loadSelectedResourcePool(t.Context(), request, false)

			require.Nil(t, err)
			require.Same(t, pool, got)
		})
	}
}

func TestEnsureResourcePoolReadyForCreateImplicitlyResumesPausedPool(t *testing.T) {
	ctrl := gomock.NewController(t)
	idGenerator := mock_idgenerator.NewMockIDGenerator(ctrl)
	resourcePoolRepo := mock_resourcepool.NewMockRepo(ctrl)
	poolSvc := &fakeCreateResourcePoolService{}
	svc := &serviceImpl{
		idGeneratorAdapter: idGenerator,
		resourcePoolRepo:   resourcePoolRepo,
		resourcePoolSvc:    poolSvc,
	}
	idGenerator.EXPECT().MustNew().Return(uint64(123))

	err := svc.ensureResourcePoolReadyForCreate(t.Context(), &entity.ResourcePool{
		ResourcePool: &globalngv1.ResourcePool{Id: 42, LifecycleState: globalngv1.ResourcePool_PAUSED},
	})

	require.Nil(t, err)
	require.Equal(t, 1, poolSvc.resumeCalls)
	require.Equal(t, uint64(42), poolSvc.resumeRequest.GetResourcePoolId())
	require.Equal(t, "123", poolSvc.resumeRequest.GetOperationId())
}

func TestEnsureResourcePoolReadyForCreateRejectsPausingPool(t *testing.T) {
	err := (&serviceImpl{}).ensureResourcePoolReadyForCreate(t.Context(), &entity.ResourcePool{
		ResourcePool: &globalngv1.ResourcePool{LifecycleState: globalngv1.ResourcePool_PAUSING},
	})

	require.ErrorContains(t, err, "pausing")
}

func TestValidateResourcePoolReadyForCreateRejectsPausedPool(t *testing.T) {
	err := validateResourcePoolReadyForCreate(&entity.ResourcePool{
		ResourcePool: &globalngv1.ResourcePool{LifecycleState: globalngv1.ResourcePool_PAUSED},
	})

	require.ErrorContains(t, err, "not available")
}

func TestResourcePoolCreateValidationRejectsNilPool(t *testing.T) {
	require.ErrorContains(t, validateResourcePoolLifecycleForCreate(nil), "resource pool is required")
	require.ErrorContains(t, validateResourcePoolReadyForCreate(nil), "resource pool is required")
	require.ErrorContains(
		t,
		(&serviceImpl{}).ensureResourcePoolReadyForCreate(t.Context(), nil),
		"resource pool is required",
	)
}

func TestLoadSelectedResourcePoolRejectsMismatchedOrUnavailablePool(t *testing.T) {
	poolID := uint64(42)
	request := selectedResourcePoolRequest(poolID)
	tests := []struct {
		name    string
		mutate  func(*entity.ResourcePool)
		wantErr string
	}{
		{
			name: "provider mismatch",
			mutate: func(pool *entity.ResourcePool) {
				pool.ProviderId = globalcommonv1.CloudProvider_GCP
			},
			wantErr: "provider and region must match",
		},
		{
			name: "region mismatch",
			mutate: func(pool *entity.ResourcePool) {
				pool.ResourcePool.RegionId = globalcommonv1.Region_AWS_US_WEST_2
			},
			wantErr: "provider and region must match",
		},
		{
			name: "failed",
			mutate: func(pool *entity.ResourcePool) {
				pool.LifecycleState = globalngv1.ResourcePool_FAILED
			},
			wantErr: "not available",
		},
	}

	for _, test := range tests {
		t.Run(test.name, func(t *testing.T) {
			ctrl := gomock.NewController(t)
			resourcePoolRepo := mock_resourcepool.NewMockRepo(ctrl)
			pool := selectedResourcePool(poolID, 200)
			test.mutate(pool)
			resourcePoolRepo.EXPECT().Load(gomock.Any(), poolID, false).Return(pool, nil)
			svc := &serviceImpl{resourcePoolRepo: resourcePoolRepo}

			got, err := svc.loadSelectedResourcePool(t.Context(), request, false)

			require.Nil(t, got)
			require.ErrorContains(t, err, test.wantErr)
		})
	}
}

func TestValidateSelectedResourcePoolInstanceLimit(t *testing.T) {
	pool := selectedResourcePool(42, 200)
	pool.InstanceLimit = 2
	ctrl := gomock.NewController(t)
	clusterRepo := mock_cluster.NewMockRepo(ctrl)
	svc := &serviceImpl{clusterRepo: clusterRepo}

	clusterRepo.EXPECT().
		CountLiveBySharedPoolIDs(gomock.Any(), []uint64{42}).
		Return(map[uint64]int64{42: 1}, nil)
	require.Nil(t, svc.validateSelectedResourcePoolInstanceLimit(t.Context(), pool))

	clusterRepo.EXPECT().
		CountLiveBySharedPoolIDs(gomock.Any(), []uint64{42}).
		Return(map[uint64]int64{42: 2}, nil)
	err := svc.validateSelectedResourcePoolInstanceLimit(t.Context(), pool)
	require.ErrorContains(t, err, "service limit")
}

func TestValidateSelectedResourcePoolVcpusLimit(t *testing.T) {
	region := &entity.Region{}
	tests := []struct {
		name       string
		limit      uint64
		used       uint64
		lifecycle  globalngv1.ResourcePool_LifecycleState
		nonBYOC    bool
		nilUsage   bool
		wantErr    bool
		wantErrMsg string
	}{
		{name: "under limit", limit: 10, used: 9},
		{name: "at limit", limit: 10, used: 10, wantErr: true, wantErrMsg: "vCPU usage (10) reaches or exceeds its limit (10)"},
		{name: "over limit", limit: 10, used: 11, wantErr: true, wantErrMsg: "vCPU usage (11) reaches or exceeds its limit (10)"},
		{name: "zero limit is unlimited", limit: 0, used: 11},
		{name: "creating pool defers until runtime is available", limit: 10, used: 11, lifecycle: globalngv1.ResourcePool_CREATING},
		{
			name:       "paused pool checks runtime capacity",
			limit:      10,
			used:       11,
			lifecycle:  globalngv1.ResourcePool_PAUSED,
			wantErr:    true,
			wantErrMsg: "vCPU usage (11) reaches or exceeds its limit (10)",
		},
		{
			name:       "resuming pool checks runtime capacity",
			limit:      10,
			used:       11,
			lifecycle:  globalngv1.ResourcePool_RESUMING,
			wantErr:    true,
			wantErrMsg: "vCPU usage (11) reaches or exceeds its limit (10)",
		},
		{name: "non-BYOC pool skips the check", limit: 10, used: 11, nonBYOC: true},
		{
			name:       "nil usage response fails closed",
			limit:      10,
			nilUsage:   true,
			wantErr:    true,
			wantErrMsg: "received nil response for shared pool vCPU usage query",
		},
	}

	for _, test := range tests {
		t.Run(test.name, func(t *testing.T) {
			ctrl := gomock.NewController(t)
			regionalCli := mock_regionalserver.NewMockClient(ctrl)
			pool := selectedResourcePool(42, 200)
			pool.VcpusLimit = test.limit
			if test.nonBYOC {
				pool.ServicePlan = globalcommonv1.ServicePlan_Premium
			}
			if test.lifecycle != globalngv1.ResourcePool_LIFECYCLE_STATE_UNSPECIFIED {
				pool.LifecycleState = test.lifecycle
			}
			if test.limit > 0 && !test.nonBYOC && pool.GetLifecycleState() != globalngv1.ResourcePool_CREATING {
				var usageResponse *clusterngv1beta.GetSharedPoolVcpusUsageResponse
				if !test.nilUsage {
					usageResponse = &clusterngv1beta.GetSharedPoolVcpusUsageResponse{VcpusUsed: test.used}
				}
				regionalCli.EXPECT().GetSharedPoolVcpusUsage(
					gomock.Any(),
					region,
					&clusterngv1beta.GetSharedPoolVcpusUsageRequest{SharedPoolId: "42"},
				).Return(usageResponse, nil)
			}

			svc := &serviceImpl{regionalCli: regionalCli}
			err := svc.validateSelectedResourcePoolVcpusLimit(
				t.Context(),
				&globalngv1.CreateClusterRequest{
					ValidateOnly: true,
					Cluster:      &globalngv1.Cluster{},
				},
				region,
				pool,
			)
			if !test.wantErr {
				require.Nil(t, err)
				return
			}
			if test.nilUsage {
				require.Equal(t, bizerr.ErrCodeUnknown, err.ErrCode())
				require.ErrorContains(t, err, test.wantErrMsg)
				return
			}
			require.Equal(t, bizerr.ErrCode(codes.ResourceExhausted), err.ErrCode())
			require.ErrorContains(t, err, test.wantErrMsg)
		})
	}
}

func TestShouldValidateSelectedResourcePoolVcpusLimit(t *testing.T) {
	tests := []struct {
		name             string
		validateOnly     bool
		mutatePool       func(*entity.ResourcePool)
		existingCluster  bool
		expectClusterGet bool
		wantValidate     bool
	}{
		{
			name:             "existing BYOC cluster",
			existingCluster:  true,
			expectClusterGet: true,
		},
		{
			name:             "new BYOC cluster",
			expectClusterGet: true,
			wantValidate:     true,
		},
		{
			name:         "validate only",
			validateOnly: true,
			wantValidate: true,
		},
		{
			name: "non-BYOC pool",
			mutatePool: func(pool *entity.ResourcePool) {
				pool.ServicePlan = globalcommonv1.ServicePlan_Premium
			},
		},
		{
			name: "unlimited pool",
			mutatePool: func(pool *entity.ResourcePool) {
				pool.VcpusLimit = 0
			},
		},
		{
			name: "creating pool",
			mutatePool: func(pool *entity.ResourcePool) {
				pool.LifecycleState = globalngv1.ResourcePool_CREATING
			},
		},
	}

	for _, test := range tests {
		t.Run(test.name, func(t *testing.T) {
			ctrl := gomock.NewController(t)
			clusterRepo := mock_cluster.NewMockRepo(ctrl)
			pool := selectedResourcePool(42, 200)
			if test.mutatePool != nil {
				test.mutatePool(pool)
			}
			if test.expectClusterGet || test.validateOnly {
				pool.VcpusLimit = 10
			}
			if test.expectClusterGet {
				if test.existingCluster {
					clusterRepo.EXPECT().Get(gomock.Any(), uint64(100), false).Return(&entity.Cluster{}, nil)
				} else {
					clusterRepo.EXPECT().Get(gomock.Any(), uint64(100), false).Return(
						nil,
						bizerr.New(bizerr.ErrCodeRecordNotFound, "cluster not found"),
					)
				}
			}

			svc := &serviceImpl{clusterRepo: clusterRepo}
			shouldValidate, err := svc.shouldValidateSelectedResourcePoolVcpusLimit(
				t.Context(),
				&globalngv1.CreateClusterRequest{
					ValidateOnly: test.validateOnly,
					Cluster:      &globalngv1.Cluster{ClusterId: 100},
				},
				pool,
			)

			require.Nil(t, err)
			require.Equal(t, test.wantValidate, shouldValidate)
		})
	}
}

func selectedResourcePoolRequest(poolID uint64) *globalngv1.CreateClusterRequest {
	return &globalngv1.CreateClusterRequest{
		ResourcePoolId: &poolID,
		Cluster: &globalngv1.Cluster{
			OrganizationId:  200,
			CloudProviderId: globalcommonv1.CloudProvider_AWS,
			RegionId:        globalcommonv1.Region_AWS_US_EAST_1,
			ServicePlan:     globalcommonv1.ServicePlan_BYOC,
		},
	}
}

func selectedResourcePool(poolID, organizationID uint64) *entity.ResourcePool {
	return &entity.ResourcePool{
		ResourcePool: &globalngv1.ResourcePool{
			Id:             poolID,
			OwnerType:      globalngv1.ResourcePool_ORGANIZATION,
			OwnerId:        organizationID,
			ProviderId:     globalcommonv1.CloudProvider_AWS,
			RegionId:       globalcommonv1.Region_AWS_US_EAST_1,
			ServicePlan:    globalcommonv1.ServicePlan_BYOC,
			LifecycleState: globalngv1.ResourcePool_ACTIVE,
			InstanceLimit:  10,
			Name:           "orders",
		},
	}
}

type selectedValidateOnlyClusterRepo struct {
	clusterrepo.Repo

	countCalls int
}

func (r *selectedValidateOnlyClusterRepo) Query(
	context.Context,
	*clusterrepo.Filter,
) ([]*entity.Cluster, bizerr.BizError) {
	return nil, nil
}

func (r *selectedValidateOnlyClusterRepo) CountLiveBySharedPoolIDs(
	context.Context,
	[]uint64,
) (map[uint64]int64, bizerr.BizError) {
	r.countCalls++
	return map[uint64]int64{42: 0}, nil
}
