//go:generate ${GOBIN}/mockgen -destination cluster_server_mock.go -package=mgmthttpcli ${GO_MODULE}/pkg/infra/clients/mgmthttpcli ClusterServer
package mgmthttpcli

import (
	"context"
	"fmt"

	apiv1 "github.com/tidbcloud/infra-provider/api/tidbcloud/cluster-server/v1"

	"github.com/go-resty/resty/v2"
	"google.golang.org/protobuf/proto"
)

type ClusterServer interface {
	CreateOperation(context.Context, *apiv1.CreateOperationRequest) (*apiv1.CreateOperationResponse, error)
	GetOperation(context.Context, *apiv1.GetOperationRequest) (*apiv1.Operation, error)
	GetCluster(context.Context, *apiv1.GetClusterRequest) (*apiv1.Cluster, error)
	CancelScaleCluster(context.Context, *apiv1.CancelScaleClusterRequest) error
	GetControlplaneK8SResource(context.Context, *apiv1.GetK8SResourceRequest) (*apiv1.GetK8SResourceResponse, error)
	ListClusterResourceMetas(context.Context, *apiv1.ListClusterResourceMetasRequest) (*apiv1.ListClusterResourceMetasResponse, error)
	ListClusterResources(context.Context, *apiv1.ListClusterResourcesRequest) (*apiv1.ListClusterResourcesResponse, error)
}

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

func (c *clusterServer) CreateOperation(ctx context.Context, req *apiv1.CreateOperationRequest) (*apiv1.CreateOperationResponse, error) {
	path := "/api/v1/clusters/{cluster_id}/operations"
	var resp apiv1.CreateOperationResponse

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

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

func (c *clusterServer) GetOperation(ctx context.Context, req *apiv1.GetOperationRequest) (*apiv1.Operation, error) {
	path := "/api/v1/clusters/{cluster_id}/operations/{operation_id}"
	var resp apiv1.Operation

	restyReq := c.core.R().
		SetContext(ctx).
		SetPathParam("operation_id", req.OperationId).
		SetBody(proto.Message(req)).
		SetResult(&resp)

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

func (c *clusterServer) GetCluster(ctx context.Context, req *apiv1.GetClusterRequest) (*apiv1.Cluster, error) {
	path := "/api/v1/clusters/{cluster_id}"
	var resp apiv1.Cluster

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

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

func (c *clusterServer) GetControlplaneK8SResource(ctx context.Context, req *apiv1.GetK8SResourceRequest) (*apiv1.GetK8SResourceResponse, error) {
	path := "/api/v1/clusters/{cluster_id}/proxy/controlplane"
	var resp apiv1.GetK8SResourceResponse

	restyReq := c.core.R().
		SetContext(ctx).
		SetQueryParam("namespace", req.Namespace).
		SetQueryParam("resource_kind", req.ResourceKind).
		SetQueryParam("resource_name", req.ResourceName).
		SetBody(proto.Message(req)).
		SetResult(&resp)

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

func (c *clusterServer) CancelScaleCluster(ctx context.Context, req *apiv1.CancelScaleClusterRequest) error {
	path := "/api/v1/clusters/{cluster_id}:cancelScale"
	restyReq := c.core.R().
		SetContext(ctx).
		SetBody(proto.Message(req))
	_, err := c.do(resty.MethodPost, path, restyReq)
	return err
}

func (c *clusterServer) ListClusterResourceMetas(ctx context.Context, _ *apiv1.ListClusterResourceMetasRequest) (*apiv1.ListClusterResourceMetasResponse, error) {
	path := "/api/v1/clusters/{cluster_id}/resourceMetas"
	var resp apiv1.ListClusterResourceMetasResponse

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

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

func (c *clusterServer) ListClusterResources(ctx context.Context, _ *apiv1.ListClusterResourcesRequest) (*apiv1.ListClusterResourcesResponse, error) {
	path := "/api/v1/clusters/{cluster_id}/resources"
	var resp apiv1.ListClusterResourcesResponse

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

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

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

	prefix := "/proxy/clusters/{cluster_id}/cluster-server"
	url := prefix + apiPath

	resp, err := request.Execute(method, url)
	if err != nil {
		return nil, fmt.Errorf("call clusterServer failed: %w", err)
	}

	return resp, nil
}
