package webapiserverless

import (
	"strings"
	"testing"

	"github.com/samber/lo"
	"github.com/spf13/viper"
	"github.com/stretchr/testify/suite"

	"github.com/tidbcloud/tidb-management-service/functional-test/pkg/coreportalapi"
	"github.com/tidbcloud/tidb-management-service/functional-test/pkg/coreportalapi/model"
	"github.com/tidbcloud/tidb-management-service/functional-test/pkg/serverlesssvc"
	"github.com/tidbcloud/tidb-management-service/functional-test/pkg/tidbcloudlogin"
	"github.com/tidbcloud/tidb-management-service/utilities/randstr"
)

func TestServerlessCreateFailures(t *testing.T) {
	testSuite := &ServerlessCreateFailuresSuite{
		WebApiLoginContext: &tidbcloudlogin.WebApiLoginContext{
			Host:              viper.GetString("host"),
			Auth0Domain:       viper.GetString("auth0_domain"),
			Auth0ClientID:     viper.GetString("auth0_client_id"),
			Auth0ClientSecret: viper.GetString("auth0_client_secret"),
			UserEmail:         viper.GetString("user_email"),
			BearerToken:       viper.GetString("bearer_token"),
		},

		orgID:             viper.GetString("org_id"),
		prjID:             viper.GetString("prj_id"),
		fakePrjID:         "99999",
		clusterNamePrefix: viper.GetString("cluster_name_prefix"),

		nodeProfileName: "Shared0",
		provider:        "aws",
		region:          "us-east-1",
		fakeRegion:      "af-south-1",
		password:        randstr.GenWithCharset(16, randstr.AlphabetCharset),
		spendLimit:      1000, // 10 USD
		freeSpendLimit:  0,
		maxFreeClusters: 5,
	}

	testSuite.components = []*model.CentralClusterComponent{
		{
			ComponentType:   lo.ToPtr(model.CentralComponentTypeCOMPONENTTYPETIDB),
			NodeProfileName: lo.ToPtr(testSuite.nodeProfileName),
			Replicas:        lo.ToPtr(int32(1)),
			StorageSizeGi:   lo.ToPtr(int32(0)),
		},
		{
			ComponentType:   lo.ToPtr(model.CentralComponentTypeCOMPONENTTYPETIKV),
			NodeProfileName: lo.ToPtr(testSuite.nodeProfileName),
			Replicas:        lo.ToPtr(int32(1)),
			StorageSizeGi:   lo.ToPtr(int32(1)),
		},
		{
			ComponentType:   lo.ToPtr(model.CentralComponentTypeCOMPONENTTYPETIFLASH),
			NodeProfileName: lo.ToPtr(testSuite.nodeProfileName),
			Replicas:        lo.ToPtr(int32(1)),
			StorageSizeGi:   lo.ToPtr(int32(1)),
		},
	}

	suite.Run(t, testSuite)
}

type ServerlessCreateFailuresSuite struct {
	suite.Suite

	*tidbcloudlogin.WebApiLoginContext

	orgID             string
	prjID             string
	fakePrjID         string
	clusterNamePrefix string

	nodeProfileName string
	provider        string
	region          string
	fakeRegion      string
	password        string
	clusterNames    []string
	components      []*model.CentralClusterComponent
	spendLimit      int64
	freeSpendLimit  int64
	maxFreeClusters int

	serverlessCli *serverlesssvc.Cli
	cmCli         *coreportalapi.Cli
}

func (su *ServerlessCreateFailuresSuite) TearDownSuite() {
	for _, clusterName := range su.clusterNames {
		su.cmCli.EnsureDeleteCluster(su.orgID, su.prjID, clusterName)
	}
}

func (su *ServerlessCreateFailuresSuite) Test_01_LoginAndInitializeClient() {
	token, err := su.LoginAndGetToken(su.orgID)
	su.Require().NoError(err)

	su.cmCli, err = coreportalapi.NewClient(&su.Suite, su.Host, token)
	su.Require().NoError(err)
	su.Require().NotNil(su.cmCli)
	su.serverlessCli, err = serverlesssvc.NewClient(&su.Suite, su.Host, token)
	su.Require().NoError(err)
	su.Require().NotNil(su.serverlessCli)
}

func (su *ServerlessCreateFailuresSuite) Test_02_CreateFreeClusters_Until_ReachLimit() {
	for ; su.maxFreeClusters > 0; su.maxFreeClusters-- {
		clusterName := su.clusterNamePrefix + randstr.GenWithCharset(4, randstr.AlphabetCharset)
		su.clusterNames = append(su.clusterNames, clusterName)

		resp := su.cmCli.CreateClusterAllowBizErr(
			su.orgID,
			su.prjID,
			su.provider,
			su.region,
			clusterName,
			4000,
			su.components,
			true,
			su.freeSpendLimit,
		)

		if resp.BaseResp != nil && resp.BaseResp.ErrCode != "0" {
			// If and only if the error code is "61200005", we can stop creation.
			// Other error codes should be treated as failure.
			su.Require().Equalf("61200005", resp.BaseResp.ErrCode, "The error code should not be 61200005")
			break
		}
		// Free clusters creation should be successful.
		su.Require().
			Falsef(resp.BaseResp != nil && resp.BaseResp.ErrCode != "0", "invalid response: %s", su.cmCli.RespJson(resp))
		su.Require().NotEmptyf(*resp.ID, "cluster id not found: %s", su.cmCli.RespJson(resp))
	}

	// Dev environment does not have max free cluster limit.
	if !strings.Contains(su.Host, "nightly") {
		su.Require().
			GreaterOrEqual(su.maxFreeClusters, 0, "The number of free clusters should not be greater than or equal %d", su.maxFreeClusters)
	}
}

func (su *ServerlessCreateFailuresSuite) Test_03_CreateClusters_NoPermissionProject() {
	clusterName := su.clusterNamePrefix + randstr.GenWithCharset(4, randstr.AlphabetCharset)
	su.clusterNames = append(su.clusterNames, clusterName)

	resp := su.cmCli.CreateClusterAllowBizErr(
		su.orgID,
		su.fakePrjID,
		su.provider,
		su.region,
		clusterName,
		4000,
		su.components,
		true,
		su.spendLimit,
	)

	su.Require().Equalf("49900003", resp.BaseResp.ErrCode, "The error code should be 49900003")
}

func (su *ServerlessCreateFailuresSuite) Test_04_CreateClusters_InvalidName() {
	invalidClusterName := su.clusterNamePrefix + randstr.GenWithCharset(4, randstr.AlphabetCharset) + "-"
	su.clusterNames = append(su.clusterNames, invalidClusterName)

	resp := su.cmCli.CreateClusterAllowBizErr(
		su.orgID,
		su.prjID,
		su.provider,
		su.region,
		invalidClusterName,
		4000,
		su.components,
		true,
		su.spendLimit,
	)

	su.Require().NotEmpty(resp.BaseResp.ErrCode, "The error code should not be empty")
	su.Require().Equalf("49900001", resp.BaseResp.ErrCode, "The error code should be 49900001")
}

func (su *ServerlessCreateFailuresSuite) Test_05_CreateClusters_DuplicatedName() {
	clusterName := su.clusterNamePrefix + randstr.GenWithCharset(4, randstr.AlphabetCharset)
	su.clusterNames = append(su.clusterNames, clusterName)

	resp1 := su.cmCli.CreateClusterAllowBizErr(
		su.orgID,
		su.prjID,
		su.provider,
		su.region,
		clusterName,
		4000,
		su.components,
		true,
		su.spendLimit,
	)

	su.Require().
		Falsef(resp1.BaseResp != nil && resp1.BaseResp.ErrCode != "0", "invalid response: %s", su.cmCli.RespJson(resp1))
	su.Require().NotEmptyf(*resp1.ID, "cluster id not found: %s", su.cmCli.RespJson(resp1))

	resp2 := su.cmCli.CreateClusterAllowBizErr(
		su.orgID,
		su.prjID,
		su.provider,
		su.region,
		clusterName,
		4000,
		su.components,
		true,
		su.spendLimit,
	)

	su.Require().NotEmpty(resp2.BaseResp.ErrCode, "The error code should not be empty")
	su.Require().
		Truef(resp2.BaseResp.ErrCode == "49900005" || resp2.BaseResp.ErrCode == "60100001", "The error code should be 49900005(new mgmt return) or 60100001(old cm return)")
}

func (su *ServerlessCreateFailuresSuite) Test_06_CreateClusters_InvalidRegion() {
	clusterName := su.clusterNamePrefix + randstr.GenWithCharset(4, randstr.AlphabetCharset)
	su.clusterNames = append(su.clusterNames, clusterName)

	resp := su.cmCli.CreateClusterAllowBizErr(
		su.orgID,
		su.prjID,
		su.provider,
		su.fakeRegion,
		clusterName,
		4000,
		su.components,
		true,
		su.spendLimit,
	)

	su.Require().NotEmpty(resp.BaseResp.ErrCode, "The error code should not be empty")
	su.Require().Equalf("49900001", resp.BaseResp.ErrCode, "The error code should be 49900001")
}
