package clustergateway

import (
	"context"
	"encoding/json"
	"fmt"
	"net/http"
	"strconv"

	"github.com/go-resty/resty/v2"
	clusteragentv1 "github.com/tidbcloud/cluster-service-ng/api/tidbcloud/cluster-agent/v1"
	"github.com/tidbcloud/cluster-service-ng/domain/entity"
	"google.golang.org/protobuf/encoding/protojson"
)

func (c *clusterGatewayImpl) ClusterAgentOpsRestart(ctx context.Context, sharedPool *entity.SharedPool, restartReq *clusteragentv1.RestartReq) error {
	resp, err := c.callClusterAgent(ctx, sharedPool, http.MethodPost, operationMap["ClusterAgentOpsRestart"], func(req *resty.Request) *resty.Request {
		return req.SetPathParam(dataplaneNamespaceParameter, sharedPool.GetDataPlaneNamespace()).
			SetPathParam(sharedPoolIdParameter, sharedPool.GetId()).
			SetBody(restartReq)
	})
	if err != nil {
		return fmt.Errorf("call ClusterAgentOps failed: %w", err)
	}
	if resp.StatusCode() != http.StatusOK {
		return fmt.Errorf("ClusterAgentOpsRestart response not expected: %s", resp.String())
	}
	var out clusteragentv1.RestartResp
	if err = json.Unmarshal(resp.Body(), &out); err != nil {
		return fmt.Errorf("unmarshal ClusterAgentOpsRestart response failed: %w, body: %s", err, string(resp.Body()))
	}

	if out.Message != "success" {
		return fmt.Errorf("ClusterAgentOpsRestart failed: %s", out.Message)
	}

	return nil
}

func (c *clusterGatewayImpl) GetClusterTopology(ctx context.Context, sharedPool *entity.SharedPool, request *clusteragentv1.GetClusterTopologyReq) (*clusteragentv1.GetClusterTopologyResp, error) {
	resp, err := c.callClusterAgent(ctx, sharedPool, http.MethodGet, operationMap["GetClusterTopology"], func(req *resty.Request) *resty.Request {
		return req.SetPathParam(dataplaneNamespaceParameter, sharedPool.GetDataPlaneNamespace()).
			SetPathParam(sharedPoolIdParameter, sharedPool.GetId()).
			SetQueryParam(includeTikvUsageParameter, strconv.FormatBool(request.IncludeTikvUsage))
	})
	if err != nil {
		return nil, fmt.Errorf("call GetClusterTopology failed: %w", err)
	}
	if resp.StatusCode() != http.StatusOK {
		return nil, fmt.Errorf("GetClusterTopology response not expected: %s", resp.String())
	}
	var out clusteragentv1.GetClusterTopologyResp
	// Notice: since this file has a unit64 fileds that cannot be unmarshaled by json.Unmarshal, we use protojson.Unmarshal instead
	if err = protojson.Unmarshal(resp.Body(), &out); err != nil {
		return nil, fmt.Errorf("unmarshal GetClusterTopology response failed: %w, body: %s", err, string(resp.Body()))
	}
	return &out, nil
}

func (c *clusterGatewayImpl) ListPods(ctx context.Context, sharedPool *entity.SharedPool) (*clusteragentv1.ListPodsResp, error) {
	resp, err := c.callClusterAgent(ctx, sharedPool, http.MethodGet, operationMap["ListPods"], func(req *resty.Request) *resty.Request {
		return req.SetPathParam(dataplaneNamespaceParameter, sharedPool.GetDataPlaneNamespace()).
			SetPathParam(sharedPoolIdParameter, sharedPool.GetId())
	})
	if err != nil {
		return nil, fmt.Errorf("call ListPods failed: %w", err)
	}
	if resp.StatusCode() != http.StatusOK {
		return nil, fmt.Errorf("ListPods response not expected: %s", resp.String())
	}
	var out clusteragentv1.ListPodsResp
	if err = protojson.Unmarshal(resp.Body(), &out); err != nil {
		return nil, fmt.Errorf("unmarshal ListPods response failed: %w, body: %s", err, string(resp.Body()))
	}
	return &out, nil
}

func (c *clusterGatewayImpl) DeleteCloudAdmin(ctx context.Context, sharedPool *entity.SharedPool, request *clusteragentv1.DeleteCloudAdminReq) (*clusteragentv1.DeleteCloudAdminResp, error) {
	resp, err := c.callClusterAgent(ctx, sharedPool, http.MethodDelete, operationMap["DeleteCloudAdmin"], func(req *resty.Request) *resty.Request {
		return req.SetPathParam(dataplaneNamespaceParameter, sharedPool.GetDataPlaneNamespace()).
			SetPathParam(keySpaceParameter, request.ClusterId)
	})
	if err != nil {
		return nil, fmt.Errorf("call DeleteCloudAdmin failed: %w", err)
	}
	if resp.StatusCode() != http.StatusOK {
		return nil, fmt.Errorf("DeleteCloudAdmin response not expected: %s", resp.String())
	}
	var out clusteragentv1.DeleteCloudAdminResp
	if err = json.Unmarshal(resp.Body(), &out); err != nil {
		return nil, fmt.Errorf("unmarshal DeleteCloudAdmin response failed: %w, body: %s", err, string(resp.Body()))
	}
	if out.Message != "success" {
		return nil, fmt.Errorf("DeleteCloudAdmin failed: %s", out.Message)
	}
	return &out, nil
}

func (c *clusterGatewayImpl) GetComponentResourceMetrics(ctx context.Context, sharedPool *entity.SharedPool, componentType, componentId string) (*clusteragentv1.GetComponentResourceMetricsResp, error) {
	resp, err := c.callClusterAgent(ctx, sharedPool, http.MethodGet, operationMap["GetComponentResourceMetrics"], func(req *resty.Request) *resty.Request {
		return req.SetPathParam(dataplaneNamespaceParameter, sharedPool.GetDataPlaneNamespace()).
			SetPathParam(sharedPoolIdParameter, sharedPool.GetId()).
			SetQueryParam("component_type", componentType).
			SetQueryParam("component_id", componentId)
	})
	if err != nil {
		return nil, fmt.Errorf("call GetComponentResourceMetrics failed: %w", err)
	}
	if resp.StatusCode() != http.StatusOK {
		return nil, fmt.Errorf("GetComponentResourceMetrics response not expected: %s", resp.String())
	}
	var out clusteragentv1.GetComponentResourceMetricsResp
	if err = protojson.Unmarshal(resp.Body(), &out); err != nil {
		return nil, fmt.Errorf("unmarshal GetComponentResourceMetrics response failed: %w, body: %s", err, string(resp.Body()))
	}
	return &out, nil
}

func (c *clusterGatewayImpl) GetComponentRawMetrics(ctx context.Context, sharedPool *entity.SharedPool, componentType, podName string) ([]byte, error) {
	resp, err := c.callClusterAgentNoRespLog(ctx, sharedPool, http.MethodGet, operationMap["GetComponentRawMetrics"], func(req *resty.Request) *resty.Request {
		return req.SetPathParam(dataplaneNamespaceParameter, sharedPool.GetDataPlaneNamespace()).
			SetPathParam(sharedPoolIdParameter, sharedPool.GetId()).
			SetPathParam(componentTypeParameter, componentType).
			SetPathParam(podNameParameter, podName)
	})
	if err != nil {
		return nil, fmt.Errorf("call GetComponentRawMetrics failed: %w", err)
	}
	if resp.StatusCode() != http.StatusOK {
		return nil, fmt.Errorf("GetComponentRawMetrics response not expected: status=%d body=%s", resp.StatusCode(), resp.String())
	}
	return resp.Body(), nil
}

func (c *clusterGatewayImpl) GetComponentNodeMetrics(ctx context.Context, sharedPool *entity.SharedPool, componentType, podName string) ([]byte, error) {
	resp, err := c.callClusterAgentNoRespLog(ctx, sharedPool, http.MethodGet, operationMap["GetComponentNodeMetrics"], func(req *resty.Request) *resty.Request {
		return req.SetPathParam(dataplaneNamespaceParameter, sharedPool.GetDataPlaneNamespace()).
			SetPathParam(sharedPoolIdParameter, sharedPool.GetId()).
			SetPathParam(componentTypeParameter, componentType).
			SetPathParam(podNameParameter, podName)
	})
	if err != nil {
		return nil, fmt.Errorf("call GetComponentNodeMetrics failed: %w", err)
	}
	if resp.StatusCode() != http.StatusOK {
		return nil, fmt.Errorf("GetComponentNodeMetrics response not expected: status=%d body=%s", resp.StatusCode(), resp.String())
	}
	return resp.Body(), nil
}

func (c *clusterGatewayImpl) MarkTiProxyPodUnhealthyLegacy(ctx context.Context, sharedPool *entity.SharedPool, request *clusteragentv1.MarkTiProxyPodUnhealthyLegacyReq) error {
	resp, err := c.callClusterAgent(ctx, sharedPool, http.MethodPost, operationMap["MarkTiProxyPodUnhealthyLegacy"], func(req *resty.Request) *resty.Request {
		return req.SetPathParam(dataplaneNamespaceParameter, sharedPool.GetDataPlaneNamespace()).
			SetPathParam(sharedPoolIdParameter, sharedPool.GetId()).
			SetBody(request)
	})
	if err != nil {
		return fmt.Errorf("call MarkTiProxyPodUnhealthyLegacy failed: %w", err)
	}
	if resp.StatusCode() != http.StatusOK {
		return fmt.Errorf("MarkTiProxyPodUnhealthyLegacy response not expected: %s", resp.String())
	}
	return nil
}

func (c *clusterGatewayImpl) ProbeTiProxyHealthAPI(ctx context.Context, sharedPool *entity.SharedPool, request *clusteragentv1.ProbeTiProxyHealthAPIReq) (*clusteragentv1.ProbeTiProxyHealthAPIResp, error) {
	resp, err := c.callClusterAgent(ctx, sharedPool, http.MethodPost, operationMap["ProbeTiProxyHealthAPI"], func(req *resty.Request) *resty.Request {
		return req.SetPathParam(dataplaneNamespaceParameter, sharedPool.GetDataPlaneNamespace()).
			SetPathParam(sharedPoolIdParameter, sharedPool.GetId()).
			SetBody(request)
	})
	if err != nil {
		return nil, fmt.Errorf("call ProbeTiProxyHealthAPI failed: %w", err)
	}
	if resp.StatusCode() != http.StatusOK {
		return nil, fmt.Errorf("ProbeTiProxyHealthAPI response not expected: %s", resp.String())
	}
	var out clusteragentv1.ProbeTiProxyHealthAPIResp
	if err = protojson.Unmarshal(resp.Body(), &out); err != nil {
		return nil, fmt.Errorf("unmarshal ProbeTiProxyHealthAPI response failed: %w, body: %s", err, string(resp.Body()))
	}
	return &out, nil
}
