package resourcepool

import (
	"testing"

	"github.com/stretchr/testify/assert"
	"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"
	"github.com/tidbcloud/tidb-management-service/nextgen-global-service/domain/repository/region/mock_region"
	resourcepoolrepo "github.com/tidbcloud/tidb-management-service/nextgen-global-service/domain/repository/resourcepool"
	"github.com/tidbcloud/tidb-management-service/nextgen-global-service/domain/repository/resourcepool/mock_resourcepool"
	"github.com/tidbcloud/tidb-management-service/utilities/bizerr"
)

func TestPickSharedResourcePool_Fallback(t *testing.T) {
	ctx := t.Context()
	ctrl := gomock.NewController(t)
	repo := mock_resourcepool.NewMockRepo(ctrl)
	strategyRepo := mock_resourcepool.NewMockStrategyRepo(ctrl)
	regionRepo := mock_region.NewMockRepo(ctrl)
	svc := &serviceImpl{resourcePoolRepo: repo, resourcePoolStrategyRepo: strategyRepo, regionRepo: regionRepo}

	want := &entity.ResourcePool{ResourcePool: &globalngv1.ResourcePool{Id: 101}}

	// org-default and global-default strategy queries return empty → fall through
	strategyRepo.EXPECT().Query(gomock.Any(), gomock.Any()).Return(nil, nil).Times(2)
	regionRepo.EXPECT().SelectRegion(gomock.Any(), gomock.Any()).Return(&entity.Region{RegionID: 9999}, nil)

	repo.EXPECT().Query(gomock.Any(), gomock.Any()).DoAndReturn(
		func(_ any, filter *resourcepoolrepo.Filter) ([]*entity.ResourcePool, int64, bizerr.BizError) {
			assert.Equal(t, int(globalngv1.ResourcePool_SHARED), filter.OwnerType)
			assert.Equal(t, []uint64{0}, filter.OwnerIds)
			assert.Empty(t, filter.HighAvailabilityTypes)
			assert.EqualValues(t, 1, filter.PageSize)
			return []*entity.ResourcePool{want}, 1, nil
		})

	got, err := svc.PickSharedResourcePool(ctx, entity.ResourcePool{
		ResourcePool: &globalngv1.ResourcePool{
			ProviderId:           globalcommonv1.CloudProvider_AWS,
			RegionId:             globalcommonv1.Region_AWS_US_WEST_2,
			ServicePlan:          globalcommonv1.ServicePlan_Premium,
			HighAvailabilityType: globalngv1.ResourcePool_ZONAL,
		},
	}, 123)

	require.Nil(t, err)
	assert.Same(t, want, got)
}

func TestPickSharedResourcePool_NonEssentialPreservesDefaultSelection(t *testing.T) {
	ctx := t.Context()
	ctrl := gomock.NewController(t)
	repo := mock_resourcepool.NewMockRepo(ctrl)
	strategyRepo := mock_resourcepool.NewMockStrategyRepo(ctrl)
	regionRepo := mock_region.NewMockRepo(ctrl)
	svc := &serviceImpl{resourcePoolRepo: repo, resourcePoolStrategyRepo: strategyRepo, regionRepo: regionRepo}

	regionalPool := &entity.ResourcePool{ResourcePool: &globalngv1.ResourcePool{
		Id:                   101,
		HighAvailabilityType: globalngv1.ResourcePool_REGIONAL,
	}}
	strategyRepo.EXPECT().Query(gomock.Any(), gomock.Any()).Return(
		[]*entity.ResourcePoolStrategy{{ResourcePoolStrategy: &globalngv1.ResourcePoolStrategy{
			DefaultResourcePoolId: 101,
		}}}, nil,
	)
	repo.EXPECT().Load(gomock.Any(), uint64(101), false).Return(regionalPool, nil)

	got, err := svc.PickSharedResourcePool(ctx, entity.ResourcePool{
		ResourcePool: &globalngv1.ResourcePool{
			ProviderId:           globalcommonv1.CloudProvider_AWS,
			RegionId:             globalcommonv1.Region_AWS_US_WEST_2,
			ServicePlan:          globalcommonv1.ServicePlan_Premium,
			HighAvailabilityType: globalngv1.ResourcePool_ZONAL,
		},
	}, 123)

	require.Nil(t, err)
	assert.Same(t, regionalPool, got)
}

func TestPickSharedResourcePool_NotFound(t *testing.T) {
	ctx := t.Context()
	ctrl := gomock.NewController(t)
	repo := mock_resourcepool.NewMockRepo(ctrl)
	strategyRepo := mock_resourcepool.NewMockStrategyRepo(ctrl)
	regionRepo := mock_region.NewMockRepo(ctrl)
	svc := &serviceImpl{resourcePoolRepo: repo, resourcePoolStrategyRepo: strategyRepo, regionRepo: regionRepo}

	strategyRepo.EXPECT().Query(gomock.Any(), gomock.Any()).Return(nil, nil).Times(1)
	regionRepo.EXPECT().SelectRegion(gomock.Any(), gomock.Any()).Return(&entity.Region{RegionID: 9999}, nil)
	repo.EXPECT().Query(gomock.Any(), gomock.Any()).Return(nil, int64(0), nil)

	got, err := svc.PickSharedResourcePool(ctx, entity.ResourcePool{
		ResourcePool: &globalngv1.ResourcePool{
			ProviderId:  globalcommonv1.CloudProvider_AWS,
			RegionId:    globalcommonv1.Region_AWS_US_WEST_2,
			ServicePlan: globalcommonv1.ServicePlan_Essential_V2,
		},
	}, 0)

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

func TestPickSharedResourcePool_SkipsMismatchedDefaultPool(t *testing.T) {
	ctx := t.Context()
	ctrl := gomock.NewController(t)
	repo := mock_resourcepool.NewMockRepo(ctrl)
	strategyRepo := mock_resourcepool.NewMockStrategyRepo(ctrl)
	regionRepo := mock_region.NewMockRepo(ctrl)
	svc := &serviceImpl{resourcePoolRepo: repo, resourcePoolStrategyRepo: strategyRepo, regionRepo: regionRepo}

	regionalPool := &entity.ResourcePool{ResourcePool: &globalngv1.ResourcePool{
		Id:                   101,
		HighAvailabilityType: globalngv1.ResourcePool_REGIONAL,
	}}
	zonalPool := &entity.ResourcePool{ResourcePool: &globalngv1.ResourcePool{
		Id:                   202,
		HighAvailabilityType: globalngv1.ResourcePool_ZONAL,
	}}

	strategyRepo.EXPECT().Query(gomock.Any(), gomock.Any()).DoAndReturn(
		func(_ any, filter *resourcepoolrepo.StrategyFilter) ([]*entity.ResourcePoolStrategy, bizerr.BizError) {
			poolID := uint64(101)
			if assert.Len(t, filter.OrganizationIds, 1) && filter.OrganizationIds[0] == 0 {
				poolID = 202
			}
			return []*entity.ResourcePoolStrategy{{ResourcePoolStrategy: &globalngv1.ResourcePoolStrategy{
				DefaultResourcePoolId: poolID,
			}}}, nil
		},
	).Times(2)
	repo.EXPECT().Load(gomock.Any(), uint64(101), false).Return(regionalPool, nil)
	repo.EXPECT().Load(gomock.Any(), uint64(202), false).Return(zonalPool, nil)

	got, err := svc.PickSharedResourcePool(ctx, entity.ResourcePool{
		ResourcePool: &globalngv1.ResourcePool{
			ProviderId:           globalcommonv1.CloudProvider_AWS,
			RegionId:             globalcommonv1.Region_AWS_AP_NORTHEAST_1,
			ServicePlan:          globalcommonv1.ServicePlan_Essential_V2,
			HighAvailabilityType: globalngv1.ResourcePool_ZONAL,
		},
	}, 123)

	require.Nil(t, err)
	assert.Same(t, zonalPool, got)
}

func TestPickSharedResourcePool_ExplicitHighAvailabilityExcludesUnspecifiedPools(t *testing.T) {
	ctx := t.Context()
	ctrl := gomock.NewController(t)
	repo := mock_resourcepool.NewMockRepo(ctrl)
	strategyRepo := mock_resourcepool.NewMockStrategyRepo(ctrl)
	regionRepo := mock_region.NewMockRepo(ctrl)
	svc := &serviceImpl{resourcePoolRepo: repo, resourcePoolStrategyRepo: strategyRepo, regionRepo: regionRepo}

	unspecifiedPool := &entity.ResourcePool{ResourcePool: &globalngv1.ResourcePool{Id: 101}}
	strategyRepo.EXPECT().Query(gomock.Any(), gomock.Any()).DoAndReturn(
		func(_ any, filter *resourcepoolrepo.StrategyFilter) ([]*entity.ResourcePoolStrategy, bizerr.BizError) {
			if filter.OrganizationIds[0] == 123 {
				return []*entity.ResourcePoolStrategy{{ResourcePoolStrategy: &globalngv1.ResourcePoolStrategy{
					DefaultResourcePoolId: 101,
				}}}, nil
			}
			return nil, nil
		},
	).Times(2)
	repo.EXPECT().Load(gomock.Any(), uint64(101), false).Return(unspecifiedPool, nil)
	regionRepo.EXPECT().SelectRegion(gomock.Any(), gomock.Any()).Return(&entity.Region{RegionID: 9999}, nil)
	repo.EXPECT().Query(gomock.Any(), gomock.Any()).DoAndReturn(
		func(_ any, filter *resourcepoolrepo.Filter) ([]*entity.ResourcePool, int64, bizerr.BizError) {
			assert.Equal(t, []globalngv1.ResourcePool_HighAvailabilityType{
				globalngv1.ResourcePool_REGIONAL,
			}, filter.HighAvailabilityTypes)
			return nil, 0, nil
		},
	)

	got, err := svc.PickSharedResourcePool(ctx, entity.ResourcePool{
		ResourcePool: &globalngv1.ResourcePool{
			ProviderId:           globalcommonv1.CloudProvider_AWS,
			RegionId:             globalcommonv1.Region_AWS_AP_NORTHEAST_1,
			ServicePlan:          globalcommonv1.ServicePlan_Essential_V2,
			HighAvailabilityType: globalngv1.ResourcePool_REGIONAL,
		},
	}, 123)

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