package bffapi

import (
	"context"
	"fmt"
	"strings"
	"testing"

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

	"github.com/tidbcloud/tidb-management-service/functional-test/pkg/bffapi"
	bffclient "github.com/tidbcloud/tidb-management-service/functional-test/pkg/bffapi/client"
	"github.com/tidbcloud/tidb-management-service/functional-test/pkg/dedicatedapi/console"
	openapiconsolecli "github.com/tidbcloud/tidb-management-service/functional-test/pkg/dedicatedapi/console/client"
	"github.com/tidbcloud/tidb-management-service/functional-test/pkg/tidbcloudlogin"
	"github.com/tidbcloud/tidb-management-service/utilities/randstr"
)

const pageSize = 100

func TestEstimatePrice(t *testing.T) {
	testSuite := &TestEstimatePriceSuite{
		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"),
		clusterNamePrefix: viper.GetString("cluster_name_prefix"),

		provider: "aws",
		region:   "us-west-2",
	}

	testSuite.clusterName = testSuite.clusterNamePrefix + randstr.GenWithCharset(4, randstr.AlphabetCharset)

	suite.Run(t, testSuite)
}

type TestEstimatePriceSuite struct {
	suite.Suite

	*tidbcloudlogin.WebApiLoginContext

	orgID             string
	prjID             string
	clusterNamePrefix string

	provider    string
	region      string
	clusterName string

	bffApi         *bffapi.Cli
	openapiConsole *console.Cli

	// Test EstimatePrice for min/max/default node specs.

	tidbNodeSpecMin *openapiconsolecli.V1beta1NodeSpec
	tikvNodeSpecMin *openapiconsolecli.V1beta1NodeSpec

	tidbNodeSpecMax    *openapiconsolecli.V1beta1NodeSpec
	tikvNodeSpecMax    *openapiconsolecli.V1beta1NodeSpec
	tiflashNodeSpecMax *openapiconsolecli.V1beta1NodeSpec

	tidbNodeSpecDefault *openapiconsolecli.V1beta1NodeSpec
	tikvNodeSpecDefault *openapiconsolecli.V1beta1NodeSpec
}

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

	su.bffApi, err = bffapi.NewClient(&su.Suite, su.Host, token)
	su.Require().NoError(err)
	su.Require().NotNil(su.bffApi)

	su.openapiConsole, err = console.NewClient(&su.Suite, su.Host, token)
	su.Require().NoError(err)
	su.Require().NotNil(su.bffApi)
}

func (su *TestEstimatePriceSuite) Test_02_QueryNodeProfiles() {
	ctx := context.Background()

	req := su.openapiConsole.RegionServiceAPI.RegionServiceListNodeSpecs(ctx, fmt.Sprintf("%s-%s", su.provider, su.region)).
		OrgId(su.orgID).
		ProjectId(su.prjID).
		PageSize(pageSize)
	resp, _, err := req.Execute()
	su.Require().NoError(err)

	tidbNodeSpecs := lo.Filter(resp.NodeSpecs, func(item openapiconsolecli.V1beta1NodeSpec, _ int) bool {
		return item.GetComponentType() == openapiconsolecli.DEDICATEDV1BETA1COMPONENTTYPE_TIDB
	})
	su.Require().NotEmpty(tidbNodeSpecs)
	tikvNodeSpecs := lo.Filter(resp.NodeSpecs, func(item openapiconsolecli.V1beta1NodeSpec, _ int) bool {
		return item.GetComponentType() == openapiconsolecli.DEDICATEDV1BETA1COMPONENTTYPE_TIKV
	})
	su.Require().NotEmpty(tikvNodeSpecs)
	tiflashNodeSpecs := lo.Filter(resp.NodeSpecs, func(item openapiconsolecli.V1beta1NodeSpec, _ int) bool {
		return item.GetComponentType() == openapiconsolecli.DEDICATEDV1BETA1COMPONENTTYPE_TIFLASH
	})
	su.Require().NotEmpty(tiflashNodeSpecs)

	// Find the minimal node spec.
	su.tidbNodeSpecMin = lo.ToPtr(
		lo.MinBy(tidbNodeSpecs, func(a openapiconsolecli.V1beta1NodeSpec, b openapiconsolecli.V1beta1NodeSpec) bool {
			return a.GetVCpu() < b.GetVCpu()
		}),
	)
	su.tikvNodeSpecMin = lo.ToPtr(
		lo.MinBy(tikvNodeSpecs, func(a openapiconsolecli.V1beta1NodeSpec, b openapiconsolecli.V1beta1NodeSpec) bool {
			return a.GetVCpu() < b.GetVCpu()
		}),
	)

	// Find the maximal node spec.
	su.tidbNodeSpecMax = lo.ToPtr(
		lo.MaxBy(tidbNodeSpecs, func(a openapiconsolecli.V1beta1NodeSpec, b openapiconsolecli.V1beta1NodeSpec) bool {
			return a.GetVCpu() < b.GetVCpu()
		}),
	)
	su.tikvNodeSpecMax = lo.ToPtr(
		lo.MaxBy(tikvNodeSpecs, func(a openapiconsolecli.V1beta1NodeSpec, b openapiconsolecli.V1beta1NodeSpec) bool {
			return a.GetVCpu() < b.GetVCpu()
		}),
	)
	su.tiflashNodeSpecMax = lo.ToPtr(
		lo.MaxBy(tiflashNodeSpecs, func(a openapiconsolecli.V1beta1NodeSpec, b openapiconsolecli.V1beta1NodeSpec) bool {
			return a.GetVCpu() < b.GetVCpu()
		}),
	)

	// Find the default node spec.
	tidbNodeSpecDefaults := lo.Filter(tidbNodeSpecs, func(item openapiconsolecli.V1beta1NodeSpec, _ int) bool {
		return item.GetComponentType() == openapiconsolecli.DEDICATEDV1BETA1COMPONENTTYPE_TIDB && item.GetDefault() &&
			strings.EqualFold(item.GetVersion(), "v1")
	})
	su.Require().Len(tidbNodeSpecDefaults, 1)
	su.tidbNodeSpecDefault = &tidbNodeSpecDefaults[0]
	tikvNodeSpecDefaults := lo.Filter(tikvNodeSpecs, func(item openapiconsolecli.V1beta1NodeSpec, _ int) bool {
		return item.GetComponentType() == openapiconsolecli.DEDICATEDV1BETA1COMPONENTTYPE_TIKV && item.GetDefault() &&
			strings.EqualFold(item.GetVersion(), "v1")
	})
	su.Require().Len(tikvNodeSpecDefaults, 1)
	su.tikvNodeSpecDefault = &tikvNodeSpecDefaults[0]
}

func (su *TestEstimatePriceSuite) Test_03_EstimateClusterPrice() {
	ctx := context.Background()

	// Test min node specs.
	req := su.bffApi.ClusterServiceAPI.ClusterServiceEstimatePrice(ctx).
		OrgId(su.orgID).
		ProjectId(su.prjID).
		Body(bffclient.ClusterManagementv1beta1EstimatePriceRequest{
			Duration:  lo.ToPtr("3600s"),
			ProjectId: lo.ToPtr(su.prjID),
			RegionId:  fmt.Sprintf("%s-%s", su.provider, su.region),
			TidbNodeSetting: &bffclient.V1beta1ClusterTidbNodeSetting{
				NodeSpecKey: lo.ToPtr(su.tidbNodeSpecMin.GetNodeSpecKey()),
				// 3 total nodes.
				TidbNodeGroups: []bffclient.Dedicatedv1beta1TidbNodeGroup{
					{
						NodeCount: 1,
					},
					{
						NodeCount: 1,
					},
				},
			},
			TikvNodeSetting: &bffclient.V1beta1ClusterStorageNodeSetting{
				NodeSpecKey:   su.tikvNodeSpecMin.GetNodeSpecKey(),
				NodeCount:     3,
				StorageSizeGi: 200,
			},
		})
	resp, _, err := req.Execute()
	su.Require().NoError(err)
	lo.ContainsBy(resp.GetPrices(), func(item bffclient.EstimatePriceResponsePrice) bool {
		return item.GetComponentType() == bffclient.DEDICATEDV1BETA1COMPONENTTYPE_TIDB
	})
}
