package resourcepool

import (
	"math"
	"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/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"
	"github.com/tidbcloud/tidb-management-service/nextgen-global-service/domain/adapter/eventbus/mock_eventbus"
	"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"
	"github.com/tidbcloud/tidb-management-service/nextgen-global-service/domain/repository/rcuquota/mock_rcuquota"
	"github.com/tidbcloud/tidb-management-service/nextgen-global-service/domain/repository/region/mock_region"
	"github.com/tidbcloud/tidb-management-service/nextgen-global-service/domain/repository/resourcepool/mock_resourcepool"
	"github.com/tidbcloud/tidb-management-service/utilities/bizerr"
)

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

	resourcePoolRepo := mock_resourcepool.NewMockRepo(ctrl)
	regionRepo := mock_region.NewMockRepo(ctrl)
	regionalCli := mock_regionalserver.NewMockClient(ctrl)

	svc := &serviceImpl{
		resourcePoolRepo: resourcePoolRepo,
		regionRepo:       regionRepo,
		regionalCli:      regionalCli,
	}

	resourcePool := &entity.ResourcePool{
		ResourcePool: &globalngv1.ResourcePool{Id: 100, VcpusLimit: 80, LifecycleState: globalngv1.ResourcePool_PAUSED},
		RegionId:     7,
	}
	region := &entity.Region{Region: &globalcommonv1.Region{Id: globalcommonv1.Region_AWS_US_WEST_2}}

	resourcePoolRepo.EXPECT().Load(gomock.Any(), uint64(100), false).Return(resourcePool, nil).Times(2)
	resourcePoolRepo.EXPECT().UpdateVcpuPolicy(
		gomock.Any(),
		uint64(100),
		globalngv1.ResourcePool_LIMITED,
		uint64(106),
	).Return(nil)
	regionRepo.EXPECT().Get(gomock.Any(), uint64(7)).Return(region, nil)
	regionalCli.EXPECT().GetSharedPoolVcpusUsage(gomock.Any(), region, &clusterngv1beta.GetSharedPoolVcpusUsageRequest{
		SharedPoolId: "100",
	}).Return(&clusterngv1beta.GetSharedPoolVcpusUsageResponse{
		SharedPoolId: "100",
		VcpusUsed:    90,
	}, nil)
	regionalCli.EXPECT().UpdateSharedPoolVcpusLimit(
		gomock.Any(),
		region,
		&clusterngv1beta.UpdateSharedPoolVcpusLimitRequest{SharedPoolId: "100", VcpusLimit: 106},
	).Return(&clusterngv1beta.SharedPool{}, nil)

	resp, err := svc.UpdateResourcePoolInstanceLimit(t.Context(), &globalngv1.UpdateResourcePoolInstanceLimitRequest{
		ResourcePoolId: 100,
		VcpusLimit:     proto.Uint64(106),
	})
	require.NoError(t, err)
	require.EqualValues(t, 106, resp.GetVcpusLimit())
}

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

	resourcePoolRepo := mock_resourcepool.NewMockRepo(ctrl)
	regionRepo := mock_region.NewMockRepo(ctrl)
	regionalCli := mock_regionalserver.NewMockClient(ctrl)

	svc := &serviceImpl{
		resourcePoolRepo: resourcePoolRepo,
		regionRepo:       regionRepo,
		regionalCli:      regionalCli,
	}

	resourcePool := &entity.ResourcePool{
		ResourcePool: &globalngv1.ResourcePool{Id: 100, VcpusLimit: 80, LifecycleState: globalngv1.ResourcePool_ACTIVE},
		RegionId:     7,
	}
	region := &entity.Region{Region: &globalcommonv1.Region{Id: globalcommonv1.Region_AWS_US_WEST_2}}

	resourcePoolRepo.EXPECT().Load(gomock.Any(), uint64(100), false).Return(resourcePool, nil).Times(2)
	regionRepo.EXPECT().Get(gomock.Any(), uint64(7)).Return(region, nil)
	regionalCli.EXPECT().GetSharedPoolVcpusUsage(gomock.Any(), region, &clusterngv1beta.GetSharedPoolVcpusUsageRequest{
		SharedPoolId: "100",
	}).Return(&clusterngv1beta.GetSharedPoolVcpusUsageResponse{
		SharedPoolId: "100",
		VcpusUsed:    90,
	}, nil)
	regionalCli.EXPECT().UpdateSharedPoolVcpusLimit(
		gomock.Any(),
		region,
		&clusterngv1beta.UpdateSharedPoolVcpusLimitRequest{SharedPoolId: "100", VcpusLimit: 106},
	).Return(nil, bizerr.New(bizerr.ErrCodeUnknown, "regional failure"))

	_, err := svc.UpdateResourcePoolInstanceLimit(t.Context(), &globalngv1.UpdateResourcePoolInstanceLimitRequest{
		ResourcePoolId: 100,
		VcpusLimit:     proto.Uint64(106),
	})
	require.Error(t, err)
}

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

	resourcePoolRepo := mock_resourcepool.NewMockRepo(ctrl)
	regionRepo := mock_region.NewMockRepo(ctrl)
	regionalCli := mock_regionalserver.NewMockClient(ctrl)

	svc := &serviceImpl{
		resourcePoolRepo: resourcePoolRepo,
		regionRepo:       regionRepo,
		regionalCli:      regionalCli,
	}

	resourcePool := &entity.ResourcePool{
		ResourcePool: &globalngv1.ResourcePool{Id: 100, VcpusLimit: 80, LifecycleState: globalngv1.ResourcePool_ACTIVE},
		RegionId:     7,
	}
	region := &entity.Region{Region: &globalcommonv1.Region{Id: globalcommonv1.Region_AWS_US_WEST_2}}

	resourcePoolRepo.EXPECT().Load(gomock.Any(), uint64(100), false).Return(resourcePool, nil).Times(2)
	regionRepo.EXPECT().Get(gomock.Any(), uint64(7)).Return(region, nil)
	gomock.InOrder(
		regionalCli.EXPECT().GetSharedPoolVcpusUsage(gomock.Any(), region, &clusterngv1beta.GetSharedPoolVcpusUsageRequest{
			SharedPoolId: "100",
		}).Return(&clusterngv1beta.GetSharedPoolVcpusUsageResponse{
			SharedPoolId: "100",
			VcpusUsed:    90,
		}, nil),
		regionalCli.EXPECT().UpdateSharedPoolVcpusLimit(
			gomock.Any(),
			region,
			&clusterngv1beta.UpdateSharedPoolVcpusLimitRequest{SharedPoolId: "100", VcpusLimit: 106},
		).Return(&clusterngv1beta.SharedPool{}, nil),
		resourcePoolRepo.EXPECT().UpdateVcpuPolicy(
			gomock.Any(),
			uint64(100),
			globalngv1.ResourcePool_LIMITED,
			uint64(106),
		).Return(bizerr.New(bizerr.ErrCodeUnknown, "save failed")),
		regionalCli.EXPECT().UpdateSharedPoolVcpusLimit(
			gomock.Any(),
			region,
			&clusterngv1beta.UpdateSharedPoolVcpusLimitRequest{SharedPoolId: "100", VcpusLimit: 80},
		).Return(&clusterngv1beta.SharedPool{}, nil),
	)

	_, err := svc.UpdateResourcePoolInstanceLimit(t.Context(), &globalngv1.UpdateResourcePoolInstanceLimitRequest{
		ResourcePoolId: 100,
		VcpusLimit:     proto.Uint64(106),
	})
	require.Error(t, err)
}

func TestUpdateResourcePoolInstanceLimitPersistsAcceptedUpdateAfterLifecycleTransition(t *testing.T) {
	ctrl := gomock.NewController(t)
	resourcePoolRepo := mock_resourcepool.NewMockRepo(ctrl)
	regionRepo := mock_region.NewMockRepo(ctrl)
	regionalCli := mock_regionalserver.NewMockClient(ctrl)

	resourcePool := &entity.ResourcePool{
		ResourcePool: &globalngv1.ResourcePool{
			Id:             100,
			VcpusLimit:     80,
			LifecycleState: globalngv1.ResourcePool_ACTIVE,
		},
		RegionId: 7,
	}
	transitionedPool := &entity.ResourcePool{
		ResourcePool: &globalngv1.ResourcePool{
			Id:             100,
			VcpusLimit:     80,
			LifecycleState: globalngv1.ResourcePool_PAUSING,
		},
		RegionId: 7,
	}
	region := &entity.Region{Region: &globalcommonv1.Region{Id: globalcommonv1.Region_AWS_US_WEST_2}}

	resourcePoolRepo.EXPECT().Load(gomock.Any(), uint64(100), false).Return(resourcePool, nil).Times(2)
	resourcePoolRepo.EXPECT().LoadForUpdate(gomock.Any(), uint64(100), false).Return(transitionedPool, nil)
	resourcePoolRepo.EXPECT().UpdateVcpuPolicy(
		gomock.Any(),
		uint64(100),
		globalngv1.ResourcePool_LIMITED,
		uint64(106),
	).Return(nil)
	regionRepo.EXPECT().Get(gomock.Any(), uint64(7)).Return(region, nil)
	regionalCli.EXPECT().GetSharedPoolVcpusUsage(
		gomock.Any(),
		region,
		&clusterngv1beta.GetSharedPoolVcpusUsageRequest{SharedPoolId: "100"},
	).Return(&clusterngv1beta.GetSharedPoolVcpusUsageResponse{
		SharedPoolId: "100",
		VcpusUsed:    90,
	}, nil)
	regionalCli.EXPECT().UpdateSharedPoolVcpusLimit(
		gomock.Any(),
		region,
		&clusterngv1beta.UpdateSharedPoolVcpusLimitRequest{SharedPoolId: "100", VcpusLimit: 106},
	).Return(&clusterngv1beta.SharedPool{}, nil)

	svc := &serviceImpl{
		transaction:      resourcePoolPassthroughTransactioner{},
		resourcePoolRepo: resourcePoolRepo,
		regionRepo:       regionRepo,
		regionalCli:      regionalCli,
	}
	response, err := svc.UpdateResourcePoolInstanceLimit(t.Context(), &globalngv1.UpdateResourcePoolInstanceLimitRequest{
		ResourcePoolId: 100,
		VcpusLimit:     proto.Uint64(106),
	})

	require.NoError(t, err)
	require.Equal(t, uint64(106), response.GetVcpusLimit())
}

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

	resourcePoolRepo := mock_resourcepool.NewMockRepo(ctrl)
	regionRepo := mock_region.NewMockRepo(ctrl)
	regionalCli := mock_regionalserver.NewMockClient(ctrl)
	rcuQuotaRepo := mock_rcuquota.NewMockRepo(ctrl)

	svc := &serviceImpl{
		resourcePoolRepo: resourcePoolRepo,
		regionRepo:       regionRepo,
		regionalCli:      regionalCli,
		rcuQuotaRepo:     rcuQuotaRepo,
	}

	resourcePool := &entity.ResourcePool{
		ResourcePool: &globalngv1.ResourcePool{
			Id:             100,
			VcpusLimit:     180,
			OwnerId:        42,
			ServicePlan:    globalcommonv1.ServicePlan_BYOC,
			LifecycleState: globalngv1.ResourcePool_ACTIVE,
		},
		RegionId: 7,
	}
	region := &entity.Region{Region: &globalcommonv1.Region{Id: globalcommonv1.Region_AWS_US_WEST_2}}

	resourcePoolRepo.EXPECT().Load(gomock.Any(), uint64(100), false).Return(resourcePool, nil).Times(2)
	rcuQuotaRepo.EXPECT().
		Load(gomock.Any(), uint64(42), globalcommonv1.ServicePlan_BYOC.String()).
		Return(&globalngv1.RcuQuota{VcpusLimitPerResourcePool: 120}, nil)
	regionRepo.EXPECT().Get(gomock.Any(), uint64(7)).Return(region, nil)
	regionalCli.EXPECT().GetSharedPoolVcpusUsage(gomock.Any(), region, &clusterngv1beta.GetSharedPoolVcpusUsageRequest{
		SharedPoolId: "100",
	}).Return(&clusterngv1beta.GetSharedPoolVcpusUsageResponse{
		SharedPoolId: "100",
		VcpusUsed:    90,
	}, nil)
	regionalCli.EXPECT().UpdateSharedPoolVcpusLimit(
		gomock.Any(),
		region,
		&clusterngv1beta.UpdateSharedPoolVcpusLimitRequest{SharedPoolId: "100", VcpusLimit: 120},
	).Return(&clusterngv1beta.SharedPool{}, nil)
	resourcePoolRepo.EXPECT().UpdateVcpuPolicy(
		gomock.Any(),
		uint64(100),
		globalngv1.ResourcePool_INHERITED,
		uint64(120),
	).Return(nil)

	resp, err := svc.UpdateResourcePoolInstanceLimit(t.Context(), &globalngv1.UpdateResourcePoolInstanceLimitRequest{
		ResourcePoolId: 100,
		VcpusLimit:     proto.Uint64(0),
	})
	require.NoError(t, err)
	require.EqualValues(t, 120, resp.GetVcpusLimit())
}

func TestUpdateResourcePoolInstanceLimitRequiresAtLeastOneField(t *testing.T) {
	svc := &serviceImpl{}
	_, err := svc.UpdateResourcePoolInstanceLimit(
		t.Context(),
		&globalngv1.UpdateResourcePoolInstanceLimitRequest{ResourcePoolId: 100},
	)
	require.Error(t, err)
	require.Equal(t, bizerr.ErrCodeParams, err.ErrCode())
}

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

	resourcePoolRepo := mock_resourcepool.NewMockRepo(ctrl)
	regionRepo := mock_region.NewMockRepo(ctrl)
	regionalCli := mock_regionalserver.NewMockClient(ctrl)

	svc := &serviceImpl{
		resourcePoolRepo: resourcePoolRepo,
		regionRepo:       regionRepo,
		regionalCli:      regionalCli,
	}

	resourcePool := &entity.ResourcePool{
		ResourcePool: &globalngv1.ResourcePool{Id: 100, VcpusLimit: 100, LifecycleState: globalngv1.ResourcePool_ACTIVE},
		RegionId:     7,
	}
	region := &entity.Region{Region: &globalcommonv1.Region{Id: globalcommonv1.Region_AWS_US_WEST_2}}

	resourcePoolRepo.EXPECT().Load(gomock.Any(), uint64(100), false).Return(resourcePool, nil)
	regionRepo.EXPECT().Get(gomock.Any(), uint64(7)).Return(region, nil)
	regionalCli.EXPECT().GetSharedPoolVcpusUsage(gomock.Any(), region, &clusterngv1beta.GetSharedPoolVcpusUsageRequest{
		SharedPoolId: "100",
	}).Return(&clusterngv1beta.GetSharedPoolVcpusUsageResponse{
		SharedPoolId: "100",
		VcpusUsed:    130,
	}, nil)

	_, err := svc.UpdateResourcePoolInstanceLimit(t.Context(), &globalngv1.UpdateResourcePoolInstanceLimitRequest{
		ResourcePoolId: 100,
		VcpusLimit:     proto.Uint64(106),
	})
	require.Error(t, err)
	require.Equal(t, bizerr.ErrCodeParams, err.ErrCode())
	require.Contains(t, err.ErrMsg(), "106")
	require.Contains(t, err.ErrMsg(), "130")
}

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

	regionalCli := mock_regionalserver.NewMockClient(ctrl)
	svc := &serviceImpl{
		regionalCli: regionalCli,
	}

	region := &entity.Region{Region: &globalcommonv1.Region{Id: globalcommonv1.Region_AWS_US_WEST_2}}
	regionalCli.EXPECT().GetSharedPoolVcpusUsage(gomock.Any(), region, &clusterngv1beta.GetSharedPoolVcpusUsageRequest{
		SharedPoolId: "100",
	}).Return(&clusterngv1beta.GetSharedPoolVcpusUsageResponse{
		SharedPoolId: "100",
		VcpusUsed:    math.MaxUint64,
	}, nil)

	usage, err := svc.getSharedPoolVcpusUsage(t.Context(), region, 100)
	require.NoError(t, err)
	require.Equal(t, uint64(math.MaxUint64), usage)
}

func TestUpdateResourcePoolInstanceLimitPersistsLimitedPolicy(t *testing.T) {
	ctrl := gomock.NewController(t)
	resourcePoolRepo := mock_resourcepool.NewMockRepo(ctrl)
	regionRepo := mock_region.NewMockRepo(ctrl)
	regionalCli := mock_regionalserver.NewMockClient(ctrl)
	svc := &serviceImpl{
		resourcePoolRepo: resourcePoolRepo,
		regionRepo:       regionRepo,
		regionalCli:      regionalCli,
	}
	pool := &entity.ResourcePool{
		ResourcePool: &globalngv1.ResourcePool{
			Id:             100,
			VcpusLimit:     80,
			VcpuLimitMode:  globalngv1.ResourcePool_LIMITED,
			LifecycleState: globalngv1.ResourcePool_ACTIVE,
		},
		RegionId: 7,
	}
	region := &entity.Region{Region: &globalcommonv1.Region{Id: globalcommonv1.Region_AWS_US_WEST_2}}

	resourcePoolRepo.EXPECT().Load(gomock.Any(), uint64(100), false).Return(pool, nil).Times(2)
	regionRepo.EXPECT().Get(gomock.Any(), uint64(7)).Return(region, nil)
	regionalCli.EXPECT().GetSharedPoolVcpusUsage(
		gomock.Any(),
		region,
		&clusterngv1beta.GetSharedPoolVcpusUsageRequest{SharedPoolId: "100"},
	).Return(&clusterngv1beta.GetSharedPoolVcpusUsageResponse{VcpusUsed: 90}, nil)
	regionalCli.EXPECT().UpdateSharedPoolVcpusLimit(
		gomock.Any(),
		region,
		&clusterngv1beta.UpdateSharedPoolVcpusLimitRequest{SharedPoolId: "100", VcpusLimit: 120},
	).Return(&clusterngv1beta.SharedPool{}, nil)
	resourcePoolRepo.EXPECT().UpdateVcpuPolicy(
		gomock.Any(),
		uint64(100),
		globalngv1.ResourcePool_LIMITED,
		uint64(120),
	).Return(nil)

	response, err := svc.UpdateResourcePoolInstanceLimit(t.Context(), &globalngv1.UpdateResourcePoolInstanceLimitRequest{
		ResourcePoolId: 100,
		VcpusLimit:     proto.Uint64(120),
		VcpuLimitMode:  globalngv1.ResourcePool_LIMITED,
	})

	require.NoError(t, err)
	require.Equal(t, globalngv1.ResourcePool_LIMITED, response.GetVcpuLimitMode())
	require.EqualValues(t, 120, response.GetVcpusLimit())
}

func TestUpdateResourcePoolInstanceLimitAllowsFiniteDecrease(t *testing.T) {
	ctrl := gomock.NewController(t)
	resourcePoolRepo := mock_resourcepool.NewMockRepo(ctrl)
	regionRepo := mock_region.NewMockRepo(ctrl)
	regionalCli := mock_regionalserver.NewMockClient(ctrl)
	svc := &serviceImpl{
		resourcePoolRepo: resourcePoolRepo,
		regionRepo:       regionRepo,
		regionalCli:      regionalCli,
	}
	pool := &entity.ResourcePool{
		ResourcePool: &globalngv1.ResourcePool{
			Id:             100,
			VcpusLimit:     120,
			VcpuLimitMode:  globalngv1.ResourcePool_LIMITED,
			LifecycleState: globalngv1.ResourcePool_ACTIVE,
		},
		RegionId: 7,
	}
	region := &entity.Region{Region: &globalcommonv1.Region{Id: globalcommonv1.Region_AWS_US_WEST_2}}

	resourcePoolRepo.EXPECT().Load(gomock.Any(), uint64(100), false).Return(pool, nil).Times(2)
	regionRepo.EXPECT().Get(gomock.Any(), uint64(7)).Return(region, nil)
	regionalCli.EXPECT().GetSharedPoolVcpusUsage(
		gomock.Any(),
		region,
		&clusterngv1beta.GetSharedPoolVcpusUsageRequest{SharedPoolId: "100"},
	).Return(&clusterngv1beta.GetSharedPoolVcpusUsageResponse{VcpusUsed: 90}, nil)
	regionalCli.EXPECT().UpdateSharedPoolVcpusLimit(
		gomock.Any(),
		region,
		&clusterngv1beta.UpdateSharedPoolVcpusLimitRequest{SharedPoolId: "100", VcpusLimit: 100},
	).Return(&clusterngv1beta.SharedPool{}, nil)
	resourcePoolRepo.EXPECT().UpdateVcpuPolicy(
		gomock.Any(),
		uint64(100),
		globalngv1.ResourcePool_LIMITED,
		uint64(100),
	).Return(nil)

	response, err := svc.UpdateResourcePoolInstanceLimit(t.Context(), &globalngv1.UpdateResourcePoolInstanceLimitRequest{
		ResourcePoolId: 100,
		VcpusLimit:     proto.Uint64(100),
		VcpuLimitMode:  globalngv1.ResourcePool_LIMITED,
	})
	require.NoError(t, err)
	require.EqualValues(t, 100, response.GetVcpusLimit())
	require.Equal(t, globalngv1.ResourcePool_LIMITED, response.GetVcpuLimitMode())
}

func TestUpdateResourcePoolInstanceLimitAllowsUnlimitedToLimited(t *testing.T) {
	ctrl := gomock.NewController(t)
	resourcePoolRepo := mock_resourcepool.NewMockRepo(ctrl)
	regionRepo := mock_region.NewMockRepo(ctrl)
	regionalCli := mock_regionalserver.NewMockClient(ctrl)
	svc := &serviceImpl{
		resourcePoolRepo: resourcePoolRepo,
		regionRepo:       regionRepo,
		regionalCli:      regionalCli,
	}
	pool := &entity.ResourcePool{
		ResourcePool: &globalngv1.ResourcePool{
			Id:             100,
			VcpuLimitMode:  globalngv1.ResourcePool_UNLIMITED,
			LifecycleState: globalngv1.ResourcePool_ACTIVE,
		},
		RegionId: 7,
	}
	region := &entity.Region{Region: &globalcommonv1.Region{Id: globalcommonv1.Region_AWS_US_WEST_2}}

	resourcePoolRepo.EXPECT().Load(gomock.Any(), uint64(100), false).Return(pool, nil).Times(2)
	regionRepo.EXPECT().Get(gomock.Any(), uint64(7)).Return(region, nil)
	regionalCli.EXPECT().GetSharedPoolVcpusUsage(
		gomock.Any(),
		region,
		&clusterngv1beta.GetSharedPoolVcpusUsageRequest{SharedPoolId: "100"},
	).Return(&clusterngv1beta.GetSharedPoolVcpusUsageResponse{VcpusUsed: 90}, nil)
	regionalCli.EXPECT().UpdateSharedPoolVcpusLimit(
		gomock.Any(),
		region,
		&clusterngv1beta.UpdateSharedPoolVcpusLimitRequest{SharedPoolId: "100", VcpusLimit: 120},
	).Return(&clusterngv1beta.SharedPool{}, nil)
	resourcePoolRepo.EXPECT().UpdateVcpuPolicy(
		gomock.Any(),
		uint64(100),
		globalngv1.ResourcePool_LIMITED,
		uint64(120),
	).Return(nil)

	response, err := svc.UpdateResourcePoolInstanceLimit(t.Context(), &globalngv1.UpdateResourcePoolInstanceLimitRequest{
		ResourcePoolId: 100,
		VcpusLimit:     proto.Uint64(120),
		VcpuLimitMode:  globalngv1.ResourcePool_LIMITED,
	})

	require.NoError(t, err)
	require.Equal(t, globalngv1.ResourcePool_LIMITED, response.GetVcpuLimitMode())
	require.EqualValues(t, 120, response.GetVcpusLimit())
}

func TestUpdateResourcePoolInstanceLimitUsesFieldScopedPersistence(t *testing.T) {
	ctrl := gomock.NewController(t)
	resourcePoolRepo := mock_resourcepool.NewMockRepo(ctrl)
	eventCommitter := mock_eventbus.NewMockWALCommitter(ctrl)
	pool := &entity.ResourcePool{ResourcePool: &globalngv1.ResourcePool{
		Id:             100,
		Name:           "pool",
		InstanceLimit:  10,
		LifecycleState: globalngv1.ResourcePool_MODIFYING,
	}}
	resourcePoolRepo.EXPECT().Load(gomock.Any(), uint64(100), false).Return(pool, nil)
	resourcePoolRepo.EXPECT().UpdateInstanceLimit(gomock.Any(), uint64(100), uint64(20)).Return(nil)
	eventCommitter.EXPECT().Write(gomock.Any(), gomock.Any(), gomock.Any()).Return(nil).AnyTimes()
	svc := &serviceImpl{resourcePoolRepo: resourcePoolRepo, eventCommitter: eventCommitter}

	response, err := svc.UpdateResourcePoolInstanceLimit(t.Context(), &globalngv1.UpdateResourcePoolInstanceLimitRequest{
		ResourcePoolId: 100,
		InstanceLimit:  20,
	})
	require.NoError(t, err)
	require.EqualValues(t, 20, response.GetInstanceLimit())
	require.Equal(t, globalngv1.ResourcePool_MODIFYING, response.GetLifecycleState())
}

func TestUpdateResourcePoolInstanceLimitAllowsInstanceLimitWhilePaused(t *testing.T) {
	ctrl := gomock.NewController(t)
	resourcePoolRepo := mock_resourcepool.NewMockRepo(ctrl)
	eventCommitter := mock_eventbus.NewMockWALCommitter(ctrl)
	pool := &entity.ResourcePool{ResourcePool: &globalngv1.ResourcePool{
		Id:             100,
		InstanceLimit:  10,
		LifecycleState: globalngv1.ResourcePool_PAUSED,
	}}
	resourcePoolRepo.EXPECT().Load(gomock.Any(), uint64(100), false).Return(pool, nil)
	resourcePoolRepo.EXPECT().UpdateInstanceLimit(gomock.Any(), uint64(100), uint64(20)).Return(nil)
	eventCommitter.EXPECT().Write(gomock.Any(), gomock.Any(), gomock.Any()).Return(nil)
	svc := &serviceImpl{resourcePoolRepo: resourcePoolRepo, eventCommitter: eventCommitter}

	response, err := svc.UpdateResourcePoolInstanceLimit(t.Context(), &globalngv1.UpdateResourcePoolInstanceLimitRequest{
		ResourcePoolId: 100,
		InstanceLimit:  20,
	})

	require.NoError(t, err)
	require.EqualValues(t, 20, response.GetInstanceLimit())
}

func TestUpdateResourcePoolInstanceLimitPersistsUnlimitedPolicy(t *testing.T) {
	ctrl := gomock.NewController(t)
	resourcePoolRepo := mock_resourcepool.NewMockRepo(ctrl)
	regionRepo := mock_region.NewMockRepo(ctrl)
	regionalCli := mock_regionalserver.NewMockClient(ctrl)
	svc := &serviceImpl{
		resourcePoolRepo: resourcePoolRepo,
		regionRepo:       regionRepo,
		regionalCli:      regionalCli,
	}
	pool := &entity.ResourcePool{
		ResourcePool: &globalngv1.ResourcePool{
			Id:             100,
			VcpusLimit:     80,
			VcpuLimitMode:  globalngv1.ResourcePool_LIMITED,
			LifecycleState: globalngv1.ResourcePool_ACTIVE,
		},
		RegionId: 7,
	}
	region := &entity.Region{Region: &globalcommonv1.Region{Id: globalcommonv1.Region_AWS_US_WEST_2}}

	resourcePoolRepo.EXPECT().Load(gomock.Any(), uint64(100), false).Return(pool, nil).Times(2)
	regionRepo.EXPECT().Get(gomock.Any(), uint64(7)).Return(region, nil)
	regionalCli.EXPECT().UpdateSharedPoolVcpusLimit(
		gomock.Any(),
		region,
		&clusterngv1beta.UpdateSharedPoolVcpusLimitRequest{SharedPoolId: "100", VcpusLimit: 0},
	).Return(&clusterngv1beta.SharedPool{}, nil)
	resourcePoolRepo.EXPECT().UpdateVcpuPolicy(
		gomock.Any(),
		uint64(100),
		globalngv1.ResourcePool_UNLIMITED,
		uint64(0),
	).Return(nil)

	response, err := svc.UpdateResourcePoolInstanceLimit(t.Context(), &globalngv1.UpdateResourcePoolInstanceLimitRequest{
		ResourcePoolId: 100,
		VcpuLimitMode:  globalngv1.ResourcePool_UNLIMITED,
	})

	require.NoError(t, err)
	require.Equal(t, globalngv1.ResourcePool_UNLIMITED, response.GetVcpuLimitMode())
	require.Zero(t, response.GetVcpusLimit())
}

func TestUpdateResourcePoolInstanceLimitValidateOnlyDoesNotMutate(t *testing.T) {
	ctrl := gomock.NewController(t)
	resourcePoolRepo := mock_resourcepool.NewMockRepo(ctrl)
	regionRepo := mock_region.NewMockRepo(ctrl)
	regionalCli := mock_regionalserver.NewMockClient(ctrl)
	svc := &serviceImpl{
		resourcePoolRepo: resourcePoolRepo,
		regionRepo:       regionRepo,
		regionalCli:      regionalCli,
	}
	pool := &entity.ResourcePool{
		ResourcePool: &globalngv1.ResourcePool{
			Id:             100,
			InstanceLimit:  10,
			VcpusLimit:     80,
			VcpuLimitMode:  globalngv1.ResourcePool_LIMITED,
			LifecycleState: globalngv1.ResourcePool_ACTIVE,
		},
		RegionId: 7,
	}
	region := &entity.Region{Region: &globalcommonv1.Region{Id: globalcommonv1.Region_AWS_US_WEST_2}}

	resourcePoolRepo.EXPECT().Load(gomock.Any(), uint64(100), false).Return(pool, nil)
	regionRepo.EXPECT().Get(gomock.Any(), uint64(7)).Return(region, nil)
	regionalCli.EXPECT().GetSharedPoolVcpusUsage(
		gomock.Any(),
		region,
		&clusterngv1beta.GetSharedPoolVcpusUsageRequest{SharedPoolId: "100"},
	).Return(&clusterngv1beta.GetSharedPoolVcpusUsageResponse{VcpusUsed: 90}, nil)

	response, err := svc.UpdateResourcePoolInstanceLimit(t.Context(), &globalngv1.UpdateResourcePoolInstanceLimitRequest{
		ResourcePoolId: 100,
		InstanceLimit:  20,
		VcpusLimit:     proto.Uint64(120),
		VcpuLimitMode:  globalngv1.ResourcePool_LIMITED,
		ValidateOnly:   true,
	})

	require.NoError(t, err)
	require.EqualValues(t, 20, response.GetInstanceLimit())
	require.EqualValues(t, 120, response.GetVcpusLimit())
	require.Equal(t, globalngv1.ResourcePool_LIMITED, response.GetVcpuLimitMode())
}

func TestUpdateResourcePoolInstanceLimitRejectsInvalidPolicyTransitions(t *testing.T) {
	tests := []struct {
		name    string
		pool    *globalngv1.ResourcePool
		request *globalngv1.UpdateResourcePoolInstanceLimitRequest
	}{
		{
			name: "limited without value",
			pool: &globalngv1.ResourcePool{
				Id:             100,
				VcpusLimit:     80,
				VcpuLimitMode:  globalngv1.ResourcePool_LIMITED,
				LifecycleState: globalngv1.ResourcePool_ACTIVE,
			},
			request: &globalngv1.UpdateResourcePoolInstanceLimitRequest{
				ResourcePoolId: 100,
				VcpuLimitMode:  globalngv1.ResourcePool_LIMITED,
			},
		},
		{
			name: "limited with zero value",
			pool: &globalngv1.ResourcePool{
				Id:             100,
				VcpusLimit:     80,
				VcpuLimitMode:  globalngv1.ResourcePool_LIMITED,
				LifecycleState: globalngv1.ResourcePool_ACTIVE,
			},
			request: &globalngv1.UpdateResourcePoolInstanceLimitRequest{
				ResourcePoolId: 100,
				VcpusLimit:     proto.Uint64(0),
				VcpuLimitMode:  globalngv1.ResourcePool_LIMITED,
			},
		},
		{
			name: "unlimited with value present",
			pool: &globalngv1.ResourcePool{
				Id:             100,
				VcpusLimit:     80,
				VcpuLimitMode:  globalngv1.ResourcePool_LIMITED,
				LifecycleState: globalngv1.ResourcePool_ACTIVE,
			},
			request: &globalngv1.UpdateResourcePoolInstanceLimitRequest{
				ResourcePoolId: 100,
				VcpusLimit:     proto.Uint64(0),
				VcpuLimitMode:  globalngv1.ResourcePool_UNLIMITED,
			},
		},
		{
			name: "explicit inherited",
			pool: &globalngv1.ResourcePool{
				Id:             100,
				VcpusLimit:     80,
				VcpuLimitMode:  globalngv1.ResourcePool_LIMITED,
				LifecycleState: globalngv1.ResourcePool_ACTIVE,
			},
			request: &globalngv1.UpdateResourcePoolInstanceLimitRequest{
				ResourcePoolId: 100,
				VcpuLimitMode:  globalngv1.ResourcePool_INHERITED,
			},
		},
	}

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

			_, err := svc.UpdateResourcePoolInstanceLimit(t.Context(), tt.request)

			require.Error(t, err)
			require.Equal(t, bizerr.ErrCodeParams, err.ErrCode())
		})
	}
}
