package console

import (
	"encoding/json"
	"time"

	httptransport "github.com/go-openapi/runtime/client"
	"github.com/go-openapi/strfmt"
	"github.com/samber/lo"
	"github.com/stretchr/testify/require"
	"github.com/stretchr/testify/suite"

	"github.com/tidbcloud/tidb-management-service/functional-test/pkg/serverlessapi/console/client"
	"github.com/tidbcloud/tidb-management-service/functional-test/pkg/serverlessapi/console/client/serverless_service"
	"github.com/tidbcloud/tidb-management-service/functional-test/pkg/serverlessapi/console/model"
)

type Cli struct {
	*client.TiDBCloudServerlessOpenAPI

	su *suite.Suite
}

func NewClient(su *suite.Suite, host, bearerToken string) (*Cli, error) {
	trans := httptransport.New(host, "", []string{"https"})
	// Apply the bearer token
	trans.DefaultAuthentication = httptransport.BearerToken(bearerToken)
	return &Cli{su: su, TiDBCloudServerlessOpenAPI: client.New(trans, strfmt.Default)}, nil
}

func (cli *Cli) RespJson(resp any) string {
	b, err := json.Marshal(resp)
	require.NoErrorf(cli.su.T(), err, "marshal error: %s", err)
	return string(b)
}

func (cli *Cli) ListClustersAllowErr(
	orgId, projectId, pageToken string,
	pageSize int32,
) (*model.TidbCloudOpenApiserverlessv1beta1ListClustersResponse, error) {
	params := serverless_service.NewServerlessServiceListClustersParams()
	params.SetProjectID(projectId)
	params.SetOrgID(orgId)

	if pageToken != "" {
		params.SetPageToken(&pageToken)
	}

	if pageSize != 0 {
		params.SetPageSize(&pageSize)
	}

	resp, err := cli.ServerlessService.ServerlessServiceListClusters(params)
	if err != nil {
		return nil, err
	}

	require.Falsef(cli.su.T(), resp == nil || resp.Payload == nil, "invalid response: %s", cli.RespJson(resp))
	require.NotEmptyf(cli.su.T(), resp.Payload, "invalid response: %s", cli.RespJson(resp))

	return resp.Payload, nil
}

func (cli *Cli) ListClusters(
	orgId, projectId, pageToken string,
	pageSize int32,
) []*model.TidbCloudOpenApiserverlessv1beta1Cluster {
	resp, err := cli.ListClustersAllowErr(orgId, projectId, pageToken, pageSize)
	require.NoError(cli.su.T(), err)
	return resp.Clusters
}

func (cli *Cli) ListProviders(orgId string) []*model.Commonv1beta1Region {
	params := serverless_service.NewServerlessServiceListRegionsParams()
	params.SetOrgID(orgId)

	resp, err := cli.ServerlessService.ServerlessServiceListRegions(params)

	require.NoError(cli.su.T(), err)
	require.Falsef(cli.su.T(), resp == nil || resp.Payload == nil, "invalid response: %s", cli.RespJson(resp))
	require.GreaterOrEqualf(
		cli.su.T(),
		len(resp.Payload.Regions),
		1,
		"length cloud providers response less then 1: %s",
		resp.String(),
	)

	return resp.Payload.Regions
}

func (cli *Cli) CreateClusterAllowErr(
	orgId, projectId string,
	cluster *model.TidbCloudOpenApiserverlessv1beta1Cluster,
) (*model.TidbCloudOpenApiserverlessv1beta1Cluster, error) {
	params := serverless_service.NewServerlessServiceCreateClusterParams()
	params.SetProjectID(projectId)
	params.SetOrgID(orgId)
	params.SetCluster(cluster)

	resp, err := cli.ServerlessService.ServerlessServiceCreateCluster(params)
	if err != nil {
		return nil, err
	}
	require.Falsef(cli.su.T(), resp == nil || resp.Payload == nil, "invalid response: %s", cli.RespJson(resp))
	require.NotEmptyf(cli.su.T(), resp.Payload, "invalid response: %s", cli.RespJson(resp))

	return resp.Payload, nil
}

func (cli *Cli) CreateCluster(orgId, projectId string, cluster *model.TidbCloudOpenApiserverlessv1beta1Cluster) string {
	resp, err := cli.CreateClusterAllowErr(orgId, projectId, cluster)
	require.NoError(cli.su.T(), err)
	require.NotEmptyf(cli.su.T(), &resp.ClusterID, "cluster id not found: %s", cli.RespJson(resp))

	return resp.ClusterID
}

func (cli *Cli) PollClusterStatus(orgId, projectId, clusterID string) *model.Commonv1beta1ClusterState {
	cluster, err := cli.GetClusterAllowErr(orgId, projectId, clusterID)
	require.NoError(cli.su.T(), err)
	require.NotEmptyf(cli.su.T(), *cluster.State, "cluster status not found: %s", clusterID)
	return cluster.State
}

func (cli *Cli) GetClusterAllowErr(
	orgId, projectId, clusterID string,
) (*model.TidbCloudOpenApiserverlessv1beta1Cluster, error) {
	time.Sleep(1 * time.Second)
	params := serverless_service.NewServerlessServiceGetClusterParams()
	params.SetProjectID(projectId)
	params.SetOrgID(orgId)
	params.SetClusterID(clusterID)

	resp, err := cli.ServerlessService.ServerlessServiceGetCluster(params)
	if err != nil {
		return nil, err
	}

	require.Falsef(cli.su.T(), resp == nil || resp.Payload == nil, "invalid response: %s", cli.RespJson(resp))
	require.NotEmptyf(cli.su.T(), resp.Payload, "invalid response: %s", cli.RespJson(resp))

	return resp.Payload, nil
}

func (cli *Cli) GetCluster(orgId, projectId, clusterID string) *model.TidbCloudOpenApiserverlessv1beta1Cluster {
	resp, err := cli.GetClusterAllowErr(orgId, projectId, clusterID)
	require.NoError(cli.su.T(), err)
	return resp
}

func (cli *Cli) UpdateClusterAllowErr(
	orgId, projectId, clusterId, updateMask string,
	cluster *serverless_service.ServerlessServicePartialUpdateClusterParamsBodyCluster,
) (*model.TidbCloudOpenApiserverlessv1beta1Cluster, error) {
	params := serverless_service.NewServerlessServicePartialUpdateClusterParams()
	params.SetProjectID(projectId)
	params.SetOrgID(orgId)
	params.SetClusterID(clusterId)
	params.SetBody(
		serverless_service.ServerlessServicePartialUpdateClusterBody{Cluster: cluster, UpdateMask: updateMask},
	)

	resp, err := cli.ServerlessService.ServerlessServicePartialUpdateCluster(params)
	if err != nil {
		return nil, err
	}

	require.Falsef(cli.su.T(), resp == nil || resp.Payload == nil, "invalid response: %s", cli.RespJson(resp))
	require.NotEmptyf(cli.su.T(), resp.Payload, "invalid response: %s", cli.RespJson(resp))

	return resp.Payload, nil
}

func (cli *Cli) UpdateCluster(
	orgId, projectId, clusterId, updateMask string,
	cluster *serverless_service.ServerlessServicePartialUpdateClusterParamsBodyCluster,
) *model.TidbCloudOpenApiserverlessv1beta1Cluster {
	resp, err := cli.UpdateClusterAllowErr(orgId, projectId, clusterId, updateMask, cluster)
	require.NoError(cli.su.T(), err)
	return resp
}

func (cli *Cli) DeleteClusterAllowErr(
	orgId, projectId, clusterID string,
) (*model.TidbCloudOpenApiserverlessv1beta1Cluster, error) {
	params := serverless_service.NewServerlessServiceDeleteClusterParams()
	params.SetProjectID(projectId)
	params.SetOrgID(orgId)
	params.SetClusterID(clusterID)

	resp, err := cli.ServerlessService.ServerlessServiceDeleteCluster(params)
	if err != nil {
		return nil, err
	}

	require.Falsef(cli.su.T(), resp == nil || resp.Payload == nil, "invalid response: %s", cli.RespJson(resp))
	return resp.Payload, nil
}

func (cli *Cli) EnsureDeleteCluster(orgId, projectId, clusterName string) {
	clusters := cli.ListClusters(orgId, projectId, "", 100)

	cluster, ok := lo.Find(clusters, func(c *model.TidbCloudOpenApiserverlessv1beta1Cluster) bool {
		return c != nil && c.ClusterID != "" && *c.DisplayName == clusterName
	})

	if !ok {
		// cluster not found, nothing to do
		return
	}
	_, err := cli.DeleteClusterAllowErr(orgId, projectId, cluster.ClusterID)
	require.NoError(cli.su.T(), err)
}
