package mgmthttpcli

import (
	"context"
	"fmt"

	"github.com/go-resty/resty/v2"
	apiv1 "github.com/tidbcloud/infra-provider/api/tidbcloud/cluster-agent/v1"
)

type ClusterAgent interface {
	PauseTC(context.Context, *apiv1.PauseTCReq) (*apiv1.PauseTCResp, error)
	ResumeTC(context.Context, *apiv1.ResumeTCReq) (*apiv1.ResumeTCResp, error)
	ListTCs(context.Context) ([]*apiv1.TC, error)
	// DeleteASTSAndResumeTC deletes ASTS(--cascade Foreground) and resumes TCs. It's used for
	// recreating all resources owned by target ASTS, e.g. do shutdown operation for upgrade.
	DeleteASTSAndResumeTC(context.Context, *apiv1.DeleteASTSAndResumeTCReq) (*apiv1.DeleteASTSAndResumeTCResp, error)
	ListNodes(context.Context) (*apiv1.ListNodesResp, error)
	// IsNodeReadyForScaleUpDown checks whether new nodes are ready for scaled up/down components.
	IsNodeReadyForScaleUpDown(context.Context, *apiv1.IsNodeReadyForScaleUpDownReq) (*apiv1.IsNodeReadyForScaleUpDownResp, error)
	Restart(ctx context.Context, req *apiv1.RestartReq) (*apiv1.RestartResp, error)

	ExecuteCommand(ctx context.Context, req *apiv1.ExecuteCommandReq) (*apiv1.ExecuteCommandResp, error)
	GetClusterTopology(ctx context.Context, req *apiv1.GetClusterTopologyReq) (*apiv1.GetClusterTopologyResp, error)
}

type clusterAgent struct {
	core      *resty.Client
	clusterID string
}

func (c *clusterAgent) PauseTC(ctx context.Context, req *apiv1.PauseTCReq) (*apiv1.PauseTCResp, error) {
	path := "/api/v1/clusters/{cluster_id}/tcs:batch_pause"
	var resp apiv1.PauseTCResp

	restyReq := c.core.R().
		SetContext(ctx).
		SetBody(req).
		SetResult(&resp)

	_, err := c.do(resty.MethodPost, path, restyReq)
	return &resp, err
}

func (c *clusterAgent) ResumeTC(ctx context.Context, req *apiv1.ResumeTCReq) (*apiv1.ResumeTCResp, error) {
	path := "/api/v1/clusters/{cluster_id}/tcs:batch_resume"
	var resp apiv1.ResumeTCResp

	restyReq := c.core.R().
		SetContext(ctx).
		SetBody(req).
		SetResult(&resp)

	_, err := c.do(resty.MethodPost, path, restyReq)
	return &resp, err
}

func (c *clusterAgent) ListTCs(ctx context.Context) ([]*apiv1.TC, error) {
	path := "/api/v1/clusters/{cluster_id}/tcs"
	var resp apiv1.ListTCsResp

	restyReq := c.core.R().
		SetContext(ctx).
		SetResult(&resp)

	_, err := c.do(resty.MethodGet, path, restyReq)
	if err != nil {
		return nil, err
	}
	return resp.Tcs, nil
}

func (c *clusterAgent) DeleteASTSAndResumeTC(ctx context.Context, req *apiv1.DeleteASTSAndResumeTCReq) (*apiv1.DeleteASTSAndResumeTCResp, error) {
	path := "/api/v1/clusters/{cluster_id}:delete_asts_and_resume_tc"
	var resp apiv1.DeleteASTSAndResumeTCResp

	restyReq := c.core.R().
		SetContext(ctx).
		SetBody(req).
		SetResult(&resp)

	_, err := c.do(resty.MethodPost, path, restyReq)
	return &resp, err
}

func (c *clusterAgent) ListNodes(ctx context.Context) (*apiv1.ListNodesResp, error) {
	path := "/api/v1/clusters/{cluster_id}/nodes"
	var resp apiv1.ListNodesResp

	restyReq := c.core.R().
		SetContext(ctx).
		SetResult(&resp)

	_, err := c.do(resty.MethodGet, path, restyReq)
	if err != nil {
		return nil, err
	}
	return &resp, nil
}

func (c *clusterAgent) IsNodeReadyForScaleUpDown(ctx context.Context, req *apiv1.IsNodeReadyForScaleUpDownReq) (*apiv1.IsNodeReadyForScaleUpDownResp, error) {
	path := "/api/v1/clusters/{cluster_id}:is_node_ready_for_scale_up_down"
	var resp apiv1.IsNodeReadyForScaleUpDownResp

	restyReq := c.core.R().
		SetContext(ctx).
		SetBody(req).
		SetResult(&resp)
	for _, ct := range req.ComponentTypes {
		restyReq = restyReq.SetQueryParam("componentTypes", ct.String())
	}

	_, err := c.do(resty.MethodGet, path, restyReq)
	if err != nil {
		return nil, err
	}
	return &resp, nil
}

func (c *clusterAgent) Restart(ctx context.Context, req *apiv1.RestartReq) (*apiv1.RestartResp, error) {
	path := "/api/v1/clusters/{cluster_id}:restart"
	var resp apiv1.RestartResp

	restyReq := c.core.R().
		SetContext(ctx).
		SetBody(req).
		SetResult(&resp)

	_, err := c.do(resty.MethodPost, path, restyReq)
	return &resp, err
}

func (c *clusterAgent) ExecuteCommand(ctx context.Context, req *apiv1.ExecuteCommandReq) (*apiv1.ExecuteCommandResp, error) {
	path := "/api/v1/clusters/{cluster_id}:executeCommand"
	var resp apiv1.ExecuteCommandResp

	restyReq := c.core.R().
		SetContext(ctx).
		SetBody(req).
		SetResult(&resp)

	_, err := c.do(resty.MethodPost, path, restyReq)
	return &resp, err
}

func (c *clusterAgent) GetClusterTopology(ctx context.Context, req *apiv1.GetClusterTopologyReq) (*apiv1.GetClusterTopologyResp, error) {
	path := "/api/v1/clusters/{cluster_id}/topology"
	var resp apiv1.GetClusterTopologyResp

	restyReq := c.core.R().
		SetContext(ctx).
		SetBody(req).
		SetResult(&resp)

	_, err := c.do(resty.MethodGet, path, restyReq)
	return &resp, err
}

func (c *clusterAgent) do(method, apiPath string, request *resty.Request) (*resty.Response, error) {
	request = request.SetPathParam("cluster_id", c.clusterID)

	prefix := "/proxy/clusters/{cluster_id}/cluster-agent/tidb{cluster_id}"
	url := prefix + apiPath
	resp, err := request.Execute(method, url)
	if err != nil {
		return nil, fmt.Errorf("call clusterAgent failed: %w", err)
	}

	return resp, nil
}

// func (c *clusterAgent) buildAPIPrefix(ctx context.Context) (string, error) {

// 	if cluster.DeployType == mgmtv1.DeployType_DEPLOY_TYPE_DEDICATED {
// 		return "/proxy/clusters/{cluster_id}/cluster-agent/tidb{cluster_id}", nil
// 	}
// 	if cluster.DeployType == mgmtv1.DeployType_DEPLOY_TYPE_NEXTGEN {
// 		ngCluster := cluster.GetNextGenCluster()
// 		if ngCluster == nil {
// 			return "", fmt.Errorf("nextgen cluster info is nil for cluster %d", cidInt)
// 		}

// 		return fmt.Sprintf("/proxy/nextgen/clusters/{cluster_id}/dataplane/proxy/clusters/{cluster_id}/cluster-agent/tidb%s", ngCluster.SharedPoolId), nil
// 	}

// 	return "", fmt.Errorf("unsupported deploy type %s", cluster.DeployType.String())
// }
