package mgmthttpcli

import (
	"context"
	"fmt"

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

type NextGenClusterAgent interface {
	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)
	PrepareReplaceNode(ctx context.Context, req *PrepareReplaceNodeReq) (*ReplaceNodeResp, error)
	GetReplaceNodeOperation(ctx context.Context, req *ReplaceNodeOperationReq) (*ReplaceNodeResp, error)
	CommitReplaceNode(ctx context.Context, req *ReplaceNodeOperationReq) (*ReplaceNodeResp, error)
	AbortReplaceNode(ctx context.Context, req *ReplaceNodeOperationReq) (*ReplaceNodeResp, error)
}

type ngClusterAgent struct {
	core         *resty.Client
	clusterID    string
	sharedPoolID string
}

const (
	nextGenClusterAgentProxyPrefix    = "/proxy/nextgen/clusters/{cluster_id}/dataplane/proxy/clusters/{cluster_id}/cluster-agent/tidb{shared_pool_id}"
	nextGenSharedPoolAgentProxyPrefix = "/proxy/nextgen/resourcePools/{shared_pool_id}/dataplane/proxy/sharedPools/{shared_pool_id}/cluster-agent/tidb{shared_pool_id}"
)

type PrepareReplaceNodeReq struct {
	OperationID    string `json:"operationId,omitempty"`
	ResourcePoolID string `json:"-"`
	PodName        string `json:"podName,omitempty"`
	InstanceID     string `json:"instanceId,omitempty"`
	Force          bool   `json:"force,omitempty"`
}

type ReplaceNodeOperationReq struct {
	OperationID    string `json:"operationId,omitempty"`
	ResourcePoolID string `json:"-"`
}

type ReplaceNodeResp struct {
	Phase           string `json:"phase,omitempty"`
	Message         string `json:"message,omitempty"`
	PodName         string `json:"podName,omitempty"`
	InstanceID      string `json:"instanceId,omitempty"`
	ComponentKind   string `json:"componentKind,omitempty"`
	OldNode         string `json:"oldNode,omitempty"`
	WarmUpPod       string `json:"warmUpPod,omitempty"`
	WarmUpNode      string `json:"warmUpNode,omitempty"`
	ReplacementPod  string `json:"replacementPod,omitempty"`
	ReplacementNode string `json:"replacementNode,omitempty"`
}

func (c *ngClusterAgent) Restart(ctx context.Context, req *apiv1.RestartReq) (*apiv1.RestartResp, error) {
	path := "/api/v1/clusters/{shared_pool_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 *ngClusterAgent) ExecuteCommand(ctx context.Context, req *apiv1.ExecuteCommandReq) (*apiv1.ExecuteCommandResp, error) {
	path := "/api/v1/clusters/{shared_pool_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 *ngClusterAgent) GetClusterTopology(ctx context.Context, req *apiv1.GetClusterTopologyReq) (*apiv1.GetClusterTopologyResp, error) {
	path := "/api/v1/clusters/{shared_pool_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 *ngClusterAgent) PrepareReplaceNode(ctx context.Context, req *PrepareReplaceNodeReq) (*ReplaceNodeResp, error) {
	return c.replaceNode(ctx, "/api/v1/clusters/{shared_pool_id}:prepareReplaceNode", req)
}

func (c *ngClusterAgent) GetReplaceNodeOperation(ctx context.Context, req *ReplaceNodeOperationReq) (*ReplaceNodeResp, error) {
	return c.replaceNode(ctx, "/api/v1/clusters/{shared_pool_id}:getReplaceNodeOperation", req)
}

func (c *ngClusterAgent) CommitReplaceNode(ctx context.Context, req *ReplaceNodeOperationReq) (*ReplaceNodeResp, error) {
	return c.replaceNode(ctx, "/api/v1/clusters/{shared_pool_id}:commitReplaceNode", req)
}

func (c *ngClusterAgent) AbortReplaceNode(ctx context.Context, req *ReplaceNodeOperationReq) (*ReplaceNodeResp, error) {
	return c.replaceNode(ctx, "/api/v1/clusters/{shared_pool_id}:abortReplaceNode", req)
}

func (c *ngClusterAgent) replaceNode(ctx context.Context, path string, req any) (*ReplaceNodeResp, error) {
	var resp ReplaceNodeResp

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

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

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

	prefix := nextGenClusterAgentProxyPrefix
	if c.clusterID == "" {
		prefix = nextGenSharedPoolAgentProxyPrefix
	}
	url := prefix + apiPath
	resp, err := request.Execute(method, url)
	if err != nil {
		return nil, fmt.Errorf("call ngClusterAgent failed: %w", err)
	}

	return resp, nil
}
