package clustergateway

import (
	"context"
	"net/http"
	"strings"

	"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"
	statuspb "google.golang.org/genproto/googleapis/rpc/status"
	"google.golang.org/grpc/codes"
	"google.golang.org/grpc/status"
	"google.golang.org/protobuf/encoding/protojson"
)

// ListPlanReplayerFiles forwards a typed list request to cluster-agent.
func (c *clusterGatewayImpl) ListPlanReplayerFiles(
	ctx context.Context,
	sharedPool *entity.SharedPool,
	request *clusteragentv1.ListPlanReplayerFilesReq,
) (*clusteragentv1.ListPlanReplayerFilesResp, error) {
	resp, err := c.callClusterAgent(
		ctx,
		sharedPool,
		http.MethodPost,
		operationMap["ListPlanReplayerFiles"],
		func(req *resty.Request) *resty.Request {
			return req.SetPathParam(dataplaneNamespaceParameter, sharedPool.GetDataPlaneNamespace()).
				SetPathParam(logicalClusterIdParameter, request.GetClusterId()).
				SetBody(request)
		},
	)
	if err != nil {
		return nil, status.Errorf(codes.Unavailable, "call cluster-agent ListPlanReplayerFiles: %v", err)
	}
	if err := planReplayerResponseError("ListPlanReplayerFiles", resp); err != nil {
		return nil, err
	}

	var output clusteragentv1.ListPlanReplayerFilesResp
	if err := protojson.Unmarshal(resp.Body(), &output); err != nil {
		return nil, status.Errorf(codes.Internal, "decode cluster-agent ListPlanReplayerFiles response: %v", err)
	}
	return &output, nil
}

// GeneratePlanReplayerFileDownloadURL forwards a typed presign request to cluster-agent.
func (c *clusterGatewayImpl) GeneratePlanReplayerFileDownloadURL(
	ctx context.Context,
	sharedPool *entity.SharedPool,
	request *clusteragentv1.GeneratePlanReplayerFileDownloadURLReq,
) (*clusteragentv1.GeneratePlanReplayerFileDownloadURLResp, error) {
	resp, err := c.callClusterAgentNoRespLog(
		ctx,
		sharedPool,
		http.MethodPost,
		operationMap["GeneratePlanReplayerFileDownloadURL"],
		func(req *resty.Request) *resty.Request {
			return req.SetPathParam(dataplaneNamespaceParameter, sharedPool.GetDataPlaneNamespace()).
				SetPathParam(logicalClusterIdParameter, request.GetClusterId()).
				SetBody(request)
		},
	)
	if err != nil {
		return nil, status.Errorf(codes.Unavailable, "call cluster-agent GeneratePlanReplayerFileDownloadURL: %v", err)
	}
	if err := planReplayerResponseError("GeneratePlanReplayerFileDownloadURL", resp); err != nil {
		return nil, err
	}

	var output clusteragentv1.GeneratePlanReplayerFileDownloadURLResp
	if err := protojson.Unmarshal(resp.Body(), &output); err != nil {
		// The body contains a signed URL, so never include it in this error.
		return nil, status.Error(codes.Internal, "decode cluster-agent GeneratePlanReplayerFileDownloadURL response")
	}
	return &output, nil
}

func planReplayerResponseError(operation string, resp *resty.Response) error {
	if resp == nil {
		return status.Errorf(codes.Internal, "cluster-agent %s returned an empty response", operation)
	}
	if resp.StatusCode() >= http.StatusOK && resp.StatusCode() < http.StatusMultipleChoices {
		return nil
	}

	var downstream statuspb.Status
	if err := protojson.Unmarshal(resp.Body(), &downstream); err == nil && downstream.GetCode() != int32(codes.OK) {
		if resp.StatusCode() == http.StatusNotFound &&
			codes.Code(downstream.GetCode()) == codes.NotFound &&
			strings.EqualFold(strings.TrimSpace(downstream.GetMessage()), http.StatusText(http.StatusNotFound)) {
			return status.Errorf(codes.Unimplemented, "cluster-agent does not implement %s", operation)
		}
		return status.Error(codes.Code(downstream.GetCode()), downstream.GetMessage())
	}
	if resp.StatusCode() == http.StatusNotFound {
		// Old agents do not register these routes and return a plain 404 rather
		// than a grpc-gateway google.rpc.Status response.
		return status.Errorf(codes.Unimplemented, "cluster-agent does not implement %s", operation)
	}
	return status.Errorf(
		codes.Internal,
		"cluster-agent %s failed with HTTP status %d",
		operation,
		resp.StatusCode(),
	)
}
