package shoot_prom

import (
	"context"
	"fmt"
	"strconv"

	"github.com/pkg/errors"
	"github.com/prometheus/common/model"
	"github.com/tidbcloud/metrics-server/central/metrics/prom"
	"github.com/tidbcloud/metrics-server/internal/datasource"
	"github.com/tidbcloud/metrics-server/internal/datasource/client"
)

func QueryDBQueryRate(ctx context.Context, clusterID uint64, metricName string) (*client.Response, error) {
	promql := fmt.Sprintf("sum by (type) (rate(tidb_executor_statement_total{instance=~\"db-.*\", kubernetes_namespace=\"tidb%d\"}[1m]))", clusterID)
	return client.DoInstantQuery(ctx, promql, metricName)
}

func BuildDBQueryRateQuerier(clusterID uint64) datasource.MetricQuerierFunc {
	return func(ctx context.Context) (prom.Metric, error) {
		vec := prom.VecMetric{
			Name:    "tidbcloud_db_query_rate",
			Help:    "Queries per second by statement type",
			Type:    "gauge",
			Metrics: []*prom.VecMetricElement{},
		}

		thanosDBQueryRateRespResult, err := QueryDBQueryRate(ctx, clusterID, vec.Name)
		if err != nil || thanosDBQueryRateRespResult.Status != "success" {
			return &vec, errors.Errorf("failed to query db query rate, err: %v, result: %+v", err, thanosDBQueryRateRespResult)
		}

		for _, result := range thanosDBQueryRateRespResult.Data.Result {
			m := result.LabelSet
			value, err := datasource.GetFloatValue(result.Value)
			if err != nil {
				return &vec, err
			}

			vec.Metrics = append(vec.Metrics, &prom.VecMetricElement{
				Labels: []prom.MetricLabel{
					{Name: datasource.LabelSQLType, Value: m.Type},
				},
				Value: model.SampleValue(value),
			})
		}

		return &vec, nil
	}
}

func QueryDBFailedQueryRate(ctx context.Context, clusterID uint64, metricName string) (*client.Response, error) {
	promql := fmt.Sprintf("sum by (type) (rate(tidb_server_execute_error_total{instance=~\"db-.*\", kubernetes_namespace=\"tidb%d\"}[1m]))", clusterID)
	return client.DoInstantQuery(ctx, promql, metricName)
}

func BuildDBFailedQueryRateQuerier(clusterID uint64) datasource.MetricQuerierFunc {
	return func(ctx context.Context) (prom.Metric, error) {
		vec := prom.VecMetric{
			Name:    "tidbcloud_db_failed_query_rate",
			Help:    "Failed queries per second",
			Type:    "gauge",
			Metrics: []*prom.VecMetricElement{},
		}

		thanosDBFailedQueryRateRespResult, err := QueryDBFailedQueryRate(ctx, clusterID, vec.Name)
		if err != nil || thanosDBFailedQueryRateRespResult.Status != "success" {
			return &vec, errors.Errorf("failed to query db failed query rate, err: %v, result: %+v", err, thanosDBFailedQueryRateRespResult)
		}

		for _, result := range thanosDBFailedQueryRateRespResult.Data.Result {
			m := result.LabelSet
			value, err := datasource.GetFloatValue(result.Value)
			if err != nil {
				return &vec, err
			}

			vec.Metrics = append(vec.Metrics, &prom.VecMetricElement{
				Labels: []prom.MetricLabel{
					{Name: datasource.LabelType, Value: m.Type},
				},
				Value: model.SampleValue(value),
			})
		}

		return &vec, nil
	}
}

func QueryDBQueryDuration(ctx context.Context, clusterID uint64, quantile uint, metricName string) (*client.Response, error) {
	promql := fmt.Sprintf("histogram_quantile(0.%d, sum by (sql_type, le) (rate(tidb_server_handle_query_duration_seconds_bucket{instance=~\"db-.*\", kubernetes_namespace=\"tidb%d\"}[1m])))", quantile, clusterID)
	return client.DoInstantQuery(ctx, promql, fmt.Sprintf("metricName%d", quantile))
}

func BuildDBQueryDurationQuerier(clusterID uint64, quantile uint) datasource.MetricQuerierFunc {
	return func(ctx context.Context) (prom.Metric, error) {
		vec := prom.VecMetric{
			Name:    "tidbcloud_db_query_duration_seconds",
			Help:    "Queries durations of different quantiles aggregated in last minute by statement type",
			Type:    "gauge",
			Metrics: []*prom.VecMetricElement{},
		}

		thanosDBQueryDurationRespResult, err := QueryDBQueryDuration(ctx, clusterID, quantile, vec.Name)
		if err != nil || thanosDBQueryDurationRespResult.Status != "success" {
			return &vec, errors.Errorf("failed to query db query duration, err: %v, result: %+v", err, thanosDBQueryDurationRespResult)
		}

		quantileLabelValue := strconv.FormatUint(uint64(quantile), 10)

		for _, result := range thanosDBQueryDurationRespResult.Data.Result {
			m := result.LabelSet
			value, err := datasource.GetFloatValue(result.Value)
			if err != nil {
				return &vec, err
			}

			vec.Metrics = append(vec.Metrics, &prom.VecMetricElement{
				Labels: []prom.MetricLabel{
					{Name: "quantile", Value: quantileLabelValue},
					{Name: datasource.LabelSQLType, Value: m.SqlType},
				},
				Value: model.SampleValue(value),
			})
		}

		return &vec, nil
	}
}

func QueryCPUUsageCores(ctx context.Context, clusterID uint64, metricName string) (*client.Response, error) {
	promql := fmt.Sprintf("sum by (instance, component)(rate(process_cpu_seconds_total{component=~\"tidb|tikv|tiflash\", instance=~\"db-.*\", kubernetes_namespace=\"tidb%d\"}[1m]))", clusterID)
	return client.DoInstantQuery(ctx, promql, metricName)
}

func BuildCPUUsageCoresQuerier(clusterID uint64) datasource.MetricQuerierFunc {
	return func(ctx context.Context) (prom.Metric, error) {
		vec := prom.VecMetric{
			Name:    "tidbcloud_process_cpu_seconds_rate",
			Help:    "CPU usage percent by instances",
			Type:    "gauge",
			Metrics: []*prom.VecMetricElement{},
		}

		thanosCPUUsageCoresRespResult, err := QueryCPUUsageCores(ctx, clusterID, vec.Name)
		if err != nil || thanosCPUUsageCoresRespResult.Status != "success" {
			return &vec, errors.Errorf("failed to query cpu usage cores, err: %v, result: %+v", err, thanosCPUUsageCoresRespResult)
		}

		for _, result := range thanosCPUUsageCoresRespResult.Data.Result {
			m := result.LabelSet
			value, err := datasource.GetFloatValue(result.Value)
			if err != nil {
				return &vec, err
			}

			vec.Metrics = append(vec.Metrics, &prom.VecMetricElement{
				Labels: []prom.MetricLabel{
					{Name: datasource.InstanceName, Value: datasource.UnifyDBName(string(m.Instance))},
					{Name: datasource.ComponentType, Value: datasource.UnifyDBName(string(m.Component))},
				},
				Value: model.SampleValue(value),
			})
		}

		return &vec, nil
	}
}

func QueryDBConnections(ctx context.Context, clusterID uint64, metricName string) (*client.Response, error) {
	promql := fmt.Sprintf("sum(tidb_server_connections{instance=~\"db-.*\", kubernetes_namespace=\"tidb%d\"})", clusterID)
	return client.DoInstantQuery(ctx, promql, metricName)
}

func BuildDBConnectionsQuerier(clusterID uint64) datasource.MetricQuerierFunc {
	return func(ctx context.Context) (prom.Metric, error) {
		vec := prom.SingleMetric{
			Name:  "tidbcloud_db_connection_count",
			Help:  "Current connection count",
			Type:  "gauge",
			Value: 0,
		}

		thanosDBConnectionsRespResult, err := QueryDBConnections(ctx, clusterID, vec.Name)
		if err != nil || thanosDBConnectionsRespResult.Status != "success" {
			return &vec, errors.Errorf("failed to query db connections, err: %v, result: %+v", err, thanosDBConnectionsRespResult)
		}

		if len(thanosDBConnectionsRespResult.Data.Result) > 0 {
			result := thanosDBConnectionsRespResult.Data.Result[0]

			value, err := datasource.GetFloatValue(result.Value)
			if err != nil {
				return &vec, err
			}

			vec.Value = model.SampleValue(value)
		}

		return &vec, nil
	}
}

func QueryStorageUsageBytes(ctx context.Context, clusterID uint64, metricName string) (*client.Response, error) {
	promql := fmt.Sprintf("sum(pd_cluster_status{type=\"storage_size\", instance=~\"db-.*\", kubernetes_namespace=\"tidb%d\"})", clusterID)
	return client.DoInstantQuery(ctx, promql, metricName)
}

func BuildStorageUsageBytesQuerier(clusterID uint64) datasource.MetricQuerierFunc {
	return func(ctx context.Context) (prom.Metric, error) {
		vec := prom.SingleMetric{
			Name:  "tidbcloud_storage_used_bytes",
			Help:  "Current used storage size in bytes",
			Type:  "gauge",
			Value: 0,
		}

		thanosStorageUsageBytesRespResult, err := QueryStorageUsageBytes(ctx, clusterID, vec.Name)
		if err != nil || thanosStorageUsageBytesRespResult.Status != "success" {
			return &vec, errors.Errorf("failed to query storage usage bytes, err: %v, result: %+v", err, thanosStorageUsageBytesRespResult)
		}

		if len(thanosStorageUsageBytesRespResult.Data.Result) > 0 {
			result := thanosStorageUsageBytesRespResult.Data.Result[0]

			value, err := datasource.GetFloatValue(result.Value)
			if err != nil {
				return &vec, err
			}

			vec.Value = model.SampleValue(value)
		}

		return &vec, nil
	}
}

func QueryStorageCapacityBytes(ctx context.Context, clusterID uint64, metricName string) (*client.Response, error) {
	promql := fmt.Sprintf("sum(pd_cluster_status{type=\"storage_capacity\", instance=~\"db-.*\", kubernetes_namespace=\"tidb%d\"})", clusterID)
	return client.DoInstantQuery(ctx, promql, metricName)
}

func BuildStorageCapacityBytesQuerier(clusterID uint64) datasource.MetricQuerierFunc {
	return func(ctx context.Context) (prom.Metric, error) {
		vec := prom.SingleMetric{
			Name:  "tidbcloud_storage_capacity_bytes",
			Help:  "Current storage capacity in bytes",
			Type:  "gauge",
			Value: 0,
		}

		thanosStorageCapacityBytesRespResult, err := QueryStorageCapacityBytes(ctx, clusterID, vec.Name)
		if err != nil || thanosStorageCapacityBytesRespResult.Status != "success" {
			return &vec, errors.Errorf("failed to query storage capacity bytes, err: %v, result: %+v", err, thanosStorageCapacityBytesRespResult)
		}

		if len(thanosStorageCapacityBytesRespResult.Data.Result) > 0 {
			result := thanosStorageCapacityBytesRespResult.Data.Result[0]

			value, err := datasource.GetFloatValue(result.Value)
			if err != nil {
				return &vec, err
			}

			vec.Value = model.SampleValue(value)
		}

		return &vec, nil
	}
}
