package checker

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"
)

// getO11yDSDomain create Observability DS Domain
func getO11yDSDomain(vendor, region string) string {
	return fmt.Sprintf("www.ds.%s.%s.observability.tidbcloud.com", region, vendor)
}

// getO11yMetricsPath create Observability Metrics Path
func getO11yMetricsPath(vendor, region, clusterID string) string {
	return fmt.Sprintf("%s/internal/metrics/%s", getO11yDSDomain(vendor, region), clusterID)
}

// getO11yLokiPath create Observability Loki Path
func getO11yLokiPath(vendor, region string) string {
	return fmt.Sprintf("%s/loki/self-monitoring", getO11yDSDomain(vendor, region))
}

func QueryMetricsCase(clusterDetail ClusterInfo, startTs, endTs int) (bool, error) {
	// query cluster metrics and k8s metrics
	dspath := getO11yMetricsPath(clusterDetail.Provider, clusterDetail.Region, clusterDetail.O11yID)
	// query cluster metrics
	metricsExpr := fmt.Sprintf(`go_info{cluster_id="tidb%s"}`, clusterDetail.ClusterID)
	o11ycli := &O11yClient{NumRetries: 3}
	result, err := o11ycli.getO11yMetrics(dspath, metricsExpr, startTs, endTs, 60)
	if err != nil {
		fmt.Println(err)
		return false, err
	}
	if len(result) == 0 {
		return false, nil
	}
	slog.Info("metrics found", "result", result[0])
	return true, nil
}

func QueryTopsqlCase(clusterDetail ClusterInfo, startTs, endTs int) (bool, error) {
	// FIXME: remove this after topsql datasource is ready for these clusters
	if clusterDetail.ClusterID == "1379661944646416046" || clusterDetail.ClusterID == "1379661944646416075" || clusterDetail.ClusterID == "1379661944646416074" || clusterDetail.ClusterID == "1379661944646414638" || clusterDetail.ClusterID == "1379661944646416073" {
		return true, nil
	}

	dspath := getO11yMetricsPath(clusterDetail.Provider, clusterDetail.Region, clusterDetail.O11yID)
	o11ycli := &O11yClient{NumRetries: 3}
	// 1. query if topsql is reported by tidb past an hour
	timerange := (endTs - startTs) / 60
	result, err := o11ycli.queryO11yMetrics(dspath, fmt.Sprintf(TopsqlReportMetricExprTmpl, timerange), endTs, 60)
	if err != nil {
		slog.Error("query metrics failed", "error", err)
		return false, err
	}
	topsqlReported := len(result) > 0

	if topsqlReported {
		slog.Info("topsql report metrics found")
		// 2. query topsql metrics if topsql is reported by tidb past an hour
		result, err = o11ycli.getO11yMetrics(dspath, TopsqlMetricExpr, startTs, endTs, 60)
		if err != nil {
			slog.Error("query metrics failed", "error", err)
			return false, err
		}
		if len(result) == 0 {
			return false, nil
		}
	}
	return true, nil
}

func QueryLogsCase(clusterDetail ClusterInfo, startTs, endTs int) (bool, error) {
	// query cluster logs
	dspath := getO11yLokiPath(clusterDetail.Provider, clusterDetail.Region)
	// query cluster logs
	logExpr := fmt.Sprintf(`{namespace="tidb%s"}`, clusterDetail.ClusterID)
	o11ycli := &O11yClient{NumRetries: 3}
	result, err := o11ycli.getO11yLoki(dspath, logExpr, startTs, endTs)
	if err != nil {
		fmt.Println(err)
		return false, err
	}
	if len(result) == 0 {
		return false, nil
	}
	slog.Info("loki logs found", "result", result[0])
	return true, nil
}

type O11yClient struct {
	NumRetries int
}

// getO11yMetrics query metrics api
func (c *O11yClient) getO11yMetrics(dsPath, metricsExpr string, startTime, endTime, step int) ([]interface{}, error) {
	url := fmt.Sprintf("https://%s/api/v1/query_range", dsPath)
	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) queryO11yMetrics(dsPath, metricsExpr string, t, step int) ([]interface{}, error) {
	url := fmt.Sprintf("https://%s/api/v1/query", dsPath)
	body := map[string]string{
		"query": metricsExpr,
		"time":  strconv.Itoa(t),
	}

	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
}

// getO11yLoki query logs from Loki
func (c *O11yClient) getO11yLoki(dsPath, logExpr string, startTime, endTime int) ([]interface{}, error) {
	url := fmt.Sprintf("https://%s/loki/api/v1/query_range", dsPath)
	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()
}

// makeRequest
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")

	// 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
}
