package webapiserverless

import (
	"math/rand/v2"
	"testing"
	"time"

	"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/tidbcloudlogin"
	"github.com/tidbcloud/tidb-management-service/utilities/randstr"
)

func TestServerlessListClusters(t *testing.T) {
	testSuite := &TestServerlessListClustersSuite{
		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"),
		noPermitOrgID:     viper.GetString("no_permit_org_id"),
		prjID:             viper.GetString("prj_id"),
		clusterNamePrefix: viper.GetString("cluster_name_prefix"),

		nodeProfileName: "Shared0",
		provider:        "aws",
		region:          "us-east-1",
		spendLimit:      1000, // 10 USD

		clusterID2Name: map[string]string{},
		deletedID2Name: map[string]string{},
	}

	if testSuite.noPermitOrgID == "" {
		testSuite.noPermitOrgID = "60204"
	}

	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 TestServerlessListClustersSuite struct {
	suite.Suite

	*tidbcloudlogin.WebApiLoginContext

	orgID             string
	prjID             string
	clusterNamePrefix string
	noPermitOrgID     string

	nodeProfileName string
	provider        string
	region          string
	components      []*model.CentralClusterComponent
	spendLimit      int64

	cmCli *coreportalapi.Cli

	createdIDs     []string
	clusterID2Name map[string]string
	deletedID2Name map[string]string
}

func (su *TestServerlessListClustersSuite) TearDownSuite() {
	// TODO: this is slow coz there is a `ListClusters` call in `EnsureDeleteCluster`
	for _, clusterName := range su.clusterID2Name {
		su.cmCli.EnsureDeleteCluster(su.orgID, su.prjID, clusterName)
	}
}

func (su *TestServerlessListClustersSuite) 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)
}

func (su *TestServerlessListClustersSuite) Test_02_ListClusters_NoPermit() {
	resp := su.cmCli.ListClustersAllowBizErr(su.noPermitOrgID, su.prjID, 1, 100, nil)
	su.Require().Equalf("49900003", resp.BaseResp.ErrCode, "The error code should be 49900003")
}

func (su *TestServerlessListClustersSuite) Test_03_CreateClusters() {
	cnt := 3
	for range cnt {
		su.createCluster("")
	}
}

func (su *TestServerlessListClustersSuite) Test_04_ListClusters() {
	// OrgID and PrjID are must provided by the requirement of API path `/api/v1/orgs/{org_id}/projects/{project_id}/clusters`
	// su.checkListClusters(su.orgID, "")
	// su.checkListClusters("", su.prjID)
	su.checkListClusters(su.orgID, su.prjID)
}

func (su *TestServerlessListClustersSuite) Test_05_DeleteCluster() {
	deletingClusterID := su.createdIDs[rand.IntN(len(su.createdIDs))]
	deletingClusterName, ok := su.clusterID2Name[deletingClusterID]
	su.Require().True(ok)
	su.cmCli.EnsureDeleteCluster(su.orgID, su.prjID, deletingClusterName)
	su.deletedID2Name[deletingClusterID] = deletingClusterName
}

func (su *TestServerlessListClustersSuite) Test_06_ListClustersAfterDeleting() {
	// OrgID and PrjID are must provided by the requirement of API path `/api/v1/orgs/{org_id}/projects/{project_id}/clusters`
	// su.checkListClusters(su.orgID, "")
	// su.checkListClusters("", su.prjID)
	su.checkListClusters(su.orgID, su.prjID)
}

func (su *TestServerlessListClustersSuite) createCluster(clusterName string) {
	if clusterName == "" {
		clusterName = su.clusterNamePrefix + randstr.GenWithCharset(4, randstr.AlphabetCharset)
	}

	clusterID := *su.cmCli.CreateCluster(su.orgID, su.prjID, su.provider, su.region, clusterName, 4000,
		su.components, true, su.spendLimit)
	su.Require().NotEmpty(clusterID)
	su.clusterID2Name[clusterID] = clusterName
	su.createdIDs = append(su.createdIDs, clusterID)

	// timeout: 10 minutes (600 seconds)
	for range 60 {
		status := su.cmCli.PollClusterStatus(su.orgID, su.prjID, clusterID)
		if *status == model.CentralClusterStatusNORMAL {
			break
		}
		su.T().Logf("cluster status: %s", *status)
		time.Sleep(10 * time.Second)
	}
}

func (su *TestServerlessListClustersSuite) checkListClusters(orgID, prjID string) {
	clusters := su.cmCli.ListClusters(orgID, prjID, 1, 100)

	var listed []*model.CentralCluster
	for _, cluster := range clusters {
		if cluster.DevTierType == nil ||
			*cluster.DevTierType != model.CentralClusterDevTierClusterTypeDEVTIERSERVERLESS {
			continue
		}
		su.Require().NotNil(cluster.ID)
		clusterName, ok := su.clusterID2Name[*cluster.ID]
		if !ok {
			continue
		}
		_, ok = su.deletedID2Name[*cluster.ID]
		if ok {
			continue
		}
		listed = append(listed, cluster)
		su.Require().NotNil(cluster.Name)
		su.Require().Equal(clusterName, *cluster.Name)

		// TODO: check values after not-nil judgements

		su.Require().NotNil(cluster.Capacity)
		su.Require().NotNil(cluster.ClusterComponents)
		su.Require().NotNil(cluster.ClusterType)
		su.Require().NotNil(cluster.CreateProgress)
		su.Require().NotNil(cluster.CreatedAt)
		su.Require().NotNil(cluster.CreatedBy)
		// Skip this coz the user from token and user-email might not be the same one
		// su.Require().Equal(su.WebApiLoginContext.UserEmail, *cluster.CreatedBy)
		su.Require().NotNil(cluster.DisplayStatus)
		su.Require().NotNil(cluster.Endpoint)
		su.Require().False(cluster.HasSetPwd)
		su.Require().True(cluster.IsDevTier)
		su.Require().NotNil(cluster.PrivateEndpoint)
		su.Require().NotNil(cluster.PrometheusReady)
		su.Require().NotNil(cluster.Provider)
		su.Require().Equal(su.provider, *cluster.Provider)
		su.Require().NotNil(cluster.Region)
		su.Require().Equal(su.region, *cluster.Region)
		su.Require().NotNil(cluster.RegionName)
		su.Require().NotNil(cluster.Restored)
		su.Require().NotNil(cluster.SecondaryStatus)
		su.Require().NotNil(cluster.Status)
		su.Require().NotNil(cluster.Version)
		su.Require().NotNil(cluster.VpcCidrBlock)
		su.Require().NotNil(cluster.WriteStatus)
	}

	su.Require().Equal(len(listed), len(su.clusterID2Name)-len(su.deletedID2Name))
}
