package api

import (
	"encoding/json"
	"errors"
	"fmt"
	"strings"
	"time"

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

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

type Cli struct {
	*client.TiDBCloudServerlessOpenAPI

	su *suite.Suite
}

func NewClient(su *suite.Suite, host, apiKey string) (*Cli, error) {
	trans := httptransport.New(host, client.DefaultBasePath, []string{"https"})
	// Apply the api-key
	if len(strings.Split(apiKey, ":")) != 2 {
		return nil, errors.New("invalid API Key")
	}
	trans.Transport = &digest.Transport{
		Username: strings.Split(apiKey, ":")[0],
		Password: strings.Split(apiKey, ":")[1],
	}
	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(
	filter, pageToken string,
	pageSize int32,
) (*model.TidbCloudOpenApiserverlessv1beta1ListClustersResponse, error) {
	params := serverless_service.NewServerlessServiceListClustersParams()
	if filter != "" {
		params.SetFilter(&filter)
	}

	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(
	filter, pageToken string,
	pageSize int32,
) []*model.TidbCloudOpenApiserverlessv1beta1Cluster {
	resp, err := cli.ListClustersAllowErr(filter, pageToken, pageSize)
	require.NoError(cli.su.T(), err)
	return resp.Clusters
}

func (cli *Cli) ListProviders() []*model.Commonv1beta1Region {
	params := serverless_service.NewServerlessServiceListRegionsParams()
	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(
	cluster *model.TidbCloudOpenApiserverlessv1beta1Cluster,
) (*model.TidbCloudOpenApiserverlessv1beta1Cluster, error) {
	time.Sleep(1 * time.Second)
	params := serverless_service.NewServerlessServiceCreateClusterParams()
	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(cluster *model.TidbCloudOpenApiserverlessv1beta1Cluster) string {
	resp, err := cli.CreateClusterAllowErr(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(clusterID string) *model.Commonv1beta1ClusterState {
	cluster, err := cli.GetClusterAllowErr(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(clusterID string) (*model.TidbCloudOpenApiserverlessv1beta1Cluster, error) {
	params := serverless_service.NewServerlessServiceGetClusterParams()
	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(clusterID string) *model.TidbCloudOpenApiserverlessv1beta1Cluster {
	resp, err := cli.GetClusterAllowErr(clusterID)
	require.NoError(cli.su.T(), err)
	return resp
}

func (cli *Cli) UpdateClusterAllowErr(
	clusterId, updateMask string,
	cluster *serverless_service.ServerlessServicePartialUpdateClusterParamsBodyCluster,
) (*model.TidbCloudOpenApiserverlessv1beta1Cluster, error) {
	params := serverless_service.NewServerlessServicePartialUpdateClusterParams()
	params.SetClusterClusterID(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(
	clusterId, updateMask string,
	cluster *serverless_service.ServerlessServicePartialUpdateClusterParamsBodyCluster,
) *model.TidbCloudOpenApiserverlessv1beta1Cluster {
	resp, err := cli.UpdateClusterAllowErr(clusterId, updateMask, cluster)
	require.NoError(cli.su.T(), err)
	return resp
}

func (cli *Cli) DeleteClusterAllowErr(clusterID string) (*model.TidbCloudOpenApiserverlessv1beta1Cluster, error) {
	params := serverless_service.NewServerlessServiceDeleteClusterParams()
	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(prjID, clusterName string) {
	clusters := cli.ListClusters(fmt.Sprintf("projectId = %s", prjID), "", 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(cluster.ClusterID)
	require.NoError(cli.su.T(), err)
}
