package query

import (
	"context"
	"encoding/json"
	"net/http"
	"time"

	"github.com/pingcap/errors"
	"github.com/tidbcloud/aws-shared-provider/pkg/metrics"
)

const (
	// DefaultPromTimeout is the default timeout for prom queryer
	DefaultPromTimeout = 30 * time.Second
	// SuccessStatus in the response.
	SuccessStatus = "success"
)

var (
	// DefaultPromClient is the default prom queryer client
	DefaultPromClient = &http.Client{Timeout: DefaultPromTimeout}
)

// PromResponse of the Query.
type PromResponse struct {
	Status    string   `json:"status"`
	Data      PromData `json:"data"`
	ErrorType string   `json:"errorType"`
	Error     string   `json:"error"`
}

// PromData part from the response of prometheus.
type PromData struct {
	ResultType string       `json:"resultType"`
	Result     []ResultItem `json:"result"`
}

// ResultItem in the response of prometheus.
type ResultItem struct {
	Metric map[string]string `json:"metric"`
	Value  []any             `json:"value"`
}

// PromRangeResponse of the QueryRange.
type PromRangeResponse struct {
	Status    string        `json:"status"`
	Data      PromRangeData `json:"data"`
	ErrorType string        `json:"errorType"`
	Error     string        `json:"error"`
}

// PromRangeData part from the response of prometheus.
type PromRangeData struct {
	ResultType string            `json:"resultType"`
	Result     []ResultRangeItem `json:"result"`
}

// ResultRangeItem in the response of prometheus.
type ResultRangeItem struct {
	Metric map[string]string `json:"metric"`
	Values [][]any           `json:"values"`
}

// PromQuerier conducts queries about metrics
type PromQuerier interface {
	QueryMetrics(ctx context.Context, path string, values map[string]string) (*PromResponse, error)
	QueryRangeMetrics(ctx context.Context, path string, values map[string]string) (*PromRangeResponse, error)
}

type promHttpQueryer struct {
	*httpClientQueryer
}

var _ Querier = &promHttpQueryer{}
var _ PromQuerier = &promHttpQueryer{}

func NewPromQueryer(client *http.Client, url string) PromQuerier {
	if client == nil {
		client = DefaultPromClient
	}
	return &promHttpQueryer{
		&httpClientQueryer{
			url:    url,
			client: client,
		},
	}
}

func (q *promHttpQueryer) QueryMetrics(ctx context.Context,
	path string, values map[string]string) (*PromResponse, error) {
	start := time.Now()
	reason := "ok"
	defer func() {
		metrics.PromQueryHistogram.WithLabelValues("query", reason).Observe(time.Since(start).Seconds())
	}()

	body, err := q.Query(ctx, path, values)
	if err != nil {
		reason = "query error"
		return nil, err
	}

	resp := &PromResponse{}
	err = json.Unmarshal(body, resp)
	if err != nil {
		reason = "json unmarshal error"
		return nil, err
	}

	if resp.Status != SuccessStatus {
		reason = "response status is not success"
		return nil, errors.Errorf("query failed: %q", resp.Error)
	}

	return resp, nil
}

func (q *promHttpQueryer) QueryRangeMetrics(ctx context.Context,
	path string, values map[string]string) (*PromRangeResponse, error) {
	start := time.Now()
	reason := "ok"
	defer func() {
		metrics.PromQueryHistogram.WithLabelValues("query_range", reason).Observe(time.Since(start).Seconds())
	}()

	body, err := q.Query(ctx, path, values)
	if err != nil {
		reason = "query error"
		return nil, err
	}

	resp := &PromRangeResponse{}
	err = json.Unmarshal(body, resp)
	if err != nil {
		reason = "json unmarshal error"
		return nil, err
	}

	if resp.Status != SuccessStatus {
		reason = "response status is not success"
		return nil, errors.Errorf("query range failed: %q", resp.Error)
	}

	return resp, nil
}
