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

import (
	"context"
	"fmt"

	"github.com/go-resty/resty/v2"
	clusterng "github.com/tidbcloud/cluster-service-ng/api/cluster-service-ng/v1beta"
	"google.golang.org/protobuf/proto"
)

type NextGenRegionServer interface {
	GetNextGenDataplane(ctx context.Context, dataplaneID string) (*DataPlane, error)
	GetSharedPool(ctx context.Context, sharedPoolID string) (*SharedPool, error)
	ScaleSharedPool(ctx context.Context, sharedPoolID string, request *ScaleSharedPoolRequest) (*SharedPool, error)
	ScaleSharedPoolQuota(ctx context.Context, sharedPoolID string, request *clusterng.SetSharedPoolComponentQuotaRequest) (*clusterng.Component, error)
	SetSharedPoolPDDeploymentMode(ctx context.Context, request *clusterng.SetSharedPoolPDDeploymentModeRequest) (*clusterng.SharedPool, error)
	SetSharedPoolTiKVRemoteWorkers(ctx context.Context, request *clusterng.SetSharedPoolTiKVRemoteWorkersRequest) (*clusterng.SharedPool, error)
	SetSharedPoolComponentConfigVersion(ctx context.Context, clusterng *clusterng.SetSharedPoolComponentConfigVersionRequest) error
	ApplySemConfig(ctx context.Context, req *clusterng.ApplySemConfigRequest) error
	CreateTiKVGroup(ctx context.Context, req *clusterng.CreateTiKVGroupRequest) (*clusterng.Component, error)
	DeleteTiKVGroup(ctx context.Context, req *DeleteTiKVGroupRequest) error
	CreatePlacementPolicy(ctx context.Context, req *clusterng.CreatePlacementPolicyRequest) (*clusterng.PlacementPolicy, error)
	ListPlacementPolicies(ctx context.Context, req *clusterng.ListPlacementPoliciesRequest) (*clusterng.ListPlacementPoliciesResponse, error)
	GetPlacementPolicy(ctx context.Context, req *clusterng.GetPlacementPolicyRequest) (*clusterng.PlacementPolicy, error)
	UpdatePlacementPolicy(ctx context.Context, req *clusterng.UpdatePlacementPolicyRequest) (*clusterng.PlacementPolicy, error)
	DeletePlacementPolicy(ctx context.Context, req *clusterng.DeletePlacementPolicyRequest) error
	SetCassandraProxy(ctx context.Context, clusterID string, request *clusterng.SetCassandraProxyRequest) (*clusterng.SetCassandraProxyResponse, error)
	GetCassandraProxy(ctx context.Context, clusterID string) (*clusterng.GetCassandraProxyResponse, error)
	SetHPAScaleBehavior(ctx context.Context, req *clusterng.SetHPAScaleBehaviorRequest) error
}

type ngRegionServer struct {
	core          *resty.Client
	logicRegion   string
	cloudProvider string
}

type DeleteTiKVGroupRequest struct {
	SharedPoolId string `json:"sharedPoolId,omitempty"`
	TikvGroupId  string `json:"tikvGroupId,omitempty"`
}

func (r *DeleteTiKVGroupRequest) GetSharedPoolId() string {
	if r == nil {
		return ""
	}
	return r.SharedPoolId
}

func (r *DeleteTiKVGroupRequest) GetTikvGroupId() string {
	if r == nil {
		return ""
	}
	return r.TikvGroupId
}

func (c *ngRegionServer) GetNextGenDataplane(ctx context.Context, dataplaneID string) (*DataPlane, error) {
	result := &DataPlane{}

	restyReq := c.core.R().
		SetContext(ctx).
		SetPathParam("dataplane_id", dataplaneID).
		SetResult(result)

	_, err := c.do(resty.MethodGet, "/dataplane-manager/api/v1/dataplanes/{dataplane_id}", restyReq, nil)
	return result, err
}

func (c *ngRegionServer) GetSharedPool(ctx context.Context, sharedPoolID string) (*SharedPool, error) {
	result := &SharedPool{}
	restyReq := c.core.R().
		SetContext(ctx).
		SetPathParams(map[string]string{
			"shared_pool_id": sharedPoolID,
		}).
		SetResult(result)
	_, err := c.do(resty.MethodGet, "/api/premium/v1/sharedPools/{shared_pool_id}", restyReq, nil)
	if err != nil {
		return nil, err
	}
	return result, nil
}

func (c *ngRegionServer) ScaleSharedPool(ctx context.Context, sharedPoolID string, request *ScaleSharedPoolRequest) (*SharedPool, error) {
	result := &SharedPool{}
	restyReq := c.core.R().
		SetContext(ctx).
		SetPathParams(map[string]string{
			"shared_pool_id": sharedPoolID,
		}).
		SetBody(request).
		SetResult(result)
	_, err := c.do(resty.MethodPost, "/api/premium/v1/sharedPools/{shared_pool_id}:scale", restyReq, nil)
	if err != nil {
		return nil, err
	}

	return result, nil
}

func (c *ngRegionServer) ScaleSharedPoolQuota(ctx context.Context, sharedPoolID string, request *clusterng.SetSharedPoolComponentQuotaRequest) (*clusterng.Component, error) {
	resp := &clusterng.Component{}
	restyReq := c.core.R().
		SetContext(ctx).
		SetPathParams(map[string]string{
			"shared_pool_id": sharedPoolID,
		}).
		SetBody(request).
		SetResult(resp)
	_, err := c.do(resty.MethodPost, "/api/premium/v1/sharedPools/{shared_pool_id}:setComponentQuota", restyReq, resp)
	if err != nil {
		return nil, err
	}

	return resp, nil
}

func (c *ngRegionServer) SetSharedPoolPDDeploymentMode(ctx context.Context, request *clusterng.SetSharedPoolPDDeploymentModeRequest) (*clusterng.SharedPool, error) {
	resp := &clusterng.SharedPool{}
	restyReq := c.core.R().
		SetContext(ctx).
		SetPathParam("shared_pool_id", request.GetSharedPoolId()).
		SetBody(request)
	_, err := c.do(resty.MethodPost, "/api/premium/v1/sharedPools/{shared_pool_id}:setPDDeploymentMode", restyReq, resp)
	if err != nil {
		return nil, err
	}
	return resp, nil
}

func (c *ngRegionServer) SetSharedPoolTiKVRemoteWorkers(ctx context.Context, request *clusterng.SetSharedPoolTiKVRemoteWorkersRequest) (*clusterng.SharedPool, error) {
	resp := &clusterng.SharedPool{}
	restyReq := c.core.R().
		SetContext(ctx).
		SetPathParam("shared_pool_id", request.GetSharedPoolId()).
		SetBody(request)
	_, err := c.do(resty.MethodPost, "/api/premium/v1/sharedPools/{shared_pool_id}:setTiKVRemoteWorkers", restyReq, resp)
	if err != nil {
		return nil, err
	}
	return resp, nil
}

func (c *ngRegionServer) SetSharedPoolComponentConfigVersion(ctx context.Context, req *clusterng.SetSharedPoolComponentConfigVersionRequest) error {
	restyReq := c.core.R().
		SetContext(ctx).
		SetPathParams(map[string]string{
			"shared_pool_id": req.GetSharedPoolId(),
		}).
		SetBody(req)
	_, err := c.do(resty.MethodPost, "/api/premium/v1/sharedPools/{shared_pool_id}:setConfigVersion", restyReq, nil)
	return err
}

func (c *ngRegionServer) ApplySemConfig(ctx context.Context, req *clusterng.ApplySemConfigRequest) error {
	restyReq := c.core.R().
		SetContext(ctx).
		SetPathParams(map[string]string{
			"shared_pool_id": req.GetSharedPoolId(),
		}).
		SetBody(req)
	_, err := c.do(resty.MethodPost, "/api/premium/v1/sharedPools/{shared_pool_id}:applySemConfig", restyReq, nil)
	return err
}

func (c *ngRegionServer) CreateTiKVGroup(ctx context.Context, req *clusterng.CreateTiKVGroupRequest) (*clusterng.Component, error) {
	resp := &clusterng.Component{}
	restyReq := c.core.R().
		SetContext(ctx).
		SetPathParams(map[string]string{
			"shared_pool_id": req.GetSharedPoolId(),
		}).
		SetBody(req)
	_, err := c.do(resty.MethodPost, "/api/premium/v1/sharedPools/{shared_pool_id}/tikvGroups", restyReq, resp)
	if err != nil {
		return nil, err
	}
	return resp, nil
}

func (c *ngRegionServer) DeleteTiKVGroup(ctx context.Context, req *DeleteTiKVGroupRequest) error {
	restyReq := c.core.R().
		SetContext(ctx).
		SetPathParams(map[string]string{
			"shared_pool_id": req.GetSharedPoolId(),
			"tikv_group_id":  req.GetTikvGroupId(),
		})
	_, err := c.do(resty.MethodDelete, "/api/premium/v1/sharedPools/{shared_pool_id}/tikvGroups/{tikv_group_id}", restyReq, nil)
	return err
}

func (c *ngRegionServer) CreatePlacementPolicy(ctx context.Context, req *clusterng.CreatePlacementPolicyRequest) (*clusterng.PlacementPolicy, error) {
	resp := &clusterng.PlacementPolicy{}
	restyReq := c.core.R().
		SetContext(ctx).
		SetPathParams(map[string]string{
			"shared_pool_id": req.GetSharedPoolId(),
		}).
		SetBody(&clusterng.CreatePlacementPolicyRequest{
			ClusterId:   req.GetClusterId(),
			TikvGroupId: req.GetTikvGroupId(),
		})
	_, err := c.do(resty.MethodPost, "/api/premium/v1/sharedPools/{shared_pool_id}/placementPolicies", restyReq, resp)
	if err != nil {
		return nil, err
	}
	return resp, nil
}

func (c *ngRegionServer) ListPlacementPolicies(ctx context.Context, req *clusterng.ListPlacementPoliciesRequest) (*clusterng.ListPlacementPoliciesResponse, error) {
	resp := &clusterng.ListPlacementPoliciesResponse{}
	restyReq := c.core.R().
		SetContext(ctx).
		SetPathParams(map[string]string{
			"shared_pool_id": req.GetSharedPoolId(),
		})
	if req.GetTikvGroupId() != "" {
		restyReq.SetQueryParam("tikvGroupId", req.GetTikvGroupId())
	}
	_, err := c.do(resty.MethodGet, "/api/premium/v1/sharedPools/{shared_pool_id}/placementPolicies", restyReq, resp)
	if err != nil {
		return nil, err
	}
	return resp, nil
}

func (c *ngRegionServer) GetPlacementPolicy(ctx context.Context, req *clusterng.GetPlacementPolicyRequest) (*clusterng.PlacementPolicy, error) {
	resp := &clusterng.PlacementPolicy{}
	restyReq := c.core.R().
		SetContext(ctx).
		SetPathParams(map[string]string{
			"shared_pool_id":      req.GetSharedPoolId(),
			"placement_policy_id": req.GetPlacementPolicyId(),
		})
	_, err := c.do(resty.MethodGet, "/api/premium/v1/sharedPools/{shared_pool_id}/placementPolicies/{placement_policy_id}", restyReq, resp)
	if err != nil {
		return nil, err
	}
	return resp, nil
}

func (c *ngRegionServer) UpdatePlacementPolicy(ctx context.Context, req *clusterng.UpdatePlacementPolicyRequest) (*clusterng.PlacementPolicy, error) {
	resp := &clusterng.PlacementPolicy{}
	restyReq := c.core.R().
		SetContext(ctx).
		SetPathParams(map[string]string{
			"shared_pool_id":      req.GetSharedPoolId(),
			"placement_policy_id": req.GetPlacementPolicyId(),
		}).
		SetBody(&clusterng.UpdatePlacementPolicyRequest{
			TikvGroupId: req.GetTikvGroupId(),
		})
	_, err := c.do(resty.MethodPut, "/api/premium/v1/sharedPools/{shared_pool_id}/placementPolicies/{placement_policy_id}", restyReq, resp)
	if err != nil {
		return nil, err
	}
	return resp, nil
}

func (c *ngRegionServer) DeletePlacementPolicy(ctx context.Context, req *clusterng.DeletePlacementPolicyRequest) error {
	restyReq := c.core.R().
		SetContext(ctx).
		SetPathParams(map[string]string{
			"shared_pool_id":      req.GetSharedPoolId(),
			"placement_policy_id": req.GetPlacementPolicyId(),
		})
	_, err := c.do(resty.MethodDelete, "/api/premium/v1/sharedPools/{shared_pool_id}/placementPolicies/{placement_policy_id}", restyReq, nil)
	return err
}

func (c *ngRegionServer) SetCassandraProxy(ctx context.Context, clusterID string, request *clusterng.SetCassandraProxyRequest) (*clusterng.SetCassandraProxyResponse, error) {
	resp := &clusterng.SetCassandraProxyResponse{}
	restyReq := c.core.R().
		SetContext(ctx).
		SetPathParam("cluster_id", clusterID).
		SetBody(request)
	_, err := c.do(resty.MethodPost, "/cluster-ng-server/api/v1beta/clusters/{cluster_id}:setCassandraProxy", restyReq, resp)
	if err != nil {
		return nil, err
	}
	return resp, nil
}

func (c *ngRegionServer) GetCassandraProxy(ctx context.Context, clusterID string) (*clusterng.GetCassandraProxyResponse, error) {
	resp := &clusterng.GetCassandraProxyResponse{}
	restyReq := c.core.R().
		SetContext(ctx).
		SetPathParam("cluster_id", clusterID)
	_, err := c.do(resty.MethodGet, "/cluster-ng-server/api/v1beta/clusters/{cluster_id}/cassandraProxy", restyReq, resp)
	if err != nil {
		return nil, err
	}
	return resp, nil
}

func (c *ngRegionServer) SetHPAScaleBehavior(ctx context.Context, req *clusterng.SetHPAScaleBehaviorRequest) error {
	restyReq := c.core.R().
		SetContext(ctx).
		SetPathParams(map[string]string{
			"shared_pool_id": req.GetSharedPoolId(),
		}).
		SetBody(req)
	_, err := c.do(resty.MethodPost, "/api/premium/v1/sharedPools/{shared_pool_id}:setHPAScaleBehavior", restyReq, nil)
	return err
}

// to use proto unmarshal, use output field, to use custom struct and json unmarshal, set output to nil and just use resty SetResult
func (c *ngRegionServer) do(method, apiPath string, request *resty.Request, output interface{}) (*resty.Response, error) {
	prefix := fmt.Sprintf("proxy/nextgen/regions/%s/cloudProviders/%s", c.logicRegion, c.cloudProvider)
	url := prefix + apiPath
	resp, err := request.Execute(method, url)
	if err != nil {
		return nil, err
	}

	if output != nil {
		err = defaultProtojsonUnmarshalOptions.Unmarshal(resp.Body(), output.(proto.Message))
		if err != nil {
			return resp, fmt.Errorf("unmarshal response failed: %s", err)
		}
	}

	return resp, nil
}
