package prometheus

import (
	"context"
	"fmt"
	"math"
	"strings"
	"time"

	"github.com/pingcap-inc/clinic/clinic_data_service/pkg/log"
	"github.com/prometheus/common/model"
	"go.uber.org/zap"
)

//	type TimeRange struct {
//		Start            time.Time
//		End              time.Time
//	}
func GetOneValueByDay(clusterId string, dayStr string, query string) (float64, error) {
	queryTime := GetDayQueryTime(dayStr)
	return GetOneValueByTime(clusterId, queryTime, query)
}

// GetOneValueBydate queries Prometheus for a single value at a specific date and time.
// The queryTimeStr format is "2023-08-05T00:00:00Z", UTC time str
func GetOneValueByTimeStr(clusterId string, queryTimeStr string, query string) (float64, error) {
	queryTime, err := time.Parse(time.RFC3339, queryTimeStr)
	if err != nil {
		return 0, err
	}
	return GetOneValueByTime(clusterId, queryTime, query)
}

// getOneValue is a helper function to query Prometheus for a single value at a specific time.
func GetOneValueByTime(clusterId string, queryTime time.Time, query string) (float64, error) {
	pc, err := CreatePrometheusClient(context.Background(), CreatePrometheusClientReq{ClusterID: clusterId})
	if err != nil {
		return 0, err
	}
	return pc.GetOneValueByTime(clusterId, queryTime, query)
}

// getOneValue is a helper function to query Prometheus for a single value at a specific time.
func (pcClient *PrometheusClient) GetOneValueByTime(clusterId string, queryTime time.Time, query string) (float64, error) {
	// pc, err := CreatePrometheusClient(context.Background(), CreatePrometheusClientReq{ClusterID: clusterId})
	// if err != nil {
	// 	return 0, err
	// }

	ctx, cancel := context.WithTimeout(context.Background(), 40*time.Second)
	defer cancel()

	log.PlainInfo("Querying",
		zap.String("query", query),
		zap.String("date", queryTime.Format(time.RFC3339)),
		zap.Any("queryTime", queryTime))

	result, err := pcClient.Query(ctx, query, queryTime)
	if err != nil {
		log.PlainError("Query failed", zap.Time("queryTime", queryTime), zap.Error(err))
		return 0, err
	}
	if result == nil {
		log.PlainWarn("Query result is nil", zap.Time("queryTime", queryTime))
		return 0, nil
	}

	// Analyze according to Vector first; if Scalar is returned, it is also compatible
	if vector, ok := result.(model.Vector); ok {
		if len(vector) == 0 {
			log.PlainWarn("Vector result is empty", zap.Time("queryTime", queryTime))
			return 0, nil
		}
		val := float64(vector[0].Value)
		return val, nil
	}
	if scalar, ok := result.(*model.Scalar); ok {
		return float64(scalar.Value), nil
	}
	return 0, nil
}

// getOneValue is a helper function to query Prometheus for a single value at a specific time.
func (pc *PrometheusClient) GetDigestArrayByTime(clusterId string, queryTime time.Time, query string) ([]DigestInfo, error) {
	digestArr := make([]DigestInfo, 0)
	// pc, err := CreatePrometheusClient(context.Background(), CreatePrometheusClientReq{ClusterID: clusterId})
	// if err != nil {
	// 	return digestArr, err
	// }
	ctx, cancel := context.WithTimeout(context.Background(), 40*time.Second)
	defer cancel()

	log.PlainInfo("Querying",
		zap.String("query", query),
		zap.String("date", queryTime.Format(time.RFC3339)),
		zap.Any("queryTime", queryTime))

	result, err := pc.Query(ctx, query, queryTime)
	if err != nil {
		log.PlainError("Query failed", zap.Time("queryTime", queryTime), zap.Error(err))
		return digestArr, err
	}
	if result == nil {
		log.PlainWarn("Query result is nil", zap.Time("queryTime", queryTime))
		return digestArr, nil
	}

	if vector, ok := result.(model.Vector); ok {
		for _, sample := range vector {
			// Get sql_digest from Metric labels
			digest := string(sample.Metric[model.LabelName("sql_digest")])
			// Get the sample value (CPU time)
			value := float64(sample.Value)
			value = math.Round(value*100) / 100 // Round to 2 decimal places

			// Accumulate in map
			digestArr = append(digestArr, DigestInfo{
				Digest: digest,
				Value:  value,
			})
		}
	} else {
		log.PlainWarn("Query result is not a vector", zap.Time("queryTime", queryTime))
	}

	return digestArr, nil
}

// GetLabelValuesByTime queries a metric at a specific time and extracts the given label values.
// Example: label = "instance" will return all instance values in the vector result.
func (pcClient *PrometheusClient) GetLabels(clusterId string, queryTime time.Time, query string, label string) ([]string, error) {
	values := make([]string, 0)
	// pc, err := CreatePrometheusClient(context.Background(), CreatePrometheusClientReq{ClusterID: clusterId})
	// if err != nil {
	// 	return values, err
	// }
	ctx, cancel := context.WithTimeout(context.Background(), 40*time.Second)
	defer cancel()

	log.PlainInfo("Querying",
		zap.String("query", query),
		zap.String("date", queryTime.Format(time.RFC3339)),
		zap.Any("queryTime", queryTime),
		zap.String("label", label))

	result, err := pcClient.Query(ctx, query, queryTime)
	if err != nil {
		log.PlainError("Query failed", zap.Time("queryTime", queryTime), zap.Error(err))
		return values, err
	}
	if result == nil {
		log.PlainWarn("Query result is nil", zap.Time("queryTime", queryTime))
		return values, nil
	}

	if vector, ok := result.(model.Vector); ok {
		uniq := make(map[string]struct{})
		for _, sample := range vector {
			v := string(sample.Metric[model.LabelName(label)])
			if v == "" {
				continue
			}
			if _, seen := uniq[v]; !seen {
				uniq[v] = struct{}{}
				values = append(values, v)
			}
		}
	} else {
		log.PlainWarn("Query result is not a vector", zap.Time("queryTime", queryTime))
	}
	return values, nil
}

func GetDigestArray(clusterId string, queryTime time.Time, query string) ([]TopSqlDigest, error) {
	digestArr := make([]TopSqlDigest, 0)
	pc, err := CreatePrometheusClient(context.Background(), CreatePrometheusClientReq{ClusterID: clusterId})
	if err != nil {
		return digestArr, err
	}
	ctx, cancel := context.WithTimeout(context.Background(), 40*time.Second)
	defer cancel()

	log.PlainInfo("Querying",
		zap.String("query", query),
		zap.String("date", queryTime.Format(time.RFC3339)),
		zap.Any("queryTime", queryTime))

	result, err := pc.Query(ctx, query, queryTime)
	if err != nil {
		log.PlainError("Query failed", zap.Time("queryTime", queryTime), zap.Error(err))
		return digestArr, err
	}
	if result == nil {
		log.PlainWarn("Query result is nil", zap.Time("queryTime", queryTime))
		return digestArr, nil
	}

	if vector, ok := result.(model.Vector); ok {
		for _, sample := range vector {
			// Get sql_digest from Metric labels
			digest := string(sample.Metric[model.LabelName("sql_digest")])
			value := float64(sample.Value)
			// value = math.Round(value*100) / 100 // Round to 2 decimal places

			// Accumulate in map
			digestArr = append(digestArr, TopSqlDigest{
				Digest: digest,
				Value:  value,
			})
		}
	} else {
		log.PlainWarn("Query result is not a vector", zap.Time("queryTime", queryTime))
	}

	return digestArr, nil
}
func (pc *PrometheusClient) GetDigestArray(clusterId string, queryTime time.Time, query string) ([]TopSqlDigest, error) {
	digestArr := make([]TopSqlDigest, 0)
	ctx, cancel := context.WithTimeout(context.Background(), 40*time.Second)
	defer cancel()

	log.PlainInfo("Querying",
		zap.String("query", query),
		zap.String("date", queryTime.Format(time.RFC3339)),
		zap.Any("queryTime", queryTime))

	result, err := pc.Query(ctx, query, queryTime)
	if err != nil {
		log.PlainError("Query failed", zap.Time("queryTime", queryTime), zap.Error(err))
		return digestArr, err
	}
	if result == nil {
		log.PlainWarn("Query result is nil", zap.Time("queryTime", queryTime))
		return digestArr, nil
	}

	if vector, ok := result.(model.Vector); ok {
		for _, sample := range vector {
			// Get sql_digest from Metric labels
			digest := string(sample.Metric[model.LabelName("sql_digest")])
			value := float64(sample.Value)
			// value = math.Round(value*100) / 100 // Round to 2 decimal places

			// Accumulate in map
			digestArr = append(digestArr, TopSqlDigest{
				Digest: digest,
				Value:  value,
			})
		}
	} else {
		log.PlainWarn("Query result is not a vector", zap.Time("queryTime", queryTime))
	}

	return digestArr, nil
}

/*
		curl -G 'https://www.ds.us-east-1.aws.observability.tidbcloud.com/internal/metrics/bd692fb5-6454-464a-b40d-ca0d9f188c12/api/v1/query' \
	      --data-urlencode 'query=topk(5, sum by (sql_digest) (sum_over_time(topsql_cpu_time_ms{sql_digest!="", instance=~"db-tikv-6.*", instance_type="tikv"}[30m])))' \
	      --data-urlencode 'time=2025-09-05T04:59:00Z' \ | jq -r
*/
func (pc *PrometheusClient) GetTopCpuTikvDigests(req GetTopCpuTikvDigestsReq) ([]DigestInfo, error) {
	query := fmt.Sprintf(`topk(%d, sum by (sql_digest) (sum_over_time(topsql_cpu_time_ms{sql_digest!="", instance=~"%s", instance_type="tikv"}[%s])))`, req.TopN, req.Instance, req.Interval)
	return pc.GetDigestArrayByTime(req.ClusterId, req.QueryTime, query)
}

type GetTopCpuTikvDigestsReq struct {
	ClusterId string
	QueryTime time.Time
	TopN      int
	Instance  string
	Interval  string
}

// QueryRangeMatrix executes a range query and returns the result as model.Matrix.
func (pc *PrometheusClient) QueryRangeMatrix(clusterId string, query string, startTime, endTime time.Time, step time.Duration) (model.Matrix, error) {
	// client, err := CreatePrometheusClient(context.Background(), CreatePrometheusClientReq{ClusterID: clusterId})
	// if err != nil {
	// 	return nil, fmt.Errorf("failed to create prometheus client: %w", err)
	// }
	log.PlainInfo(query, zap.Any("startTime", startTime), zap.Any("endTime", endTime))
	result, err := pc.QueryRange(context.Background(), query, startTime, endTime, step)
	if err != nil {
		log.PlainError("QueryRange failed", zap.Error(err))
		return nil, err
	}
	matrix, ok := result.(model.Matrix)
	if !ok {
		log.PlainError("QueryRange failed", zap.Error(err))
		return nil, err
	}
	return matrix, nil
}

//func buildInstanceRegex(instances []string) string {
//    // 需要把每个 instance 转义，避免 "." 变成正则
//    var escaped []string
//    for _, inst := range instances {
//        escaped = append(escaped, regexp.QuoteMeta(inst))
//    }
//    // a|b|c
//    return strings.Join(escaped, "|")
//}

func buildLabelFilter(labelName string, labelValues []string) string {
	parts := make([]string, 0, len(labelValues))
	for _, inst := range labelValues {
		parts = append(parts, fmt.Sprintf(`%s="%s"`, labelName, inst))
	}
	return strings.Join(parts, " or ")
}
