package central_thanos

import (
	"context"
	"fmt"
	"strconv"
	"strings"

	"github.com/pingcap/dbaas/central/metrics/prom"
	"github.com/pingcap/dbaas/pkg/tidb/component"
	"github.com/pingcap/errors"
	"github.com/prometheus/common/model"
	"github.com/tidbcloud/metrics/internal/datasource"
)

func QueryNodeMemoryUsedBytes(ctx context.Context, clusterID uint64) (*Response, error) {
	promql := fmt.Sprintf("process_resident_memory_bytes{instance=~\"db-.*\",kubernetes_namespace=\"tidb%d\"} or tiflash_proxy_process_resident_memory_bytes{instance=~\"db-.*\",kubernetes_namespace=\"tidb%d\"}", clusterID, clusterID)
	return doInstantQuery(ctx, promql)
}

func QueryNodeCPUUsedSeconds(ctx context.Context, clusterID uint64) (*Response, error) {
	promql := fmt.Sprintf("process_cpu_seconds_total{instance=~\"db-.*\",kubernetes_namespace=\"tidb%d\"} or tiflash_proxy_process_cpu_seconds_total{instance=~\"db-.*\",kubernetes_namespace=\"tidb%d\"}", clusterID, clusterID)
	return doInstantQuery(ctx, promql)
}

func QueryNodeStorageUsedBytes(ctx context.Context, clusterID uint64) (*Response, error) {
	promql := fmt.Sprintf("sum(tikv_engine_size_bytes{instance=~\"db-.*\",kubernetes_namespace=\"tidb%d\"}) by (cluster,cluster_id,component,instance,kubernetes_namespace,project,tenant,tenant_id) or tiflash_system_current_metric_StoreSizeUsed{instance=~\"db-.*\",kubernetes_namespace=\"tidb%d\"}", clusterID, clusterID)
	return doInstantQuery(ctx, promql)
}

func QueryNodeStorageCapacityBytes(ctx context.Context, clusterID uint64) (*Response, error) {
	promql := fmt.Sprintf("sum(pd_cluster_status{type=\"storage_capacity\", kubernetes_namespace=\"tidb%d\"})", clusterID)
	return doInstantQuery(ctx, promql)
}

func QueryDBQueriesTotal(ctx context.Context, clusterID uint64) (*Response, error) {
	promql := fmt.Sprintf("tidb_executor_statement_total{instance=~\"db-.*\",kubernetes_namespace=\"tidb%d\"}", clusterID)
	return doInstantQuery(ctx, promql)
}

func QueryDBFailedQueriesTotal(ctx context.Context, clusterID uint64) (*Response, error) {
	promql := fmt.Sprintf("tidb_server_execute_error_total{instance=~\"db-.*\",kubernetes_namespace=\"tidb%d\"}", clusterID)
	return doInstantQuery(ctx, promql)
}

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

func QueryDBQueryDurationSeconds(ctx context.Context, clusterID uint64) (*Response, error) {
	promql := fmt.Sprintf("{__name__=~\"tidb_server_handle_query_duration_seconds.+\",instance=~\"db-.*\",kubernetes_namespace=\"tidb%d\"}", clusterID)
	return doInstantQuery(ctx, promql)
}

func getFloatValue(valueTuple []interface{}) (float64, error) {
	if len(valueTuple) != 2 {
		return 0, errors.Errorf("failed to query value(%+v)", valueTuple)
	}
	// valueTuple[0] is the timestamp.
	valueStr, ok := valueTuple[1].(string)
	if !ok {
		return 0, errors.Errorf("failed to convert value(%+v)", valueTuple)
	}
	value, err := strconv.ParseFloat(valueStr, 64)
	if err != nil {
		return 0, errors.Errorf("failed to parse value(%s)", valueStr)
	}

	return value, nil
}

func getIntValue(valueTuple []interface{}) (int64, error) {
	if len(valueTuple) != 2 {
		return 0, errors.Errorf("failed to query value(%+v)", valueTuple)
	}
	// valueTuple[0] is the timestamp.
	valueStr, ok := valueTuple[1].(string)
	if !ok {
		return 0, errors.Errorf("failed to convert value(%+v)", valueTuple)
	}
	value, err := strconv.ParseInt(valueStr, 10, 64)
	if err != nil {
		return 0, errors.Errorf("failed to parse value(%s)", valueStr)
	}

	return value, nil
}

func BuildNodeCPUSecondsTotalQuerier(clusterID uint64, clusterName string) datasource.MetricQuerierFunc {
	return func(ctx context.Context) (prom.Metric, error) {
		vec := prom.VecMetric{
			Name:    "tidbcloud_node_cpu_seconds_total",
			Help:    "The CPU usage of TiDB/TiKV/TiFlash nodes",
			Type:    "counter",
			Metrics: []*prom.VecMetricElement{},
		}

		thanosNodeCPURespResult, err := QueryNodeCPUUsedSeconds(ctx, clusterID)
		if err != nil || thanosNodeCPURespResult.Status != "success" {
			return &vec, errors.Errorf("failed to query used node cpu seconds, status: %s", thanosNodeCPURespResult.Status)
		}

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

			if m.Component == component.PD.String() {
				continue
			}

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

		return &vec, nil
	}
}

func BuildNodeMemoryUsedQuerier(clusterID uint64, clusterName string, useClusterMetrics bool) datasource.MetricQuerierFunc {
	return func(ctx context.Context) (prom.Metric, error) {
		vec := prom.VecMetric{
			Name:    "tidbcloud_node_memory_used_bytes",
			Help:    "The used memory bytes of TiDB/TiKV/TiFlash nodes",
			Type:    "gauge",
			Metrics: []*prom.VecMetricElement{},
		}

		if !useClusterMetrics {
			// Check id against the hardcoded whitelist.
			if err := datasource.IsOKToScrapeNodeMemory(clusterID); err != nil {
				return &vec, err
			}
		}

		thanosNodeMemoryRespResult, err := QueryNodeMemoryUsedBytes(ctx, clusterID)
		if err != nil || thanosNodeMemoryRespResult.Status != "success" {
			return &vec, errors.Errorf("failed to query used node memory, status: %s", thanosNodeMemoryRespResult.Status)
		}

		for _, result := range thanosNodeMemoryRespResult.Data.Result {
			m := result.LabelSet

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

			if m.Component == component.PD.String() {
				continue
			}

			if useClusterMetrics {
				vec.Metrics = append(vec.Metrics, &prom.VecMetricElement{
					Labels: []prom.MetricLabel{
						{Name: datasource.LabelInstance, Value: datasource.UnifyDBName(m.Instance)},
						{Name: datasource.LabelComponent, Value: datasource.UnifyDBName(m.Component)},
						{Name: datasource.LabelCluster, Value: clusterName},
					},
					Value: model.SampleValue(value),
				})
			} else {
				vec.Metrics = append(vec.Metrics, &prom.VecMetricElement{
					Labels: []prom.MetricLabel{
						{Name: datasource.InstanceName, Value: datasource.UnifyDBName(m.Instance)},
						{Name: datasource.ComponentType, Value: datasource.UnifyDBName(m.Component)},
					},
					Value: model.SampleValue(value),
				})
			}
		}

		return &vec, nil
	}
}

func BuildNodeStorageUsedBytesQuerier(clusterID uint64, clusterName string) datasource.MetricQuerierFunc {
	return func(ctx context.Context) (prom.Metric, error) {
		vec := prom.VecMetric{
			Name:    "tidbcloud_node_storage_used_bytes",
			Help:    "The disk usage bytes of TiKV/TiFlash nodes",
			Type:    "gauge",
			Metrics: []*prom.VecMetricElement{},
		}

		thanosNodeStorageRespResult, err := QueryNodeStorageUsedBytes(ctx, clusterID)
		if err != nil || thanosNodeStorageRespResult.Status != "success" {
			return &vec, errors.Errorf("failed to query used node storage, status: %s", thanosNodeStorageRespResult.Status)
		}

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

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

		return &vec, nil
	}
}

func BuildDBQueriesTotalQuerier(clusterID uint64, clusterName string) datasource.MetricQuerierFunc {
	return func(ctx context.Context) (prom.Metric, error) {
		vec := prom.VecMetric{
			Name:    "tidbcloud_db_queries_total",
			Help:    "The total number of statements executed",
			Type:    "counter",
			Metrics: []*prom.VecMetricElement{},
		}

		thanosDBQueriesRespResult, err := QueryDBQueriesTotal(ctx, clusterID)
		if err != nil || thanosDBQueriesRespResult.Status != "success" {
			return &vec, errors.Errorf("failed to query db queries total, status: %s", thanosDBQueriesRespResult.Status)
		}

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

			vec.Metrics = append(vec.Metrics, &prom.VecMetricElement{
				Labels: []prom.MetricLabel{
					{Name: datasource.LabelInstance, Value: datasource.UnifyDBName(m.Instance)},
					{Name: datasource.LabelComponent, Value: datasource.UnifyDBName(m.Component)},
					{Name: datasource.LabelCluster, Value: clusterName},
					{Name: datasource.LabelSQLType, Value: m.Type},
				},
				Value: model.SampleValue(value),
			})
		}

		return &vec, nil
	}
}

func BuildDBFailedQueriesTotalQuerier(clusterID uint64, clusterName string) datasource.MetricQuerierFunc {
	return func(ctx context.Context) (prom.Metric, error) {
		vec := prom.VecMetric{
			Name:    "tidbcloud_db_failed_queries_total",
			Help:    "The total number of execution errors",
			Type:    "counter",
			Metrics: []*prom.VecMetricElement{},
		}

		thanosDBFailedQueriesRespResult, err := QueryDBFailedQueriesTotal(ctx, clusterID)
		if err != nil || thanosDBFailedQueriesRespResult.Status != "success" {
			return &vec, errors.Errorf("failed to query db failed queries total, status: %s", thanosDBFailedQueriesRespResult.Status)
		}

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

			vec.Metrics = append(vec.Metrics, &prom.VecMetricElement{
				Labels: []prom.MetricLabel{
					{Name: datasource.LabelInstance, Value: datasource.UnifyDBName(m.Instance)},
					{Name: datasource.LabelComponent, Value: datasource.UnifyDBName(m.Component)},
					{Name: datasource.LabelCluster, Value: clusterName},
					{Name: datasource.LabelType, Value: m.Type},
				},
				Value: model.SampleValue(value),
			})
		}

		return &vec, nil
	}
}

func BuildDBQueryDurationSecondsQuerier(clusterID uint64, clusterName string) datasource.MetricQuerierFunc {
	return func(ctx context.Context) (prom.Metric, error) {
		vec := prom.VecMetric{
			Name:    "tidbcloud_db_query_duration_seconds",
			Help:    "The histogram of the duration of statements",
			Type:    "histogram",
			Metrics: []*prom.VecMetricElement{},
		}

		thanosDBQueryDurationRespResult, err := QueryDBQueryDurationSeconds(ctx, clusterID)
		if err != nil || thanosDBQueryDurationRespResult.Status != "success" {
			return nil, errors.Errorf("failed to query db query duration seconds, status: %s", thanosDBQueryDurationRespResult.Status)
		}

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

			labels := []prom.MetricLabel{
				{Name: datasource.LabelInstance, Value: datasource.UnifyDBName(m.Instance)},
				{Name: datasource.LabelComponent, Value: datasource.UnifyDBName(m.Component)},
				{Name: datasource.LabelCluster, Value: clusterName},
				{Name: datasource.LabelSQLType, Value: m.SqlType},
			}

			var t string
			if strings.HasSuffix(m.MetricName, prom.VecMetricElementSum) {
				t = prom.VecMetricElementSum
			} else if strings.HasSuffix(m.MetricName, prom.VecMetricElementCount) {
				t = prom.VecMetricElementCount
			} else if strings.HasSuffix(m.MetricName, prom.VecMetricElementBucket) {
				t = prom.VecMetricElementBucket
				labels = append(labels, prom.MetricLabel{Name: datasource.LabelLE, Value: m.Le})
			} else {
				return nil, errors.Errorf("unexpected metric name: %s", m.MetricName)
			}

			vec.Metrics = append(vec.Metrics, &prom.VecMetricElement{
				Type:   t,
				Labels: labels,
				Value:  model.SampleValue(value),
			})
		}

		return &vec, nil
	}
}

func BuildDBConnectionsQuerier(clusterID uint64, clusterName string) datasource.MetricQuerierFunc {
	return func(ctx context.Context) (prom.Metric, error) {
		vec := prom.VecMetric{
			Name:    "tidbcloud_db_connections",
			Help:    "Current number of connections in TiDB server",
			Type:    "gauge",
			Metrics: []*prom.VecMetricElement{},
		}

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

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

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

		return &vec, nil
	}
}
