package regionserver

import (
	"context"
	"fmt"
	"net/http"

	"github.com/go-resty/resty/v2"
	regionaldedicatedv1 "github.com/tidbcloud/infra-provider/api/tidbcloud/regional-server/v1"

	"github.com/tidbcloud/tidb-management-service/dedicated-global-service/domain/entity"
	"github.com/tidbcloud/tidb-management-service/utilities/bizerr"
)

func (s *regionServerCliImpl) CreateGKEHeterogeneousDeploy(
	ctx context.Context,
	region *entity.Region,
	req *regionaldedicatedv1.CreateGKEHeterogeneousDeployRequest,
) (*regionaldedicatedv1.GKEHeterogeneousDeploy, bizerr.BizError) {
	body, err := marshalOptions.Marshal(req)
	if err != nil {
		return nil, bizerr.New(bizerr.ErrCodeJsonMarshal, "%s", err.Error())
	}
	resp, berr := s.callRegion(
		ctx,
		region,
		http.MethodPost,
		operationMap["CreateGKEHeterogeneousDeploy"],
		func(request *resty.Request) *resty.Request {
			request.SetPathParam(clusterIdParameter, req.GetClusterId())
			request.SetBody(body)
			return request
		},
	)
	if berr != nil {
		return nil, berr
	}
	if berr := checkGKEHeterogeneousCreateResponse(resp); berr != nil {
		return nil, berr
	}
	return unmarshalGKEHeterogeneousDeploy(resp)
}

func (s *regionServerCliImpl) EnableGKEPolicyControl(
	ctx context.Context,
	region *entity.Region,
	req *regionaldedicatedv1.EnableGKEPolicyControlRequest,
) (*regionaldedicatedv1.GKEPolicyControl, bizerr.BizError) {
	body, err := marshalOptions.Marshal(req)
	if err != nil {
		return nil, bizerr.New(bizerr.ErrCodeJsonMarshal, "%s", err.Error())
	}
	resp, berr := s.callRegion(
		ctx,
		region,
		http.MethodPost,
		operationMap["EnableGKEPolicyControl"],
		func(request *resty.Request) *resty.Request {
			request.SetPathParam(clusterIdParameter, req.GetClusterId())
			request.SetBody(body)
			return request
		},
	)
	if berr != nil {
		return nil, berr
	}
	return unmarshalGKEPolicyControl(resp)
}

func (s *regionServerCliImpl) GetGKEPolicyControl(
	ctx context.Context,
	region *entity.Region,
	req *regionaldedicatedv1.GetGKEPolicyControlRequest,
) (*regionaldedicatedv1.GKEPolicyControl, bizerr.BizError) {
	resp, berr := s.callRegion(
		ctx,
		region,
		http.MethodGet,
		operationMap["GetGKEPolicyControl"],
		func(request *resty.Request) *resty.Request {
			request.SetPathParam(clusterIdParameter, req.GetClusterId())
			return request
		},
	)
	if berr != nil {
		return nil, berr
	}
	return unmarshalGKEPolicyControl(resp)
}

func (s *regionServerCliImpl) UpdateGKEHeterogeneousDeploy(
	ctx context.Context,
	region *entity.Region,
	req *regionaldedicatedv1.UpdateGKEHeterogeneousDeployRequest,
) (*regionaldedicatedv1.GKEHeterogeneousDeploy, bizerr.BizError) {
	body, err := marshalOptions.Marshal(req)
	if err != nil {
		return nil, bizerr.New(bizerr.ErrCodeJsonMarshal, "%s", err.Error())
	}
	resp, berr := s.callRegion(
		ctx,
		region,
		http.MethodPatch,
		operationMap["UpdateGKEHeterogeneousDeploy"],
		func(request *resty.Request) *resty.Request {
			request.SetPathParam(clusterIdParameter, req.GetClusterId())
			request.SetPathParam(componentTypePathParameter, req.GetComponentType().String())
			request.SetBody(body)
			return request
		},
	)
	if berr != nil {
		return nil, berr
	}
	return unmarshalGKEHeterogeneousDeploy(resp)
}

func (s *regionServerCliImpl) GetGKEHeterogeneousDeploy(
	ctx context.Context,
	region *entity.Region,
	req *regionaldedicatedv1.GetGKEHeterogeneousDeployRequest,
) (*regionaldedicatedv1.GKEHeterogeneousDeploy, bizerr.BizError) {
	resp, berr := s.callRegion(ctx, region, http.MethodGet, operationMap["GetGKEHeterogeneousDeploy"], func(request *resty.Request) *resty.Request {
		request.SetPathParam(clusterIdParameter, req.GetClusterId())
		request.SetPathParam(componentTypePathParameter, req.GetComponentType().String())
		if req.GetTidbGroupId() != "" {
			request.SetQueryParam("tidbGroupId", req.GetTidbGroupId())
		}
		return request
	})
	if berr != nil {
		return nil, berr
	}
	return unmarshalGKEHeterogeneousDeploy(resp)
}

func (s *regionServerCliImpl) DeleteGKEHeterogeneousDeploy(
	ctx context.Context,
	region *entity.Region,
	req *regionaldedicatedv1.DeleteGKEHeterogeneousDeployRequest,
) bizerr.BizError {
	_, berr := s.callRegion(
		ctx,
		region,
		http.MethodDelete,
		operationMap["DeleteGKEHeterogeneousDeploy"],
		func(request *resty.Request) *resty.Request {
			request.SetPathParam(clusterIdParameter, req.GetClusterId())
			request.SetPathParam(componentTypePathParameter, req.GetComponentType().String())
			if req.GetTidbGroupId() != "" {
				request.SetQueryParam("tidbGroupId", req.GetTidbGroupId())
			}
			return request
		},
	)
	return berr
}

func (s *regionServerCliImpl) CreateGKEHeterogeneousNodePool(
	ctx context.Context,
	region *entity.Region,
	req *regionaldedicatedv1.CreateGKEHeterogeneousNodePoolRequest,
) (*regionaldedicatedv1.GKEHeterogeneousNodePool, bizerr.BizError) {
	body, err := marshalOptions.Marshal(req)
	if err != nil {
		return nil, bizerr.New(bizerr.ErrCodeJsonMarshal, "%s", err.Error())
	}
	resp, berr := s.callRegion(
		ctx,
		region,
		http.MethodPost,
		operationMap["CreateGKEHeterogeneousNodePool"],
		func(request *resty.Request) *resty.Request {
			request.SetPathParam(clusterIdParameter, req.GetClusterId())
			request.SetBody(body)
			return request
		},
	)
	if berr != nil {
		return nil, berr
	}
	if berr := checkGKEHeterogeneousCreateResponse(resp); berr != nil {
		return nil, berr
	}
	return unmarshalGKEHeterogeneousNodePool(resp)
}

func (s *regionServerCliImpl) GetGKEHeterogeneousNodePools(
	ctx context.Context,
	region *entity.Region,
	req *regionaldedicatedv1.GetGKEHeterogeneousNodePoolsRequest,
) (*regionaldedicatedv1.ListGKEHeterogeneousNodePoolsResponse, bizerr.BizError) {
	resp, berr := s.callRegion(
		ctx,
		region,
		http.MethodGet,
		operationMap["GetGKEHeterogeneousNodePools"],
		func(request *resty.Request) *resty.Request {
			request.SetPathParam(clusterIdParameter, req.GetClusterId())
			request.SetPathParam(componentTypePathParameter, req.GetComponentType().String())
			if req.GetTidbGroupId() != "" {
				request.SetQueryParam("tidbGroupId", req.GetTidbGroupId())
			}
			return request
		},
	)
	if berr != nil {
		return nil, berr
	}
	var out regionaldedicatedv1.ListGKEHeterogeneousNodePoolsResponse
	if err := unmarshalOptions.Unmarshal(resp.Body(), &out); err != nil {
		return nil, bizerr.New(bizerr.ErrCodeRegionalAPI, "%s", err.Error())
	}
	return &out, nil
}

func (s *regionServerCliImpl) ListGKEHeterogeneousNodePools(
	ctx context.Context,
	region *entity.Region,
	req *regionaldedicatedv1.ListGKEHeterogeneousNodePoolsRequest,
) (*regionaldedicatedv1.ListGKEHeterogeneousNodePoolsResponse, bizerr.BizError) {
	resp, berr := s.callRegion(
		ctx,
		region,
		http.MethodGet,
		operationMap["ListGKEHeterogeneousNodePools"],
		func(request *resty.Request) *resty.Request {
			request.SetPathParam(clusterIdParameter, req.GetClusterId())
			if req.GetComponentType() != regionaldedicatedv1.ComponentType_COMPONENT_TYPE_UNSPECIFIED {
				request.SetQueryParam("componentType", req.GetComponentType().String())
			}
			if req.GetTidbGroupId() != "" {
				request.SetQueryParam("tidbGroupId", req.GetTidbGroupId())
			}
			return request
		},
	)
	if berr != nil {
		return nil, berr
	}
	var out regionaldedicatedv1.ListGKEHeterogeneousNodePoolsResponse
	if err := unmarshalOptions.Unmarshal(resp.Body(), &out); err != nil {
		return nil, bizerr.New(bizerr.ErrCodeRegionalAPI, "%s", err.Error())
	}
	return &out, nil
}

func (s *regionServerCliImpl) CreateGKEHeterogeneousComputeClass(
	ctx context.Context,
	region *entity.Region,
	req *regionaldedicatedv1.CreateGKEHeterogeneousComputeClassRequest,
) (*regionaldedicatedv1.GKEHeterogeneousComputeClass, bizerr.BizError) {
	body, err := marshalOptions.Marshal(req)
	if err != nil {
		return nil, bizerr.New(bizerr.ErrCodeJsonMarshal, "%s", err.Error())
	}
	resp, berr := s.callRegion(
		ctx,
		region,
		http.MethodPost,
		operationMap["CreateGKEHeterogeneousComputeClass"],
		func(request *resty.Request) *resty.Request {
			request.SetPathParam(clusterIdParameter, req.GetClusterId())
			request.SetBody(body)
			return request
		},
	)
	if berr != nil {
		return nil, berr
	}
	if berr := checkGKEHeterogeneousCreateResponse(resp); berr != nil {
		return nil, berr
	}
	var out regionaldedicatedv1.GKEHeterogeneousComputeClass
	if err := unmarshalOptions.Unmarshal(resp.Body(), &out); err != nil {
		return nil, bizerr.New(bizerr.ErrCodeRegionalAPI, "%s", err.Error())
	}
	return &out, nil
}

func (s *regionServerCliImpl) GetGKEHeterogeneousComputeClass(
	ctx context.Context,
	region *entity.Region,
	req *regionaldedicatedv1.GetGKEHeterogeneousComputeClassRequest,
) (*regionaldedicatedv1.GetGKEHeterogeneousComputeClassResponse, bizerr.BizError) {
	queryParams, err := protobufStructToMap(req)
	if err != nil {
		return nil, bizerr.WrapErrorWithCode(err, bizerr.ErrCodeJsonMarshal, "marshal gke heterogeneous compute class query params failed")
	}
	delete(queryParams, "clusterId")
	resp, berr := s.callRegion(
		ctx,
		region,
		http.MethodGet,
		operationMap["GetGKEHeterogeneousComputeClass"],
		func(request *resty.Request) *resty.Request {
			request.SetPathParam(clusterIdParameter, req.GetClusterId())
			for key, value := range queryParams {
				switch val := value.(type) {
				case []any:
					request.SetQueryParamsFromValues(convertArrayFieldValueToValues(key, val))
				default:
					request.SetQueryParam(key, fmt.Sprintf("%v", value))
				}
			}
			return request
		},
	)
	if berr != nil {
		return nil, berr
	}
	var out regionaldedicatedv1.GetGKEHeterogeneousComputeClassResponse
	if err := unmarshalOptions.Unmarshal(resp.Body(), &out); err != nil {
		return nil, bizerr.New(bizerr.ErrCodeRegionalAPI, "%s", err.Error())
	}
	return &out, nil
}

func (s *regionServerCliImpl) DeleteGKEHeterogeneousComputeClass(
	ctx context.Context,
	region *entity.Region,
	req *regionaldedicatedv1.DeleteGKEHeterogeneousComputeClassRequest,
) bizerr.BizError {
	_, berr := s.callRegion(
		ctx,
		region,
		http.MethodDelete,
		operationMap["DeleteGKEHeterogeneousComputeClass"],
		func(request *resty.Request) *resty.Request {
			request.SetPathParam(clusterIdParameter, req.GetClusterId())
			request.SetPathParam(gkeHeterogeneousResourceNameParameter, req.GetName())
			return request
		},
	)
	return berr
}

func (s *regionServerCliImpl) CreateGKEHeterogeneousGatekeeperAssign(
	ctx context.Context,
	region *entity.Region,
	req *regionaldedicatedv1.CreateGKEHeterogeneousGatekeeperAssignRequest,
) (*regionaldedicatedv1.GKEHeterogeneousGatekeeperAssign, bizerr.BizError) {
	body, err := marshalOptions.Marshal(req)
	if err != nil {
		return nil, bizerr.New(bizerr.ErrCodeJsonMarshal, "%s", err.Error())
	}
	resp, berr := s.callRegion(
		ctx,
		region,
		http.MethodPost,
		operationMap["CreateGKEHeterogeneousGatekeeperAssign"],
		func(request *resty.Request) *resty.Request {
			request.SetPathParam(clusterIdParameter, req.GetClusterId())
			request.SetBody(body)
			return request
		},
	)
	if berr != nil {
		return nil, berr
	}
	if berr := checkGKEHeterogeneousCreateResponse(resp); berr != nil {
		return nil, berr
	}
	return unmarshalGKEHeterogeneousGatekeeperAssign(resp)
}

func (s *regionServerCliImpl) GetGKEHeterogeneousGatekeeperAssign(
	ctx context.Context,
	region *entity.Region,
	req *regionaldedicatedv1.GetGKEHeterogeneousGatekeeperAssignRequest,
) (*regionaldedicatedv1.GKEHeterogeneousGatekeeperAssign, bizerr.BizError) {
	resp, berr := s.callRegion(
		ctx,
		region,
		http.MethodGet,
		operationMap["GetGKEHeterogeneousGatekeeperAssign"],
		func(request *resty.Request) *resty.Request {
			request.SetPathParam(clusterIdParameter, req.GetClusterId())
			request.SetPathParam(componentTypePathParameter, req.GetComponentType().String())
			if req.GetTidbGroupId() != "" {
				request.SetQueryParam("tidbGroupId", req.GetTidbGroupId())
			}
			return request
		},
	)
	if berr != nil {
		return nil, berr
	}
	return unmarshalGKEHeterogeneousGatekeeperAssign(resp)
}

func (s *regionServerCliImpl) DeleteGKEHeterogeneousGatekeeperAssign(
	ctx context.Context,
	region *entity.Region,
	req *regionaldedicatedv1.DeleteGKEHeterogeneousGatekeeperAssignRequest,
) bizerr.BizError {
	_, berr := s.callRegion(
		ctx,
		region,
		http.MethodDelete,
		operationMap["DeleteGKEHeterogeneousGatekeeperAssign"],
		func(request *resty.Request) *resty.Request {
			request.SetPathParam(clusterIdParameter, req.GetClusterId())
			request.SetPathParam(gkeHeterogeneousResourceNameParameter, req.GetName())
			return request
		},
	)
	return berr
}

func unmarshalGKEHeterogeneousDeploy(resp *resty.Response) (*regionaldedicatedv1.GKEHeterogeneousDeploy, bizerr.BizError) {
	var out regionaldedicatedv1.GKEHeterogeneousDeploy
	if err := unmarshalOptions.Unmarshal(resp.Body(), &out); err != nil {
		return nil, bizerr.New(bizerr.ErrCodeRegionalAPI, "%s", err.Error())
	}
	return &out, nil
}

func unmarshalGKEPolicyControl(resp *resty.Response) (*regionaldedicatedv1.GKEPolicyControl, bizerr.BizError) {
	var out regionaldedicatedv1.GKEPolicyControl
	if err := unmarshalOptions.Unmarshal(resp.Body(), &out); err != nil {
		return nil, bizerr.New(bizerr.ErrCodeRegionalAPI, "%s", err.Error())
	}
	return &out, nil
}

func checkGKEHeterogeneousCreateResponse(resp *resty.Response) bizerr.BizError {
	// callRegion lets 409 responses through so create callers can map them to
	// ErrCodeRecordExist. Keep this check before unmarshalling the response body.
	if resp.StatusCode() != http.StatusConflict {
		return nil
	}
	return bizerr.New(bizerr.ErrCodeRecordExist, "gke heterogeneous resource already exists")
}

func unmarshalGKEHeterogeneousNodePool(resp *resty.Response) (*regionaldedicatedv1.GKEHeterogeneousNodePool, bizerr.BizError) {
	var out regionaldedicatedv1.GKEHeterogeneousNodePool
	if err := unmarshalOptions.Unmarshal(resp.Body(), &out); err != nil {
		return nil, bizerr.New(bizerr.ErrCodeRegionalAPI, "%s", err.Error())
	}
	return &out, nil
}

func unmarshalGKEHeterogeneousGatekeeperAssign(resp *resty.Response) (*regionaldedicatedv1.GKEHeterogeneousGatekeeperAssign, bizerr.BizError) {
	var out regionaldedicatedv1.GKEHeterogeneousGatekeeperAssign
	if err := unmarshalOptions.Unmarshal(resp.Body(), &out); err != nil {
		return nil, bizerr.New(bizerr.ErrCodeRegionalAPI, "%s", err.Error())
	}
	if out.GetName() == "" {
		return nil, bizerr.New(
			bizerr.ErrCodeRegionalAPI,
			"empty gatekeeper assign response from %s",
			fmt.Sprintf("%s %s", resp.Request.Method, resp.Request.URL),
		)
	}
	return &out, nil
}
