package cluster

import (
	"context"
	"testing"

	"github.com/stretchr/testify/assert"
	"github.com/stretchr/testify/require"

	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"
	resourcepoolsvc "github.com/tidbcloud/tidb-management-service/nextgen-global-service/domain/service/resourcepool"
	"github.com/tidbcloud/tidb-management-service/utilities/bizerr"
)

func TestPickResourcePoolForCreate_EssentialV2UsesSharedPool(t *testing.T) {
	ctx := t.Context()
	poolSvc := &fakeCreateResourcePoolService{
		sharedPool: &entity.ResourcePool{ResourcePool: &globalngv1.ResourcePool{Id: 101}},
	}
	svc := &serviceImpl{resourcePoolSvc: poolSvc}

	got, err := svc.pickResourcePoolForCreate(ctx, &globalngv1.Cluster{
		CloudProviderId:      globalcommonv1.CloudProvider_AWS,
		RegionId:             globalcommonv1.Region_AWS_US_WEST_2,
		ServicePlan:          globalcommonv1.ServicePlan_Essential_V2,
		OrganizationId:       123,
		HighAvailabilityType: globalngv1.Cluster_ZONAL,
		MaxRcu:               1000,
	})

	require.Nil(t, err)
	assert.EqualValues(t, 101, got.GetId())
	assert.Equal(t, 1, poolSvc.pickSharedCalls)
	assert.Equal(t, 0, poolSvc.getOrCreateDefaultCalls)
	assert.Equal(t, globalcommonv1.CloudProvider_AWS, poolSvc.pickSharedPool.GetProviderId())
	assert.Equal(t, globalcommonv1.Region_AWS_US_WEST_2, poolSvc.pickSharedPool.GetRegionId())
	assert.Equal(t, globalcommonv1.ServicePlan_Essential_V2, poolSvc.pickSharedPool.GetServicePlan())
	assert.EqualValues(t, 123, poolSvc.pickSharedOrgId)
}

func TestPickResourcePoolForCreate_NonEssentialV2UsesOrgDefaultPool(t *testing.T) {
	ctx := t.Context()
	poolSvc := &fakeCreateResourcePoolService{
		defaultPool: &entity.ResourcePool{ResourcePool: &globalngv1.ResourcePool{Id: 202}},
	}
	svc := &serviceImpl{resourcePoolSvc: poolSvc}

	got, err := svc.pickResourcePoolForCreate(ctx, &globalngv1.Cluster{
		CloudProviderId: globalcommonv1.CloudProvider_AWS,
		RegionId:        globalcommonv1.Region_AWS_US_WEST_2,
		ServicePlan:     globalcommonv1.ServicePlan_Premium,
		OrganizationId:  123,
		MaxRcu:          1000,
	})

	require.Nil(t, err)
	assert.EqualValues(t, 202, got.GetId())
	assert.Equal(t, 0, poolSvc.pickSharedCalls)
	assert.Equal(t, 1, poolSvc.getOrCreateDefaultCalls)
	assert.Equal(t, globalngv1.ResourcePool_ORGANIZATION, poolSvc.defaultPoolRequest.GetOwnerType())
	assert.EqualValues(t, 123, poolSvc.defaultPoolRequest.GetOwnerId())
	assert.EqualValues(t, 1000, poolSvc.requiredRcu)
}

func TestPickResourcePoolForCreate_BYOCRejectsMissingSelection(t *testing.T) {
	ctx := t.Context()
	poolSvc := &fakeCreateResourcePoolService{}
	svc := &serviceImpl{resourcePoolSvc: poolSvc}

	got, err := svc.pickResourcePoolForCreate(ctx, &globalngv1.Cluster{
		CloudProviderId: globalcommonv1.CloudProvider_AWS,
		RegionId:        globalcommonv1.Region_AWS_US_WEST_2,
		ServicePlan:     globalcommonv1.ServicePlan_BYOC,
		OrganizationId:  123,
		MaxRcu:          1000,
	})

	require.Nil(t, got)
	require.ErrorContains(t, err, "resource_pool_id is required for BYOC")
	assert.Zero(t, poolSvc.pickSharedCalls)
	assert.Zero(t, poolSvc.getOrCreateDefaultCalls)
	assert.Zero(t, poolSvc.createTopologyDrivenCalls)
}

func TestPickResourcePoolForCreate_PremiumReservedUsesTopologyDrivenPool(t *testing.T) {
	ctx := t.Context()
	fixedTopology := &globalngv1.FixedTopology{
		Components: []*globalngv1.ComponentTopology{
			{
				ComponentType: globalngv1.ComponentType_TIDB,
				NodeQuota: &globalngv1.NodeQuota{
					NodeSpec:  &globalngv1.NodeSpec{Cpu: 4, MemoryGi: 8},
					NodeCount: 1,
				},
			},
		},
	}
	poolSvc := &fakeCreateResourcePoolService{
		defaultPool: &entity.ResourcePool{ResourcePool: &globalngv1.ResourcePool{Id: 303}},
	}
	svc := &serviceImpl{resourcePoolSvc: poolSvc}

	got, err := svc.pickResourcePoolForCreate(ctx, &globalngv1.Cluster{
		ClusterId:       999,
		CloudProviderId: globalcommonv1.CloudProvider_AWS,
		RegionId:        globalcommonv1.Region_AWS_US_WEST_2,
		ServicePlan:     globalcommonv1.ServicePlan_Premium_Reserved,
		OrganizationId:  123,
		ReservedCapacityTemplate: &globalngv1.ReservedCapacityTemplate{
			FixedTopology: fixedTopology,
		},
	})

	require.Nil(t, err)
	assert.EqualValues(t, 303, got.GetId())
	assert.Equal(t, 0, poolSvc.pickSharedCalls)
	assert.Equal(t, 0, poolSvc.getOrCreateDefaultCalls)
	assert.Equal(t, 1, poolSvc.createTopologyDrivenCalls)
	assert.Same(t, fixedTopology, poolSvc.fixedTopology)
	assert.Equal(t, globalngv1.ResourcePool_ORGANIZATION, poolSvc.defaultPoolRequest.GetOwnerType())
	assert.EqualValues(t, 123, poolSvc.defaultPoolRequest.GetOwnerId())
	assert.EqualValues(t, 1, poolSvc.defaultPoolRequest.GetInstanceLimit())
	assert.Equal(t, "reserved-for-999", poolSvc.poolName)
}

type fakeCreateResourcePoolService struct {
	resourcepoolsvc.Service

	sharedPool      *entity.ResourcePool
	defaultPool     *entity.ResourcePool
	findDefaultPool *entity.ResourcePool

	pickSharedCalls           int
	getOrCreateDefaultCalls   int
	findDefaultCalls          int
	createTopologyDrivenCalls int
	pickSharedPool            entity.ResourcePool
	pickSharedOrgId           uint64
	pickSharedErr             bizerr.BizError
	defaultPoolRequest        entity.ResourcePool
	poolName                  string
	requiredRcu               uint32
	fixedTopology             *globalngv1.FixedTopology
	resumeCalls               int
	resumeRequest             *globalngv1.ResumeResourcePoolRequest
	resumeErr                 bizerr.BizError
}

func (f *fakeCreateResourcePoolService) ResumeResourcePool(
	_ context.Context,
	request *globalngv1.ResumeResourcePoolRequest,
) (*globalngv1.ResumeResourcePoolResponse, bizerr.BizError) {
	f.resumeCalls++
	f.resumeRequest = request
	if f.resumeErr != nil {
		return nil, f.resumeErr
	}
	return &globalngv1.ResumeResourcePoolResponse{OperationId: request.GetOperationId()}, nil
}

func (f *fakeCreateResourcePoolService) PickSharedResourcePool(
	_ context.Context,
	pool entity.ResourcePool,
	orgId uint64,
) (*entity.ResourcePool, bizerr.BizError) {
	f.pickSharedCalls++
	f.pickSharedPool = pool
	f.pickSharedOrgId = orgId
	if f.pickSharedErr != nil {
		return nil, f.pickSharedErr
	}
	return f.sharedPool, nil
}

func (f *fakeCreateResourcePoolService) GetOrCreateDefaultResourcePool(
	_ context.Context,
	pool entity.ResourcePool,
	requiredRcu uint32,
) (*entity.ResourcePool, bizerr.BizError) {
	f.getOrCreateDefaultCalls++
	f.defaultPoolRequest = pool
	f.requiredRcu = requiredRcu
	return f.defaultPool, nil
}

func (f *fakeCreateResourcePoolService) FindDefaultResourcePool(
	_ context.Context,
	pool entity.ResourcePool,
) (*entity.ResourcePool, bizerr.BizError) {
	f.findDefaultCalls++
	f.defaultPoolRequest = pool
	return f.findDefaultPool, nil
}

func (f *fakeCreateResourcePoolService) CreateTopologyDrivenResourcePool(
	_ context.Context,
	pool entity.ResourcePool,
	poolName string,
	fixedTopology *globalngv1.FixedTopology,
) (*entity.ResourcePool, bizerr.BizError) {
	f.createTopologyDrivenCalls++
	f.defaultPoolRequest = pool
	f.poolName = poolName
	f.fixedTopology = fixedTopology
	return f.defaultPool, nil
}
