package tidb_management_service

import (
	"bytes"
	"context"
	"fmt"
	"github.com/tidbcloud/tiinf/monitor/tilog"
	"go.uber.org/zap"
	"io"
	"net/http"
	"net/url"

	regionalpb "github.com/tidbcloud/tidb-management-service/api/spec/regional/dedicated/v1"
	spb "google.golang.org/genproto/googleapis/rpc/status"
	grpc "google.golang.org/grpc"
	grpcstatus "google.golang.org/grpc/status"
	"google.golang.org/protobuf/encoding/protojson"
	emptypb "google.golang.org/protobuf/types/known/emptypb"
)

type ClusterServiceHTTPClient struct {
	endpoint     string
	httpClient   *http.Client
	marshalOpt   protojson.MarshalOptions
	unmarshalOpt protojson.UnmarshalOptions
}

func NewClusterServiceHTTPClient(endpoint string) *ClusterServiceHTTPClient {
	return &ClusterServiceHTTPClient{
		endpoint:   endpoint,
		httpClient: http.DefaultClient,
		marshalOpt: protojson.MarshalOptions{
			UseProtoNames:   false,
			EmitUnpopulated: true,
		},
		unmarshalOpt: protojson.UnmarshalOptions{
			DiscardUnknown: true,
		},
	}
}

func (c *ClusterServiceHTTPClient) processError(httpstatuscode int, data []byte) error {
	status := new(spb.Status)
	if err := c.unmarshalOpt.Unmarshal(data, status); err != nil {
		return err
	}
	status.Message = fmt.Sprintf("%s;HTTPSTATUSCODE:%v", status.Message, httpstatuscode)
	return grpcstatus.ErrorProto(status)
}

func (c *ClusterServiceHTTPClient) CreateCluster(ctx context.Context, in *regionalpb.CreateClusterRequest, opts ...grpc.CallOption) (*regionalpb.Cluster, error) {
	bs, err := c.marshalOpt.Marshal(in.Cluster)
	if err != nil {
		return nil, err
	}
	req, err := http.NewRequestWithContext(ctx, http.MethodPost, fmt.Sprintf("%s/api/dedicated/v1/clusters", c.endpoint), bytes.NewReader(bs))
	if err != nil {
		return nil, err
	}
	resp, err := c.httpClient.Do(req)
	if err != nil {
		return nil, err
	}
	defer resp.Body.Close()
	data, err := io.ReadAll(resp.Body)
	if err != nil {
		return nil, err
	}
	if resp.StatusCode > 300 {
		return nil, c.processError(resp.StatusCode, data)
	}
	out := new(regionalpb.Cluster)
	if err := c.unmarshalOpt.Unmarshal(data, out); err != nil {
		return nil, err
	}
	return out, nil
}

func (c *ClusterServiceHTTPClient) ListClusters(ctx context.Context, in *regionalpb.ListClustersRequest, opts ...grpc.CallOption) (*regionalpb.ListClustersResponse, error) {
	query := make(url.Values)
	if in.GetTenantId() != "" {
		query.Add("tenantId", in.GetTenantId())
	}
	for _, id := range in.GetProjectIds() {
		query.Add("projectId", id)
	}
	req, err := http.NewRequestWithContext(ctx, http.MethodGet, fmt.Sprintf("%s/api/dedicated/v1/clusters?%s", c.endpoint, query.Encode()), nil)
	if err != nil {
		return nil, err
	}
	resp, err := c.httpClient.Do(req)
	if err != nil {
		return nil, err
	}
	defer resp.Body.Close()
	data, err := io.ReadAll(resp.Body)
	if err != nil {
		return nil, err
	}
	if resp.StatusCode > 300 {
		return nil, c.processError(resp.StatusCode, data)
	}
	out := new(regionalpb.ListClustersResponse)
	if err := c.unmarshalOpt.Unmarshal(data, out); err != nil {
		return nil, err
	}
	return out, nil
}

func (c *ClusterServiceHTTPClient) GetCluster(ctx context.Context, in *regionalpb.GetClusterRequest, opts ...grpc.CallOption) (*regionalpb.Cluster, error) {
	tilog.Info(ctx, "GetCluster url", zap.String("url", fmt.Sprintf("%s/api/dedicated/v1/clusters/%s", c.endpoint, in.GetClusterId())))
	req, err := http.NewRequestWithContext(ctx, http.MethodGet, fmt.Sprintf("%s/api/dedicated/v1/clusters/%s", c.endpoint, in.GetClusterId()), nil)
	if err != nil {
		return nil, err
	}
	resp, err := c.httpClient.Do(req)
	if err != nil {
		return nil, err
	}
	defer resp.Body.Close()
	data, err := io.ReadAll(resp.Body)
	if err != nil {
		return nil, err
	}
	if resp.StatusCode > 300 {
		return nil, c.processError(resp.StatusCode, data)
	}
	out := new(regionalpb.Cluster)
	if err := c.unmarshalOpt.Unmarshal(data, out); err != nil {
		return nil, err
	}
	return out, nil
}

func (c *ClusterServiceHTTPClient) DeleteCluster(ctx context.Context, in *regionalpb.DeleteClusterRequest, opts ...grpc.CallOption) (*emptypb.Empty, error) {
	req, err := http.NewRequestWithContext(ctx, http.MethodDelete, fmt.Sprintf("%s/api/dedicated/v1/clusters/%s", c.endpoint, in.ClusterId), nil)
	if err != nil {
		return nil, err
	}
	resp, err := c.httpClient.Do(req)
	if err != nil {
		return nil, err
	}
	defer resp.Body.Close()
	data, err := io.ReadAll(resp.Body)
	if err != nil {
		return nil, err
	}
	if resp.StatusCode > 300 {
		return nil, c.processError(resp.StatusCode, data)
	}
	return new(emptypb.Empty), nil
}

func (c *ClusterServiceHTTPClient) GetClusterRole(ctx context.Context, in *regionalpb.GetClusterRoleRequest, opts ...grpc.CallOption) (*regionalpb.ClusterRole, error) {
	tilog.Info(ctx, "GetClusterRole url", zap.String("url", fmt.Sprintf("%s/api/dedicated/v1/clusters/%s:getClusterRole", c.endpoint, in.GetClusterId())))
	req, err := http.NewRequestWithContext(ctx, http.MethodGet, fmt.Sprintf("%s/api/dedicated/v1/clusters/%s:getClusterRole", c.endpoint, in.GetClusterId()), nil)
	if err != nil {
		return nil, err
	}
	resp, err := c.httpClient.Do(req)
	if err != nil {
		return nil, err
	}
	defer resp.Body.Close()
	data, err := io.ReadAll(resp.Body)
	if err != nil {
		return nil, err
	}
	if resp.StatusCode > 300 {
		return nil, c.processError(resp.StatusCode, data)
	}
	out := new(regionalpb.ClusterRole)
	if err := c.unmarshalOpt.Unmarshal(data, out); err != nil {
		return nil, err
	}
	return out, nil
}
