package mgmthttpcli

import (
	"context"
	"fmt"
	"net/url"
	"strconv"

	"github.com/go-resty/resty/v2"
	ccproto "github.com/tidbcloud/config-center/proto/config_center"
	"google.golang.org/protobuf/proto"
)

type ConfigCenterNgProxy interface {
	GetClusterMeta(ctx context.Context, premiumClusterID string, req *ccproto.GetClusterMetaRequest) (*ccproto.ClusterMeta, error)
	UpdateClusterMeta(ctx context.Context, premiumClusterID string, req *ccproto.UpdateClusterMetaRequest) (*ccproto.UpdateClusterMetaResponse, error)
	AutoUpdateClusterMeta(ctx context.Context, premiumClusterID string, req *ccproto.AutoUpdateClusterMetaRequest) (*ccproto.AutoUpdateClusterMetaResponse, error)
	GetClusterMeta2(ctx context.Context, logicalRegion, cloud string, req *ccproto.GetClusterMetaRequest) (*ccproto.ClusterMeta, error)
	UpdateClusterMeta2(ctx context.Context, logicalRegion, cloud string, req *ccproto.UpdateClusterMetaRequest) (*ccproto.UpdateClusterMetaResponse, error)
	SearchClusterComponents(ctx context.Context, logicalRegion, cloud string, req *ccproto.SearchClusterComponentsRequest) (*ccproto.SearchClusterComponentsResponse, error)
}

type configCenter struct {
	core *resty.Client
}

func (c *configCenter) GetClusterMeta(ctx context.Context, premiumClusterID string, req *ccproto.GetClusterMetaRequest) (*ccproto.ClusterMeta, error) {
	result := &ccproto.ClusterMeta{}
	restyReq := c.core.R().
		SetContext(ctx).
		SetPathParams(map[string]string{
			"premium_cluster": premiumClusterID,
			"cluster_type":    req.GetClusterType().String(),
			"cluster_id":      req.GetClusterId(),
		}).
		SetQueryParam("mainComponent", req.GetMainComponent())

	_, err := c.do(resty.MethodGet, "/proxy/nextgen/clusters/{premium_cluster}/config-center/cluster-types/{cluster_type}/clusters/{cluster_id}/meta", restyReq, result)
	if err != nil {
		return nil, err
	}

	return result, nil
}

func (c *configCenter) UpdateClusterMeta(ctx context.Context, premiumClusterID string, req *ccproto.UpdateClusterMetaRequest) (*ccproto.UpdateClusterMetaResponse, error) {
	result := &ccproto.UpdateClusterMetaResponse{}
	restyReq := c.core.R().
		SetContext(ctx).
		SetPathParams(map[string]string{
			"premium_cluster": premiumClusterID,
			"cluster_type":    req.GetClusterType().String(),
			"cluster_id":      req.GetClusterId(),
		}).
		SetBody(proto.Message(req)).
		SetResult(result)

	_, err := c.do(resty.MethodPatch, "/proxy/nextgen/clusters/{premium_cluster}/config-center/cluster-types/{cluster_type}/clusters/{cluster_id}/meta", restyReq, result)
	if err != nil {
		return nil, err
	}

	return result, nil
}

func (c *configCenter) GetClusterMeta2(ctx context.Context, logicalRegion, cloud string, req *ccproto.GetClusterMetaRequest) (*ccproto.ClusterMeta, error) {
	result := &ccproto.ClusterMeta{}
	restyReq := c.core.R().
		SetContext(ctx).
		SetPathParams(map[string]string{
			"region":       logicalRegion,
			"cloud":        cloud,
			"cluster_type": req.GetClusterType().String(),
			"cluster_id":   req.GetClusterId(),
		}).
		SetQueryParam("mainComponent", req.GetMainComponent())

	_, err := c.do(resty.MethodGet, "/proxy/nextgen/regions/{region}/cloudProviders/{cloud}/config-center/cluster-types/{cluster_type}/clusters/{cluster_id}/meta", restyReq, result)
	if err != nil {
		return nil, err
	}

	return result, nil
}

func (c *configCenter) UpdateClusterMeta2(ctx context.Context, logicalRegion, cloud string, req *ccproto.UpdateClusterMetaRequest) (*ccproto.UpdateClusterMetaResponse, error) {
	result := &ccproto.UpdateClusterMetaResponse{}
	restyReq := c.core.R().
		SetContext(ctx).
		SetPathParams(map[string]string{
			"region":       logicalRegion,
			"cloud":        cloud,
			"cluster_type": req.GetClusterType().String(),
			"cluster_id":   req.GetClusterId(),
		}).
		SetBody(proto.Message(req)).
		SetResult(result)

	_, err := c.do(resty.MethodPatch, "/proxy/nextgen/regions/{region}/cloudProviders/{cloud}/config-center/cluster-types/{cluster_type}/clusters/{cluster_id}/meta", restyReq, result)
	if err != nil {
		return nil, err
	}

	return result, nil
}

func (c *configCenter) SearchClusterComponents(ctx context.Context, logicalRegion, cloud string, req *ccproto.SearchClusterComponentsRequest) (*ccproto.SearchClusterComponentsResponse, error) {
	result := &ccproto.SearchClusterComponentsResponse{}
	restyReq := c.core.R().
		SetContext(ctx).
		SetPathParams(map[string]string{
			"region":       logicalRegion,
			"cloud":        cloud,
			"cluster_type": req.GetClusterType().String(),
		}).
		SetQueryString(searchClusterComponentsQuery(req).Encode())

	_, err := c.do(resty.MethodGet, "/proxy/nextgen/regions/{region}/cloudProviders/{cloud}/config-center/cluster-types/{cluster_type}/cluster-components:search", restyReq, result)
	if err != nil {
		return nil, err
	}

	return result, nil
}

func searchClusterComponentsQuery(req *ccproto.SearchClusterComponentsRequest) url.Values {
	values := url.Values{}
	values.Set("componentName", req.GetComponentName())
	for _, clusterID := range req.GetClusterIds() {
		values.Add("clusterIds", clusterID)
	}
	if req.GetComponentVersion() != "" {
		values.Set("componentVersion", req.GetComponentVersion())
	}
	if req.GetImageTag() != "" {
		values.Set("imageTag", req.GetImageTag())
	}
	if req.GetPageSize() != 0 {
		values.Set("pageSize", strconv.FormatInt(int64(req.GetPageSize()), 10))
	}
	if req.GetPage() != 0 {
		values.Set("page", strconv.FormatInt(int64(req.GetPage()), 10))
	}
	if req != nil && req.LogicalRegion != nil {
		values.Set("logicalRegion", req.GetLogicalRegion())
	}
	if req.GetShowDeleted() {
		values.Set("showDeleted", strconv.FormatBool(req.GetShowDeleted()))
	}
	return values
}

func (c *configCenter) AutoUpdateClusterMeta(ctx context.Context, premiumClusterID string, req *ccproto.AutoUpdateClusterMetaRequest) (*ccproto.AutoUpdateClusterMetaResponse, error) {
	result := &ccproto.AutoUpdateClusterMetaResponse{}
	restyReq := c.core.R().
		SetContext(ctx).
		SetPathParams(map[string]string{
			"premium_cluster": premiumClusterID,
			"cluster_type":    req.GetClusterType().String(),
			"cluster_id":      req.GetClusterId(),
		}).
		SetBody(proto.Message(req)).
		SetResult(result)

	_, err := c.do(resty.MethodPatch, "/proxy/nextgen/clusters/{premium_cluster}/config-center/cluster-types/{cluster_type}/clusters/{cluster_id}/meta:autoUpdate", restyReq, result)
	if err != nil {
		return nil, err
	}

	return result, nil
}

func (c *configCenter) do(method, apiPath string, request *resty.Request, output interface{}) (*resty.Response, error) {
	resp, err := request.Execute(method, apiPath)
	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
}
