package checkernextgen

import (
	"encoding/json"
	"errors"
	"fmt"
	"io"
	"log/slog"
	"net/http"
	"net/url"
	"strconv"
	"time"
)

const (
	TopsqlMetricExpr           = "sum by (cluster_id) (topsql_instance) > 0"
	TopsqlReportMetricExprTmpl = "sum (increase(tidb_topsql_report_data_total_count[%dm])) by (cluster_id) > 0"
)

func QueryMetricsCase(cluster ClusterInfo, startTs, endTs int, authToken string) (bool, error) {
	url := cluster.MetricsQueryRangeURL()
	o11ycli := &O11yClient{NumRetries: 3, AuthToken: authToken}
	result, err := o11ycli.getO11yMetrics(url, "go_info{}", startTs, endTs, 60)
	if err != nil {
		return false, err
	}
	return len(result) > 0, nil
}

func QueryTopsqlCase(cluster ClusterInfo, startTs, endTs int, authToken string) (bool, error) {
	url := cluster.MetricsQueryRangeURL()
	o11ycli := &O11yClient{NumRetries: 3, AuthToken: authToken}
	// 1. query if topsql is reported by tidb past an hour
	timerange := (endTs - startTs) / 60
	result, err := o11ycli.getO11yMetrics(url, fmt.Sprintf(TopsqlReportMetricExprTmpl, timerange), startTs, endTs, 60)
	if err != nil {
		return false, err
	}
	topsqlReported := len(result) > 0
	slog.Info("topsql report metrics found", "result", topsqlReported)
	if topsqlReported {
		// 2. query topsql metrics if topsql is reported by tidb past an hour
		result, err = o11ycli.getO11yMetrics(url, TopsqlMetricExpr, startTs, endTs, 60)
		if err != nil {
			return false, err
		}
		if len(result) == 0 {
			return false, nil
		}
	}
	return true, nil
}

func QueryLogsCase(cluster ClusterInfo, startTs, endTs int, authToken string) (bool, error) {
	url := cluster.LokiAPIAddress()
	logExpr := fmt.Sprintf(`{namespace="tidb%s"}`, cluster.ClusterID) // TODO: modify for nextgen
	o11ycli := &O11yClient{NumRetries: 3, AuthToken: authToken}
	result, err := o11ycli.getO11yLoki(url, logExpr, startTs, endTs)
	if err != nil {
		return false, err
	}
	if len(result) == 0 {
		return false, nil
	}
	return true, nil
}

type O11yClient struct {
	NumRetries int
	AuthToken  string
}

func (c *O11yClient) getO11yMetrics(url, metricsExpr string, startTime, endTime, step int) ([]interface{}, error) {
	body := map[string]string{
		"query": metricsExpr,
		"start": strconv.Itoa(startTime),
		"end":   strconv.Itoa(endTime),
		"step":  fmt.Sprintf("%ds", step),
	}

	var result []interface{}
	for i := 0; i < c.NumRetries; i++ {
		resp, err := c.makeRequest("GET", url, body)
		if err != nil {
			fmt.Printf("get o11y metrics failed: %s\n", err)
			time.Sleep(100 * time.Millisecond)
			continue
		}

		if resp["status"] != "success" {
			err = errors.New("get o11y metrics failed: status not success")
			fmt.Println(err)
			time.Sleep(100 * time.Millisecond)
			continue
		}

		// Extract result
		if data, ok := resp["data"].(map[string]interface{}); ok {
			if r, ok := data["result"].([]interface{}); ok {
				result = r
				break
			}
		}
	}

	if result == nil {
		return nil, errors.New("get o11y metrics failed after retries")
	}

	return result, nil
}

func (c *O11yClient) getO11yLoki(url, logExpr string, startTime, endTime int) ([]interface{}, error) {
	body := map[string]string{
		"query":     logExpr,
		"start":     strconv.Itoa(startTime),
		"end":       strconv.Itoa(endTime),
		"limit":     "100",
		"direction": "backward",
	}

	resp, err := c.makeRequest("GET", url, body)
	if err != nil {
		return nil, err
	}

	if resp["status"] != "success" {
		return nil, errors.New("get o11y loki logs failed: status not success")
	}

	// Extract result
	if data, ok := resp["data"].(map[string]interface{}); ok {
		if result, ok := data["result"].([]interface{}); ok {
			return result, nil
		}
	}

	return nil, errors.New("get o11y loki logs failed: invalid response format")
}

func mapToURLParams(params map[string]string) string {
	values := url.Values{}
	for key, value := range params {
		values.Add(key, value)
	}
	return values.Encode()
}

func (c *O11yClient) makeRequest(method, url string, params map[string]string) (map[string]interface{}, error) {
	// Convert params to URL-encoded query string or JSON
	query := ""
	if method == "GET" {
		query = mapToURLParams(params)
	}

	// Create HTTP request
	fullURL := fmt.Sprintf("%s?%s", url, query)
	req, err := http.NewRequest(method, fullURL, nil)
	if err != nil {
		return nil, err
	}
	req.Header.Set("Content-Type", "application/json")
	req.Header.Set("Authorization", fmt.Sprintf("Bearer %s", c.AuthToken))

	// Send request
	client := &http.Client{}
	resp, err := client.Do(req)
	if err != nil {
		// fmt.Printf("http request failed: %s,status code is %s \n", err, resp.Status)
		return nil, err
	}
	defer resp.Body.Close()

	// Read response body
	body, err := io.ReadAll(resp.Body)
	if err != nil {
		fmt.Printf("http request failed: %s,status code is %s \n", err, resp.Status)
		return nil, err
	}

	// Parse JSON response
	var result map[string]interface{}
	if err := json.Unmarshal(body, &result); err != nil {
		fmt.Printf("http json filed: %s,status code is %s, result is %s\n", err, resp.Status, string(body))
		return nil, err
	}

	return result, nil
}
