package tidb_management_service

import (
	"bytes"
	"context"
	"fmt"
	"io"
	"net/http"
	"net/url"

	regionalpb "github.com/tidbcloud/tidb-management-service/api/spec/regional/dedicated/v1"
	"golang.org/x/net/context/ctxhttp"
	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"
)

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

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

func (c *VpcServiceHTTPClient) decodeGrpcStatus(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 *VpcServiceHTTPClient) ListVpcs(ctx context.Context, in *regionalpb.ListVpcsRequest, opts ...grpc.CallOption) (*regionalpb.ListVpcsResponse, error) {
	query := make(url.Values)
	if in.GetTenantId() != "" {
		query.Add("tenantId", in.GetTenantId())
	}
	if in.GetProjectId() != "" {
		query.Add("projectId", in.GetProjectId())
	}
	if in.GetClusterId() != "" {
		query.Add("clusterId", in.GetClusterId())
	}
	resp, err := ctxhttp.Get(ctx, c.httpClient, fmt.Sprintf("%s/api/dedicated/v1/vpcs?%s", c.endpoint, query.Encode()))
	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.decodeGrpcStatus(resp.StatusCode, data)
	}
	out := new(regionalpb.ListVpcsResponse)
	if err := c.unmarshalOpt.Unmarshal(data, out); err != nil {
		return nil, err
	}
	return out, nil
}

func (c *VpcServiceHTTPClient) QueryVpcClusterIds(ctx context.Context, in *regionalpb.QueryVpcClusterIdsRequest, opts ...grpc.CallOption) (*regionalpb.QueryVpcClusterIdsResponse, error) {
	query := make(url.Values)
	if in.GetTenantId() != "" {
		query.Add("tenantId", in.GetTenantId())
	}
	if in.GetProjectId() != "" {
		query.Add("projectId", in.GetProjectId())
	}
	if info := in.GetAws(); info != nil {
		query.Add("aws.vpcId", info.GetVpcId())
		query.Add("aws.accountId", info.GetAccountId())
	} else if info := in.GetGcp(); info != nil {
		query.Add("gcp.networkName", info.GetNetworkName())
		query.Add("gcp.projectId", info.GetProjectId())
	}

	resp, err := ctxhttp.Get(ctx, c.httpClient, fmt.Sprintf("%s/api/dedicated/v1/vpcs/queryVpcClusterIds?%s", c.endpoint, query.Encode()))
	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.decodeGrpcStatus(resp.StatusCode, data)
	}
	out := new(regionalpb.QueryVpcClusterIdsResponse)
	if err := c.unmarshalOpt.Unmarshal(data, out); err != nil {
		return nil, err
	}
	return out, nil
}

func (c *VpcServiceHTTPClient) CreateVpc(ctx context.Context, in *regionalpb.CreateVpcRequest, opts ...grpc.CallOption) (*regionalpb.Vpc, error) {
	bs, err := c.marshalOpt.Marshal(in)
	if err != nil {
		return nil, err
	}
	u := fmt.Sprintf("%s/api/dedicated/v1/vpcs", c.endpoint)
	resp, err := ctxhttp.Post(ctx, c.httpClient, u, "application/json", bytes.NewReader(bs))
	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.decodeGrpcStatus(resp.StatusCode, data)
	}
	out := new(regionalpb.Vpc)
	if err := c.unmarshalOpt.Unmarshal(data, out); err != nil {
		return nil, err
	}
	return out, nil
}

func (c *VpcServiceHTTPClient) GetVpc(ctx context.Context, in *regionalpb.GetVpcRequest, opts ...grpc.CallOption) (*regionalpb.Vpc, error) {
	resp, err := ctxhttp.Get(ctx, c.httpClient, fmt.Sprintf("%s/api/dedicated/v1/vpcs/%s", c.endpoint, in.GetVpcId()))
	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.decodeGrpcStatus(resp.StatusCode, data)
	}
	out := new(regionalpb.Vpc)
	if err := c.unmarshalOpt.Unmarshal(data, out); err != nil {
		return nil, err
	}
	return out, nil
}

func (c *VpcServiceHTTPClient) DeleteVpc(ctx context.Context, in *regionalpb.DeleteVpcRequest) error {
	req, err := http.NewRequest(http.MethodDelete, fmt.Sprintf("%s/api/dedicated/v1/vpcs/%s", c.endpoint, in.VpcId), nil)
	if err != nil {
		return err
	}
	resp, err := ctxhttp.Do(ctx, c.httpClient, req)
	if err != nil {
		return err
	}
	defer resp.Body.Close()
	data, err := io.ReadAll(resp.Body)
	if err != nil {
		return err
	}
	if resp.StatusCode > 300 {
		return c.decodeGrpcStatus(resp.StatusCode, data)
	}
	return nil
}
