package rcuquota

import (
	"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/pkg/config"
)

func TestBuildRcuQuotaForTenantSupportsEssentialV2Default(t *testing.T) {
	repo := &rcuQuotaRepoImpl{
		defaultRcuQuota: globalngv1.RcuQuota{
			InstanceMaxRcu:             100000,
			TotalMaxRcu:                200000,
			BurstyRatio:                1.2,
			IdleResourcePoolTtlInHours: 4,
			VcpusLimitPerResourcePool:  64,
			MinimalRcuBaseline:         5000,
			MinimalRcuCoefficient:      0.25,
			ScaleDownCooldownMinutes:   720,
		},
		defaultRcuQuotaOverrides: map[string]*globalngv1.RcuQuota{},
	}

	quota, err := repo.buildRcuQuotaForTenant(
		t.Context(),
		123,
		globalcommonv1.ServicePlan_Essential_V2.String(),
	)

	require.Nil(t, err)
	assert.Equal(t, uint64(123), quota.GetOrganizationId())
	assert.Equal(t, globalcommonv1.ServicePlan_Essential_V2, quota.GetServicePlan())
	assert.Equal(t, uint64(100000), quota.GetInstanceMaxRcu())
	assert.Equal(t, uint64(200000), quota.GetTotalMaxRcu())
	assert.InEpsilon(t, float32(1.2), quota.GetBurstyRatio(), 1e-6)
	assert.Equal(t, uint64(4), quota.GetIdleResourcePoolTtlInHours())
	assert.Equal(t, uint64(64), quota.GetVcpusLimitPerResourcePool())
	assert.Equal(t, uint64(5000), quota.GetMinimalRcuBaseline())
	assert.InEpsilon(t, float32(0.25), quota.GetMinimalRcuCoefficient(), 1e-6)
	assert.Equal(t, uint64(720), quota.GetScaleDownCooldownMinutes())
}

func TestNewRcuQuotaRepoInitializesDefaultQuotaFromConfig(t *testing.T) {
	repo, ok := NewRcuQuotaRepo(nil, &config.Config{
		BizConfig: config.BizConfig{
			MaxRCUQuotaPerCluster:      110000,
			TotalMaxRCU:                220000,
			BurstyRatio:                1.3,
			IdleResourcePoolTtlInHours: 5,
			VcpusLimitPerResourcePool:  96,
			MinimalRcuBaseline:         6000,
			MinimalRcuCoefficient:      0.3,
			ScaleDownCooldownMinutes:   360,
		},
	}).(*rcuQuotaRepoImpl)
	require.True(t, ok)

	assert.Equal(t, uint64(110000), repo.defaultRcuQuota.GetInstanceMaxRcu())
	assert.Equal(t, uint64(220000), repo.defaultRcuQuota.GetTotalMaxRcu())
	assert.InEpsilon(t, float32(1.3), repo.defaultRcuQuota.GetBurstyRatio(), 1e-6)
	assert.Equal(t, uint64(5), repo.defaultRcuQuota.GetIdleResourcePoolTtlInHours())
	assert.Equal(t, uint64(96), repo.defaultRcuQuota.GetVcpusLimitPerResourcePool())
	assert.Equal(t, uint64(6000), repo.defaultRcuQuota.GetMinimalRcuBaseline())
	assert.InEpsilon(t, float32(0.3), repo.defaultRcuQuota.GetMinimalRcuCoefficient(), 1e-6)
	assert.Equal(t, uint64(360), repo.defaultRcuQuota.GetScaleDownCooldownMinutes())
}

func TestBuildRcuQuotaForTenantUsesServicePlanDefaultQuota(t *testing.T) {
	repo, ok := NewRcuQuotaRepo(nil, &config.Config{
		BizConfig: config.BizConfig{
			MaxRCUQuotaPerCluster:      100000,
			TotalMaxRCU:                200000,
			BurstyRatio:                1.0,
			IdleResourcePoolTtlInHours: 4,
			VcpusLimitPerResourcePool:  64,
			MinimalRcuBaseline:         5000,
			MinimalRcuCoefficient:      0.25,
			ScaleDownCooldownMinutes:   720,
			RcuQuotaConfigs: map[string]config.RcuQuotaConfig{
				globalcommonv1.ServicePlan_Essential_V2.String(): {
					MaxRCUQuotaPerCluster:      ptr(uint(20000)),
					TotalMaxRCU:                ptr(uint(50000)),
					BurstyRatio:                ptr(float32(1.2)),
					IdleResourcePoolTtlInHours: ptr(uint(8)),
					VcpusLimitPerResourcePool:  ptr(uint(0)),
					MinimalRcuBaseline:         ptr(uint32(1000)),
					MinimalRcuCoefficient:      ptr(float32(0.1)),
					ScaleDownCooldownMinutes:   ptr(uint(120)),
				},
			},
		},
	}).(*rcuQuotaRepoImpl)
	require.True(t, ok)

	quota, err := repo.buildRcuQuotaForTenant(
		t.Context(),
		123,
		globalcommonv1.ServicePlan_Essential_V2.String(),
	)

	require.Nil(t, err)
	assert.Equal(t, globalcommonv1.ServicePlan_Essential_V2, quota.GetServicePlan())
	assert.Equal(t, uint64(20000), quota.GetInstanceMaxRcu())
	assert.Equal(t, uint64(50000), quota.GetTotalMaxRcu())
	assert.InEpsilon(t, float32(1.2), quota.GetBurstyRatio(), 1e-6)
	assert.Equal(t, uint64(8), quota.GetIdleResourcePoolTtlInHours())
	assert.Equal(t, uint64(0), quota.GetVcpusLimitPerResourcePool())
	assert.Equal(t, uint64(1000), quota.GetMinimalRcuBaseline())
	assert.InEpsilon(t, float32(0.1), quota.GetMinimalRcuCoefficient(), 1e-6)
	assert.Equal(t, uint64(120), quota.GetScaleDownCooldownMinutes())
}

func TestBuildRcuQuotaForTenantFallsBackToGlobalDefaultQuota(t *testing.T) {
	repo, ok := NewRcuQuotaRepo(nil, &config.Config{
		BizConfig: config.BizConfig{
			MaxRCUQuotaPerCluster:      100000,
			TotalMaxRCU:                200000,
			BurstyRatio:                1.0,
			IdleResourcePoolTtlInHours: 4,
			VcpusLimitPerResourcePool:  64,
			MinimalRcuBaseline:         5000,
			MinimalRcuCoefficient:      0.25,
			ScaleDownCooldownMinutes:   720,
			RcuQuotaConfigs: map[string]config.RcuQuotaConfig{
				globalcommonv1.ServicePlan_Essential_V2.String(): {
					MaxRCUQuotaPerCluster: ptr(uint(20000)),
				},
			},
		},
	}).(*rcuQuotaRepoImpl)
	require.True(t, ok)

	quota, err := repo.buildRcuQuotaForTenant(
		t.Context(),
		123,
		globalcommonv1.ServicePlan_Premium.String(),
	)

	require.Nil(t, err)
	assert.Equal(t, globalcommonv1.ServicePlan_Premium, quota.GetServicePlan())
	assert.Equal(t, uint64(100000), quota.GetInstanceMaxRcu())
	assert.Equal(t, uint64(200000), quota.GetTotalMaxRcu())
	assert.InEpsilon(t, float32(1.0), quota.GetBurstyRatio(), 1e-6)
	assert.Equal(t, uint64(64), quota.GetVcpusLimitPerResourcePool())
}

func TestBuildRcuQuotaForTenantUsesServicePlanDefaultQuotaWhenConfigKeyLowercased(t *testing.T) {
	repo, ok := NewRcuQuotaRepo(nil, &config.Config{
		BizConfig: config.BizConfig{
			MaxRCUQuotaPerCluster:      100000,
			TotalMaxRCU:                200000,
			BurstyRatio:                1.0,
			IdleResourcePoolTtlInHours: 4,
			VcpusLimitPerResourcePool:  64,
			MinimalRcuBaseline:         5000,
			MinimalRcuCoefficient:      0.25,
			ScaleDownCooldownMinutes:   720,
			RcuQuotaConfigs: map[string]config.RcuQuotaConfig{
				"essential_v2": {
					MinimalRcuBaseline:    ptr(uint32(2000)),
					MinimalRcuCoefficient: ptr(float32(0.1)),
				},
			},
		},
	}).(*rcuQuotaRepoImpl)
	require.True(t, ok)

	quota, err := repo.buildRcuQuotaForTenant(
		t.Context(),
		123,
		globalcommonv1.ServicePlan_Essential_V2.String(),
	)

	require.Nil(t, err)
	assert.Equal(t, globalcommonv1.ServicePlan_Essential_V2, quota.GetServicePlan())
	assert.Equal(t, uint64(2000), quota.GetMinimalRcuBaseline())
	assert.InEpsilon(t, float32(0.1), quota.GetMinimalRcuCoefficient(), 1e-6)
}

func ptr[T any](v T) *T {
	return &v
}
