package ccb_application_router

import (
	"context"
	"fmt"
	"time"

	. "github.com/onsi/gomega"
	"github.com/pingcap/endless/pkg/workloadnode"
	"github.com/pingcap/log"
	"github.com/pingcap/test-infra/sdk/core"
	"github.com/pingcap/test-infra/sdk/resource"
	v1 "github.com/prometheus/client_golang/api/prometheus/v1"
	"github.com/prometheus/common/model"
	"go.uber.org/zap"
)

// workload0-0.workload0-peer
var ccbloadPrometheusConfigTmplate = `# my global config
global:
  scrape_interval:     15s
  evaluation_interval: 15s
scrape_configs:
  - job_name: 'prometheus'
    static_configs:
    - targets: ['localhost:9090']
  - job_name: 'ccbload'
    scrape_interval: 1s
    static_configs:
    - targets: [%s]
  - job_name: 'jvm'
    scrape_interval: 1s
    static_configs:
    - targets: [%s] 
`

func UpdatePrometheusConfig(suiteTestCtx core.TestContext, ctx context.Context, peerCount int) {
	clientMonitor := suiteTestCtx.Resource("clientmonitor").(resource.WorkloadNode)
	var ccbloadTargets string
	var ccbloadJVMTargets string
	for i := 0; i < peerCount; i++ {
		ccbloadTargets += fmt.Sprintf("'workload0-%v.workload0-peer:10000',", i)
		ccbloadJVMTargets += fmt.Sprintf("'workload0-%v.workload0-peer:8081',", i)
	}
	ccbloadPrometheusConfig := fmt.Sprintf(ccbloadPrometheusConfigTmplate, ccbloadTargets[0:len(ccbloadTargets)-1], ccbloadJVMTargets[0:len(ccbloadJVMTargets)-1])
	cmd := fmt.Sprintf("echo \"%s\" > /etc/prometheus/prometheus.yml", ccbloadPrometheusConfig)
	err := workloadnode.CommandContext(ctx, cmd).Run(clientMonitor)
	Expect(err).Should(BeNil())
	err = workloadnode.CommandContext(ctx, "ps aux | grep prometheus | grep -v grep | awk -F' ' '{print $1}' | xargs kill -s SIGHUP || true").Run(clientMonitor)
	Expect(err).Should(BeNil())
}

func MetricsMax(value model.Value) float64 {
	maxVal := model.SampleValue(0.0)
	switch value.Type() {
	case model.ValMatrix:
		valueMatrix := value.(model.Matrix)
		if len(valueMatrix) > 0 {
			for _, sample := range valueMatrix[0].Values {
				if sample.Value > maxVal {
					maxVal = sample.Value
				}
			}
		}
	default:
		log.S().Panicf("MetricDelta Type[%v] should be model.ValMatrix", value.Type().String())
	}
	return float64(maxVal)
}

func MetricsDelta(value model.Value) float64 {
	deltaVal := model.SampleValue(0.0)
	switch value.Type() {
	case model.ValMatrix:
		valueMatrix := value.(model.Matrix)
		if len(valueMatrix) > 0 {
			if len(valueMatrix[0].Values) > 1 {
				first := valueMatrix[0].Values[0]
				last := valueMatrix[0].Values[len(valueMatrix[0].Values)-1]
				deltaVal = last.Value - first.Value
			}
		}
	default:
		log.S().Panicf("MetricDelta Type[%v] should be model.ValMatrix", value.Type().String())
	}
	return float64(deltaVal)
}

type CheckPromQL struct {
	name  string
	query string
	step  time.Duration
	fn    func(value model.Value) float64 // processing the PromQL results
}

var metricsExpect = map[string]float64{
	"rawkvworkload.err":     0.0,
	"rawkvworkload.lat.avg": 1.0,   // s
	"rawkvworkload.lat.p99": 1.0,   // s
	"client.lat.avg":        0.002, // s
	"client.lat.p99":        0.01,  // s
	"client.err":            0,
}

func VerifyEachStageMetrics(ctx context.Context, ccbloadClientAPIPrometheus v1.API, promQLMetrics []CheckPromQL, StagesTSInfo []TestCaseStageInfo) {
	pass := true
	for i, stageInfo := range StagesTSInfo {
		// log.Info("", zap.Int("Stage", i), zap.String("Desc", stageInfo.Description))
		log.S().Infof("%v StageInfo: %v", i, stageInfo)
		for _, promQL := range promQLMetrics {
			val, warnings, err := ccbloadClientAPIPrometheus.QueryRange(ctx, promQL.query, v1.Range{Start: stageInfo.Start.UTC(), End: stageInfo.End.UTC(), Step: promQL.step})
			result := promQL.fn(val)
			log.Info("Metric Result", zap.String("Indicator", promQL.name), zap.Float64("Value", result), zap.Strings("Warning", warnings), zap.Error(err))
			expectVal, ok := metricsExpect[promQL.name]
			if ok {
				if result > expectVal {
					pass = false
				}
			}
		}
	}
	Expect(pass).Should(BeTrue())
	// Check the whole testcase
	val, warnings, err := ccbloadClientAPIPrometheus.QueryRange(ctx, "sum(rawkvworkload_trx_failures_total)", v1.Range{Start: StagesTSInfo[0].Start.UTC(), End: StagesTSInfo[len(StagesTSInfo)-1].End.UTC(), Step: 1 * time.Second})
	result := MetricsDelta(val)
	log.Info("During the whole process of testcase, ccbload.err: ", zap.String("Indicator", "rawkvworkload.err"), zap.Float64("Value", result), zap.Strings("Warning", warnings), zap.Error(err))
	Expect(result).Should(BeEquivalentTo(0.0))
}
