//go:generate ${GOBIN}/mockgen -destination client_mock.go -package=cliniccli ${GO_MODULE}/pkg/infra/clients/cliniccli Client
package cliniccli

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

	"github.com/go-resty/resty/v2"
)

type Client interface {
	GetOrganizationByCloudID(ctx context.Context, cloudID string) (*Organization, error)
	ListClusters(ctx context.Context, filter *ClusterFilter) ([]Cluster, error)
	AddCluster(ctx context.Context, orgID string, req AddClusterRequest) (*AddClusterResponse, error)
	UpdateClusterStatus(ctx context.Context, orgID, clusterID string, req UpdateClusterStatusReq) error
	QueryClusterMetrics(ctx context.Context, req QueryClusterMetricsRequest) (*QueryMetricResponse, error)
}

type client struct {
	clinicAddr  string
	clinicToken string
	raw         *resty.Client
}

func NewClient(clinicAddr string, clinicToken string) (Client, error) {
	raw := resty.New().
		SetTransport(http.DefaultTransport).
		SetBaseURL(clinicAddr).
		OnBeforeRequest(func(c *resty.Client, r *resty.Request) error {
			r.Header.Set("Authorization", "Bearer "+clinicToken)
			return nil
		}).
		OnAfterResponse(func(c *resty.Client, r *resty.Response) error {
			if !r.IsSuccess() {
				return fmt.Errorf("status: %s, body: %s", r.Status(), string(r.Body()))
			}
			return nil
		})

	return &client{
		clinicAddr:  clinicAddr,
		clinicToken: clinicToken,
		raw:         raw,
	}, nil
}

func (c *client) GetOrganizationByCloudID(ctx context.Context, cloudID string) (*Organization, error) {
	resp := &Organization{}
	_, err := c.raw.R().
		SetContext(ctx).
		SetPathParam("cloud_id", cloudID).
		SetResult(resp).
		Get("/clinic/api/v1/orgs/external/{cloud_id}")
	if err != nil {
		return nil, fmt.Errorf("get org by cloud id failed: %w", err)
	}
	return resp, nil
}

func (c *client) ListClusters(ctx context.Context, filter *ClusterFilter) ([]Cluster, error) {
	page := 1
	perPage := 100
	clusters := make([]Cluster, 0)

	baseQuery := url.Values{}
	// build request
	for _, clusterID := range filter.ClusterIds {
		baseQuery.Add("cluster_id", clusterID)
	}
	for _, status := range filter.ClusterStatus {
		baseQuery.Add("cluster_status", status)
	}
	if filter.ShowDeleted {
		baseQuery.Set("show_deleted", "true")
	} else {
		baseQuery.Set("show_deleted", "false")
	}
	for _, deployType := range filter.DeployType {
		baseQuery.Add("deploy_type", deployType)
	}
	if filter.CreatedAfter != 0 {
		baseQuery.Set("created_ts_from", fmt.Sprintf("%d", filter.CreatedAfter))
	}
	if filter.CreatedBefore != 0 {
		baseQuery.Set("created_ts_to", fmt.Sprintf("%d", filter.CreatedBefore))
	}
	if filter.DeletedAfter != 0 {
		baseQuery.Set("deleted_ts_from", fmt.Sprintf("%d", filter.DeletedAfter))
	}
	if filter.DeletedBefore != 0 {
		baseQuery.Set("deleted_ts_to", fmt.Sprintf("%d", filter.DeletedBefore))
	}

	for {
		resp := &ListClustersResponse{}
		_, err := c.raw.R().
			SetContext(ctx).
			SetQueryParamsFromValues(baseQuery).
			SetQueryParams(map[string]string{
				"page":  fmt.Sprintf("%d", page),
				"limit": fmt.Sprintf("%d", perPage),
			}).
			SetResult(resp).
			Get("/clinic/api/v1/dashboard/clusters2")
		if err != nil {
			return nil, fmt.Errorf("list clusters from clinic failed: %w", err)
		}

		clusters = append(clusters, resp.Items...)

		if page*perPage >= resp.Total || len(resp.Items) == 0 {
			break
		}
		page++
	}

	return clusters, nil
}

func (c *client) AddCluster(ctx context.Context, orgID string, req AddClusterRequest) (*AddClusterResponse, error) {
	resp := &AddClusterResponse{}
	_, err := c.raw.R().
		SetContext(ctx).
		SetBody(req).
		SetResult(resp).
		SetPathParam("org_id", orgID).
		Post("/clinic/api/v1/orgs/{org_id}/clusters")
	if err != nil {
		return nil, fmt.Errorf("add cluster failed: %w", err)
	}
	return resp, nil
}

func (c *client) UpdateClusterStatus(ctx context.Context, orgID, clusterID string, req UpdateClusterStatusReq) error {
	_, err := c.raw.R().
		SetContext(ctx).
		SetBody(req).
		SetPathParam("org_id", orgID).
		SetPathParam("cluster_id", clusterID).
		Post("/clinic/api/v1/orgs/{org_id}/clusters/{cluster_id}/update_status")
	if err != nil {
		return fmt.Errorf("update cluster status failed: %w", err)
	}
	return nil
}

func (c *client) QueryClusterMetrics(ctx context.Context, req QueryClusterMetricsRequest) (*QueryMetricResponse, error) {
	query := url.Values{}

	query.Set("query", req.PromQL)
	if !req.Start.IsZero() {
		query.Set("start", strconv.FormatInt(req.Start.Unix(), 10))
	}
	if !req.End.IsZero() {
		query.Set("end", strconv.FormatInt(req.End.Unix(), 10))
	}
	if req.Step != "" {
		query.Set("step", req.Step)
	}

	resp := &QueryMetricResponse{}
	_, err := c.raw.R().
		SetContext(ctx).
		SetHeaders(map[string]string{
			"X-OrgType":   "cloud",
			"X-OrgID":     req.OrgID,
			"X-ClusterID": req.ClusterID,
		}).
		SetResult(resp).
		SetQueryParamsFromValues(query).Get("/clinic/api/v1/data/metrics")
	if err != nil {
		return nil, fmt.Errorf("query cluster metrics failed: %w", err)
	}

	return resp, nil
}
