package clustercli

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

	"github.com/pingcap/log"
	clusterctlpb "github.com/tidbcloud/infra-provider/api/tidbcloud/cluster-server/v1"
	"go.uber.org/zap"
	spb "google.golang.org/genproto/googleapis/rpc/status"
	grpcstatus "google.golang.org/grpc/status"
	"google.golang.org/protobuf/encoding/protojson"
)

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

func NewClusterCli(endpoint string) *ClusterCli {
	return &ClusterCli{
		endpoint: endpoint,
		httpClient: &http.Client{
			Timeout: 10 * time.Second,
		},
		marshalOpt: protojson.MarshalOptions{
			UseProtoNames:   false,
			EmitUnpopulated: true,
		},
		unmarshalOpt: protojson.UnmarshalOptions{
			DiscardUnknown: true,
		},
	}
}

func (c *ClusterCli) CreateCluster(ctx context.Context, in *clusterctlpb.CreateClusterRequest) (*clusterctlpb.Cluster, error) {
	bs, err := c.marshalOpt.Marshal(in)
	if err != nil {
		return nil, err
	}
	log.Debug("create cluster request", zap.String("request", string(bs)))
	req, err := http.NewRequestWithContext(ctx, http.MethodPost, fmt.Sprintf("%s/api/v1/clusters", c.endpoint), bytes.NewReader(bs))
	if err != nil {
		return nil, err
	}
	data, err := requestDo(c, req)
	if err != nil {
		return nil, err
	}
	out := new(clusterctlpb.Cluster)
	if err := c.unmarshalOpt.Unmarshal(data, out); err != nil {
		return nil, err
	}
	return out, nil
}

func (c *ClusterCli) DeleteCluster(ctx context.Context, in *clusterctlpb.DeleteClusterRequest) (*clusterctlpb.Cluster, error) {
	req, err := http.NewRequestWithContext(ctx, http.MethodDelete, fmt.Sprintf("%s/api/v1/clusters/%v", c.endpoint, in.GetClusterId()), nil)
	if err != nil {
		return nil, err
	}
	data, err := requestDo(c, req)
	if err != nil {
		return nil, err
	}
	out := new(clusterctlpb.Cluster)
	if err := c.unmarshalOpt.Unmarshal(data, out); err != nil {
		return nil, err
	}
	return out, nil
}

func (c *ClusterCli) GetTidbGroup(ctx context.Context, in *clusterctlpb.GetTidbGroupRequest) (*clusterctlpb.TidbGroup, error) {
	req, err := http.NewRequestWithContext(ctx, http.MethodGet, fmt.Sprintf("%s/api/v1/clusters/%v/tidbgroups/%v", c.endpoint, in.GetClusterId(), in.GetTidbGroupId()), nil)
	if err != nil {
		return nil, err
	}
	data, err := requestDo(c, req)
	if err != nil {
		return nil, err
	}
	out := new(clusterctlpb.TidbGroup)
	if err := c.unmarshalOpt.Unmarshal(data, out); err != nil {
		return nil, err
	}
	return out, nil
}

func (c *ClusterCli) SetPrivateLinkIPAddressType(
	ctx context.Context,
	in *clusterctlpb.SetPrivateLinkIPAddressTypeRequest,
) error {
	bs, err := c.marshalOpt.Marshal(in)
	if err != nil {
		return err
	}
	url := fmt.Sprintf(
		"%s/api/v1/clusters/%v/tidbgroups/%v:setPrivateLinkIpAddressType",
		c.endpoint, in.GetClusterId(), in.GetTidbGroupId(),
	)
	req, err := http.NewRequestWithContext(ctx, http.MethodPost, url, bytes.NewReader(bs))
	if err != nil {
		return err
	}
	_, err = requestDo(c, req)
	return err
}

func (c *ClusterCli) GetClusterRole(ctx context.Context, in *clusterctlpb.GetClusterRoleRequest) (*clusterctlpb.ClusterRole, error) {
	req, err := http.NewRequestWithContext(ctx, http.MethodGet, fmt.Sprintf("%s/api/v1/clusters/%v:getClusterRole", c.endpoint, in.GetClusterId()), nil)
	if err != nil {
		return nil, err
	}
	data, err := requestDo(c, req)
	if err != nil {
		return nil, err
	}
	out := new(clusterctlpb.ClusterRole)
	if err := c.unmarshalOpt.Unmarshal(data, out); err != nil {
		return nil, err
	}
	return out, nil
}

func (c *ClusterCli) GetCluster(ctx context.Context, in *clusterctlpb.GetClusterRequest) (*clusterctlpb.Cluster, error) {
	req, err := http.NewRequestWithContext(ctx, http.MethodGet, fmt.Sprintf("%s/api/v1/clusters/%v", c.endpoint, in.GetClusterId()), nil)
	if err != nil {
		return nil, err
	}
	data, err := requestDo(c, req)
	if err != nil {
		return nil, err
	}
	out := new(clusterctlpb.Cluster)
	if err := c.unmarshalOpt.Unmarshal(data, out); err != nil {
		return nil, err
	}
	return out, nil
}

func (c *ClusterCli) GetComponentNodeInfos(ctx context.Context, in *clusterctlpb.GetComponentNodeInfosRequest) (*clusterctlpb.GetComponentNodeInfosResponse, error) {
	req, err := http.NewRequestWithContext(ctx, http.MethodGet, fmt.Sprintf("%s/api/v1/clusters/%v:getComponentNodeInfos", c.endpoint, in.GetClusterId()), nil)
	if err != nil {
		return nil, err
	}
	data, err := requestDo(c, req)
	if err != nil {
		return nil, err
	}
	out := new(clusterctlpb.GetComponentNodeInfosResponse)
	if err := c.unmarshalOpt.Unmarshal(data, out); err != nil {
		return nil, err
	}
	return out, nil
}

func (c *ClusterCli) GetVersionAndConfigurations(ctx context.Context, in *clusterctlpb.GetVersionAndConfigurationsRequest) (*clusterctlpb.GetVersionAndConfigurationsResponse, error) {
	req, err := http.NewRequestWithContext(ctx, http.MethodGet, fmt.Sprintf("%s/api/v1/clusters/%v:getVersionAndConfigurations", c.endpoint, in.GetClusterId()), nil)
	if err != nil {
		return nil, err
	}
	data, err := requestDo(c, req)
	if err != nil {
		return nil, err
	}
	out := new(clusterctlpb.GetVersionAndConfigurationsResponse)
	if err := c.unmarshalOpt.Unmarshal(data, out); err != nil {
		return nil, err
	}
	return out, nil
}

func (c *ClusterCli) ListTidbGroups(ctx context.Context, in *clusterctlpb.ListTidbGroupsRequest) (*clusterctlpb.ListTidbGroupsResponse, error) {
	req, err := http.NewRequestWithContext(ctx, http.MethodGet, fmt.Sprintf("%s/api/v1/clusters/%v/tidbgroups", c.endpoint, in.GetClusterId()), nil)
	if err != nil {
		return nil, err
	}
	data, err := requestDo(c, req)
	if err != nil {
		return nil, err
	}
	out := new(clusterctlpb.ListTidbGroupsResponse)
	if err := c.unmarshalOpt.Unmarshal(data, out); err != nil {
		return nil, err
	}
	return out, nil
}

func (c *ClusterCli) GetInfraInformation(ctx context.Context, in *clusterctlpb.GetInfraInformationRequest) (*clusterctlpb.InfraInformation, error) {
	req, err := http.NewRequestWithContext(ctx, http.MethodGet, fmt.Sprintf("%s/api/v1/clusters/%v:getInfraInformation", c.endpoint, in.GetClusterId()), nil)
	if err != nil {
		return nil, err
	}
	data, err := requestDo(c, req)
	if err != nil {
		return nil, err
	}
	out := new(clusterctlpb.InfraInformation)
	if err := c.unmarshalOpt.Unmarshal(data, out); err != nil {
		return nil, err
	}
	return out, nil
}

func (c *ClusterCli) UpgradeNodes(ctx context.Context, in *clusterctlpb.UpgradeNodesRequest) (*clusterctlpb.UpgradeNodesResponse, error) {
	bs, err := c.marshalOpt.Marshal(in)
	if err != nil {
		return nil, err
	}
	log.Debug("upgrade tidb nodes", zap.String("request", string(bs)))
	req, err := http.NewRequestWithContext(ctx, http.MethodPost, fmt.Sprintf("%s/api/v1/clusters/%v/operations:upgradeNodes", c.endpoint, in.GetClusterId()), bytes.NewReader(bs))
	if err != nil {
		return nil, err
	}
	data, err := requestDo(c, req)
	if err != nil {
		return nil, err
	}
	out := new(clusterctlpb.UpgradeNodesResponse)
	if err := c.unmarshalOpt.Unmarshal(data, out); err != nil {
		return nil, err
	}
	return out, nil
}

func (c *ClusterCli) UpdateClusterAWSCloud(ctx context.Context, in *clusterctlpb.UpdateClusterAWSCloudRequest) (*clusterctlpb.Cluster, error) {
	bs, err := c.marshalOpt.Marshal(in)
	if err != nil {
		return nil, err
	}
	log.Debug("update cluster aws cloud", zap.String("request", string(bs)))
	req, err := http.NewRequestWithContext(ctx, http.MethodPost, fmt.Sprintf("%s/api/v1/clusters/%v:updateClusterAWSCloud", c.endpoint, in.GetClusterId()), bytes.NewReader(bs))
	if err != nil {
		return nil, err
	}
	data, err := requestDo(c, req)
	if err != nil {
		return nil, err
	}
	out := new(clusterctlpb.Cluster)
	if err := c.unmarshalOpt.Unmarshal(data, out); err != nil {
		return nil, err
	}
	return out, nil
}

func requestDo(c *ClusterCli, req *http.Request) ([]byte, error) {
	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 {
		status := new(spb.Status)
		if err := c.unmarshalOpt.Unmarshal(data, status); err != nil {
			return nil, err
		}
		return nil, grpcstatus.ErrorProto(status)
	}
	return data, nil
}

// func (c *ClusterCli) ListClusters(ctx context.Context, in *clusterctlpb.ListClustersRequest) (*clusterctlpb.ListClustersResponse, error) {
// 	query := make(url.Values)
// 	if in.GetTenantId() != "" {
// 		query.Add("tenantId", in.GetTenantId())
// 	}
// 	for _, id := range in.GetProjectIds() {
// 		query.Add("projectId", id)
// 	}
// 	resp, err := ctxhttp.Get(ctx, c.httpClient, fmt.Sprintf("%s/api/v1/clusters?%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 {
// 		status := new(spb.Status)
// 		if err := c.unmarshalOpt.Unmarshal(data, status); err != nil {
// 			return nil, err
// 		}
// 		return nil, grpcstatus.ErrorProto(status)
// 	}
// 	out := new(clusterctlpb.ListClustersResponse)
// 	if err := c.unmarshalOpt.Unmarshal(data, out); err != nil {
// 		return nil, err
// 	}
// 	return out, nil
// }
