package resourcepool

import (
	"context"
	"testing"

	"github.com/stretchr/testify/require"
	"go.uber.org/mock/gomock"

	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/entity"
	mockcluster "github.com/tidbcloud/tidb-management-service/nextgen-global-service/domain/repository/cluster/mock_cluster"
	mockresourcepool "github.com/tidbcloud/tidb-management-service/nextgen-global-service/domain/repository/resourcepool/mock_resourcepool"
	"github.com/tidbcloud/tidb-management-service/utilities/bizerr"
)

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

	repo := mockresourcepool.NewMockRepo(ctrl)
	strategyRepo := mockresourcepool.NewMockStrategyRepo(ctrl)
	clusterRepo := mockcluster.NewMockRepo(ctrl)

	svc := &serviceImpl{
		resourcePoolRepo:         repo,
		resourcePoolStrategyRepo: strategyRepo,
		clusterRepo:              clusterRepo,
	}

	defaultPool := &entity.ResourcePool{ResourcePool: &globalngv1.ResourcePool{
		Id:            1001,
		ProviderId:    globalcommonv1.CloudProvider_AWS,
		RegionId:      globalcommonv1.Region_AWS_US_WEST_2,
		OwnerType:     globalngv1.ResourcePool_ORGANIZATION,
		OwnerId:       42,
		ServicePlan:   globalcommonv1.ServicePlan_Premium,
		InstanceLimit: 2,
	}}

	strategyRepo.EXPECT().GetByOrgProviderRegion(
		gomock.Any(),
		uint64(42),
		globalcommonv1.CloudProvider_AWS,
		globalcommonv1.Region_AWS_US_WEST_2,
		globalcommonv1.ServicePlan_Premium,
	).Return(&entity.ResourcePoolStrategy{ResourcePoolStrategy: &globalngv1.ResourcePoolStrategy{
		Id:                    701,
		OrganizationId:        42,
		DefaultResourcePoolId: 1001,
	}}, nil)
	repo.EXPECT().Load(gomock.Any(), uint64(1001), false).Return(defaultPool, nil)
	clusterRepo.EXPECT().CountBySharedPoolID(gomock.Any(), uint64(1001)).Return(int64(1), nil)

	called := false
	creator := func(
		ctx context.Context,
		pool entity.ResourcePool,
		poolName string,
		requiredRcu uint32,
		fixedTopology *globalngv1.FixedTopology,
	) (*entity.ResourcePool, bizerr.BizError) {
		called = true
		return &entity.ResourcePool{ResourcePool: &globalngv1.ResourcePool{Id: 9999}}, nil
	}

	got, err := svc.getOrCreateDefaultResourcePoolWithCreator(t.Context(), entity.ResourcePool{
		ResourcePool: &globalngv1.ResourcePool{
			ProviderId:  globalcommonv1.CloudProvider_AWS,
			RegionId:    globalcommonv1.Region_AWS_US_WEST_2,
			OwnerType:   globalngv1.ResourcePool_ORGANIZATION,
			OwnerId:     42,
			ServicePlan: globalcommonv1.ServicePlan_Premium,
		},
	}, 60000, nil, creator)
	require.NoError(t, err)
	require.False(t, called)
	require.Equal(t, uint64(1001), got.GetId())
}

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

	repo := mockresourcepool.NewMockRepo(ctrl)
	strategyRepo := mockresourcepool.NewMockStrategyRepo(ctrl)
	clusterRepo := mockcluster.NewMockRepo(ctrl)
	svc := &serviceImpl{
		resourcePoolRepo:         repo,
		resourcePoolStrategyRepo: strategyRepo,
		clusterRepo:              clusterRepo,
	}

	strategyRepo.EXPECT().GetByOrgProviderRegion(
		gomock.Any(), uint64(42), globalcommonv1.CloudProvider_AWS,
		globalcommonv1.Region_AWS_US_WEST_2, globalcommonv1.ServicePlan_Premium,
	).Return(&entity.ResourcePoolStrategy{ResourcePoolStrategy: &globalngv1.ResourcePoolStrategy{
		DefaultResourcePoolId: 1001,
	}}, nil)
	repo.EXPECT().Load(gomock.Any(), uint64(1001), false).Return(&entity.ResourcePool{
		ResourcePool: &globalngv1.ResourcePool{
			Id:            1001,
			ServicePlan:   globalcommonv1.ServicePlan_Premium,
			InstanceLimit: 1,
		},
	}, nil)
	clusterRepo.EXPECT().CountBySharedPoolID(gomock.Any(), uint64(1001)).Return(int64(1), nil)

	got, err := svc.FindDefaultResourcePool(t.Context(), entity.ResourcePool{ResourcePool: &globalngv1.ResourcePool{
		ProviderId:  globalcommonv1.CloudProvider_AWS,
		RegionId:    globalcommonv1.Region_AWS_US_WEST_2,
		OwnerType:   globalngv1.ResourcePool_ORGANIZATION,
		OwnerId:     42,
		ServicePlan: globalcommonv1.ServicePlan_Premium,
	}})

	require.NoError(t, err)
	require.Nil(t, got)
}

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

	repo := mockresourcepool.NewMockRepo(ctrl)
	strategyRepo := mockresourcepool.NewMockStrategyRepo(ctrl)
	clusterRepo := mockcluster.NewMockRepo(ctrl)
	svc := &serviceImpl{
		resourcePoolRepo:         repo,
		resourcePoolStrategyRepo: strategyRepo,
		clusterRepo:              clusterRepo,
	}

	strategyRepo.EXPECT().GetByOrgProviderRegion(
		gomock.Any(), uint64(42), globalcommonv1.CloudProvider_AWS,
		globalcommonv1.Region_AWS_US_WEST_2, globalcommonv1.ServicePlan_Premium,
	).Return(&entity.ResourcePoolStrategy{ResourcePoolStrategy: &globalngv1.ResourcePoolStrategy{}}, nil)
	first := &entity.ResourcePool{ResourcePool: &globalngv1.ResourcePool{
		Id:            1001,
		ServicePlan:   globalcommonv1.ServicePlan_Premium,
		InstanceLimit: 1,
	}}
	second := &entity.ResourcePool{ResourcePool: &globalngv1.ResourcePool{
		Id:            1002,
		ServicePlan:   globalcommonv1.ServicePlan_Premium,
		InstanceLimit: 2,
	}}
	repo.EXPECT().Query(gomock.Any(), gomock.Any()).Return([]*entity.ResourcePool{first, second}, int64(2), nil)
	clusterRepo.EXPECT().CountBySharedPoolID(gomock.Any(), uint64(1001)).Return(int64(1), nil)
	clusterRepo.EXPECT().CountBySharedPoolID(gomock.Any(), uint64(1002)).Return(int64(1), nil)

	got, err := svc.FindDefaultResourcePool(t.Context(), entity.ResourcePool{ResourcePool: &globalngv1.ResourcePool{
		ProviderId:  globalcommonv1.CloudProvider_AWS,
		RegionId:    globalcommonv1.Region_AWS_US_WEST_2,
		OwnerType:   globalngv1.ResourcePool_ORGANIZATION,
		OwnerId:     42,
		ServicePlan: globalcommonv1.ServicePlan_Premium,
	}})

	require.NoError(t, err)
	require.Same(t, second, got)
}

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

	repo := mockresourcepool.NewMockRepo(ctrl)
	strategyRepo := mockresourcepool.NewMockStrategyRepo(ctrl)
	clusterRepo := mockcluster.NewMockRepo(ctrl)

	svc := &serviceImpl{
		resourcePoolRepo:         repo,
		resourcePoolStrategyRepo: strategyRepo,
		clusterRepo:              clusterRepo,
	}

	defaultPool := &entity.ResourcePool{ResourcePool: &globalngv1.ResourcePool{
		Id:                    1001,
		Name:                  "default-42-aws-us-west-2",
		ProviderId:            globalcommonv1.CloudProvider_AWS,
		RegionId:              globalcommonv1.Region_AWS_US_WEST_2,
		OwnerType:             globalngv1.ResourcePool_ORGANIZATION,
		OwnerId:               42,
		Cidr:                  "10.0.0.0/16",
		K8SServiceCidr:        "172.20.0.0/16",
		ServicePlan:           globalcommonv1.ServicePlan_BYOC,
		InstanceLimit:         1,
		VcpusLimit:            88,
		PlacementGroupEnabled: true,
		PcaArn:                "arn:aws:acm-pca:us-west-2:123456789012:certificate-authority/ca-id",
		HighAvailabilityType:  globalngv1.ResourcePool_REGIONAL,
		ZoneConfig:            &globalngv1.ResourcePool_ZoneConfig{NodeZones: []string{"usw2-az1", "usw2-az2", "usw2-az3"}},
	}}

	strategy := &entity.ResourcePoolStrategy{ResourcePoolStrategy: &globalngv1.ResourcePoolStrategy{
		Id:                    701,
		OrganizationId:        42,
		DefaultResourcePoolId: 1001,
	}}

	strategyRepo.EXPECT().GetByOrgProviderRegion(
		gomock.Any(),
		uint64(42),
		globalcommonv1.CloudProvider_AWS,
		globalcommonv1.Region_AWS_US_WEST_2,
		globalcommonv1.ServicePlan_BYOC,
	).Return(strategy, nil)
	repo.EXPECT().Load(gomock.Any(), uint64(1001), false).Return(defaultPool, nil)
	clusterRepo.EXPECT().CountBySharedPoolID(gomock.Any(), uint64(1001)).Return(int64(1), nil)
	strategyRepo.EXPECT().SwitchDefaultResourcePool(gomock.Any(), uint64(701), uint64(1001), uint64(7002)).Return(true, nil)

	creatorCalled := false
	creator := func(
		ctx context.Context,
		pool entity.ResourcePool,
		poolName string,
		requiredRcu uint32,
		fixedTopology *globalngv1.FixedTopology,
	) (*entity.ResourcePool, bizerr.BizError) {
		creatorCalled = true
		require.Equal(t, uint32(50000), requiredRcu)
		require.NotEmpty(t, poolName)
		require.Regexp(t, `default-42-aws-us-west-2-\d+`, poolName)
		require.Equal(t, defaultPool.GetProviderId(), pool.GetProviderId())
		require.Equal(t, defaultPool.GetRegionId(), pool.GetRegionId())
		require.Equal(t, defaultPool.GetOwnerType(), pool.GetOwnerType())
		require.Equal(t, defaultPool.GetOwnerId(), pool.GetOwnerId())
		require.Equal(t, defaultPool.GetCidr(), pool.GetCidr())
		require.Equal(t, defaultPool.GetK8SServiceCidr(), pool.GetK8SServiceCidr())
		require.Equal(t, defaultPool.GetServicePlan(), pool.GetServicePlan())
		require.Equal(t, defaultPool.GetInstanceLimit(), pool.GetInstanceLimit())
		require.Equal(t, defaultPool.GetVcpusLimit(), pool.GetVcpusLimit())
		require.Equal(t, defaultPool.GetPlacementGroupEnabled(), pool.GetPlacementGroupEnabled())
		require.Equal(t, defaultPool.GetPcaArn(), pool.GetPcaArn())
		require.Equal(t, defaultPool.GetHighAvailabilityType(), pool.GetHighAvailabilityType())
		require.Equal(t, defaultPool.GetZoneConfig().GetNodeZones(), pool.GetZoneConfig().GetNodeZones())
		return &entity.ResourcePool{ResourcePool: &globalngv1.ResourcePool{Id: 7002}}, nil
	}

	got, err := svc.getOrCreateDefaultResourcePoolWithCreator(t.Context(), entity.ResourcePool{
		ResourcePool: &globalngv1.ResourcePool{
			ProviderId:  globalcommonv1.CloudProvider_AWS,
			RegionId:    globalcommonv1.Region_AWS_US_WEST_2,
			OwnerType:   globalngv1.ResourcePool_ORGANIZATION,
			OwnerId:     42,
			ServicePlan: globalcommonv1.ServicePlan_BYOC,
		},
	}, 50000, nil, creator)
	require.NoError(t, err)
	require.True(t, creatorCalled)
	require.Equal(t, uint64(7002), got.GetId())
}

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

	repo := mockresourcepool.NewMockRepo(ctrl)
	strategyRepo := mockresourcepool.NewMockStrategyRepo(ctrl)
	clusterRepo := mockcluster.NewMockRepo(ctrl)

	svc := &serviceImpl{
		resourcePoolRepo:         repo,
		resourcePoolStrategyRepo: strategyRepo,
		clusterRepo:              clusterRepo,
	}

	candidate := &entity.ResourcePool{ResourcePool: &globalngv1.ResourcePool{
		Id:            2111,
		Name:          "default-42-aws-us-west-2",
		ProviderId:    globalcommonv1.CloudProvider_AWS,
		RegionId:      globalcommonv1.Region_AWS_US_WEST_2,
		OwnerType:     globalngv1.ResourcePool_ORGANIZATION,
		OwnerId:       42,
		ServicePlan:   globalcommonv1.ServicePlan_Premium,
		InstanceLimit: 1,
	}}

	strategyRepo.EXPECT().GetByOrgProviderRegion(gomock.Any(), uint64(42),
		globalcommonv1.CloudProvider_AWS, globalcommonv1.Region_AWS_US_WEST_2,
		globalcommonv1.ServicePlan_Premium,
	).Return(&entity.ResourcePoolStrategy{ResourcePoolStrategy: &globalngv1.ResourcePoolStrategy{}}, nil)
	repo.EXPECT().Query(gomock.Any(), gomock.Any()).Return([]*entity.ResourcePool{candidate}, int64(1), nil)
	clusterRepo.EXPECT().CountBySharedPoolID(gomock.Any(), uint64(2111)).Return(int64(1), nil)

	creator := func(
		ctx context.Context,
		pool entity.ResourcePool,
		poolName string,
		requiredRcu uint32,
		fixedTopology *globalngv1.FixedTopology,
	) (*entity.ResourcePool, bizerr.BizError) {
		require.Equal(t, uint32(70000), requiredRcu)
		require.NotEmpty(t, poolName)
		return &entity.ResourcePool{ResourcePool: &globalngv1.ResourcePool{Id: 2112}}, nil
	}

	got, err := svc.getOrCreateDefaultResourcePoolWithCreator(t.Context(), entity.ResourcePool{
		ResourcePool: &globalngv1.ResourcePool{
			ProviderId:  globalcommonv1.CloudProvider_AWS,
			RegionId:    globalcommonv1.Region_AWS_US_WEST_2,
			OwnerType:   globalngv1.ResourcePool_ORGANIZATION,
			OwnerId:     42,
			ServicePlan: globalcommonv1.ServicePlan_Premium,
		},
	}, 70000, nil, creator)
	require.NoError(t, err)
	require.Equal(t, uint64(2112), got.GetId())
}

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

	repo := mockresourcepool.NewMockRepo(ctrl)
	strategyRepo := mockresourcepool.NewMockStrategyRepo(ctrl)
	clusterRepo := mockcluster.NewMockRepo(ctrl)

	svc := &serviceImpl{
		resourcePoolRepo:         repo,
		resourcePoolStrategyRepo: strategyRepo,
		clusterRepo:              clusterRepo,
	}

	fullCandidate := &entity.ResourcePool{ResourcePool: &globalngv1.ResourcePool{
		Id:            2111,
		Name:          "default-42-aws-us-west-2",
		ProviderId:    globalcommonv1.CloudProvider_AWS,
		RegionId:      globalcommonv1.Region_AWS_US_WEST_2,
		OwnerType:     globalngv1.ResourcePool_ORGANIZATION,
		OwnerId:       42,
		ServicePlan:   globalcommonv1.ServicePlan_Premium,
		InstanceLimit: 1,
	}}
	availableCandidate := &entity.ResourcePool{ResourcePool: &globalngv1.ResourcePool{
		Id:            2112,
		Name:          "default-42-aws-us-west-2-2",
		ProviderId:    globalcommonv1.CloudProvider_AWS,
		RegionId:      globalcommonv1.Region_AWS_US_WEST_2,
		OwnerType:     globalngv1.ResourcePool_ORGANIZATION,
		OwnerId:       42,
		ServicePlan:   globalcommonv1.ServicePlan_Premium,
		InstanceLimit: 2,
	}}

	strategyRepo.EXPECT().GetByOrgProviderRegion(gomock.Any(), uint64(42),
		globalcommonv1.CloudProvider_AWS, globalcommonv1.Region_AWS_US_WEST_2,
		globalcommonv1.ServicePlan_Premium,
	).Return(&entity.ResourcePoolStrategy{ResourcePoolStrategy: &globalngv1.ResourcePoolStrategy{}}, nil)
	repo.EXPECT().Query(gomock.Any(), gomock.Any()).Return([]*entity.ResourcePool{fullCandidate, availableCandidate}, int64(2), nil)
	clusterRepo.EXPECT().CountBySharedPoolID(gomock.Any(), uint64(2111)).Return(int64(1), nil)
	clusterRepo.EXPECT().CountBySharedPoolID(gomock.Any(), uint64(2112)).Return(int64(1), nil)

	creatorCalled := false
	creator := func(
		ctx context.Context,
		pool entity.ResourcePool,
		poolName string,
		requiredRcu uint32,
		fixedTopology *globalngv1.FixedTopology,
	) (*entity.ResourcePool, bizerr.BizError) {
		creatorCalled = true
		return &entity.ResourcePool{ResourcePool: &globalngv1.ResourcePool{Id: 9999}}, nil
	}

	got, err := svc.getOrCreateDefaultResourcePoolWithCreator(t.Context(), entity.ResourcePool{
		ResourcePool: &globalngv1.ResourcePool{
			ProviderId:  globalcommonv1.CloudProvider_AWS,
			RegionId:    globalcommonv1.Region_AWS_US_WEST_2,
			OwnerType:   globalngv1.ResourcePool_ORGANIZATION,
			OwnerId:     42,
			ServicePlan: globalcommonv1.ServicePlan_Premium,
		},
	}, 70000, nil, creator)
	require.NoError(t, err)
	require.False(t, creatorCalled)
	require.Equal(t, uint64(2112), got.GetId())
}
