package main

import (
	"encoding/json"
	"os"
	"path/filepath"
	"regexp"
	"strings"
	"testing"

	"github.com/coreos/go-semver/semver"
	"github.com/onsi/gomega"
	"github.com/stretchr/testify/assert"
	"github.com/tidwall/gjson"
)

func TestPartialMarshall(t *testing.T) {
	var bs = `
{
	"a": [1,2,3,4],
	"b": {
		"hello":"world"
	}
}`
	var data = make(map[string]json.RawMessage)
	if err := json.Unmarshal([]byte(bs), &data); err != nil {
		t.Fatal(err)
	}
	t.Logf("%+v", data)
}

func TestInsertDS(t *testing.T) {
	var db = `
{
  "templating": {
    "list": [
      {
        "allValue": null,
        "datasource": "Prometheus1",
        "label": "tidb_cluster",
        "multi": false,
        "name": "tidb_cluster",
        "type": "query"
      },
      {
        "allValue": null,
        "datasource": "Prometheus2",
        "label": "tidb_cluster",
        "multi": false,
        "name": "tidb_cluster",
        "type": "query"
      }
    ]
}
`

	p := opClusterProcessor{
		v: db,
	}

	v := p.insertDS()

	g := gomega.NewWithT(t)

	list := gjson.Get(v, "templating.list").Array()
	g.Expect(len(list)).To(gomega.Equal(6))

	g.Expect(gjson.Get(v, "templating.list.0.datasource").Str).To(gomega.Equal("Prometheus1"))
	g.Expect(gjson.Get(v, "templating.list.2.datasource").Str).To(gomega.Equal("Prometheus-fake"))
	g.Expect(gjson.Get(v, "templating.list.3.name").Str).To(gomega.Equal("org"))
	g.Expect(gjson.Get(v, "templating.list.4.name").Str).To(gomega.Equal("tidb_version"))
}

func TestReplaceVariablesRegexp(t *testing.T) {
	re := regexp.MustCompile(`label_values\((\w+),\s?(\w+)\)`)
	{
		match := re.FindStringSubmatch("label_values(tikv_engine_size_bytes, instance)")
		if len(match) != 3 {
			t.Error("sub match count is not correct", match)
		}
	}
	{
		match := re.FindStringSubmatch("label_values(tikv_engine_size_bytes{}, instance)")
		if len(match) != 0 {
			t.Error("sub match count is not correct", match)
		}
	}
}

func TestReplaceVariables(t *testing.T) {
	var bs = `
{
  "templating": {
    "list": [
      {
        "allValue": null,
        "current": {
          "isNone": true,
          "selected": false,
          "text": "None",
          "value": ""
        },
        "datasource": "Prometheus",
        "definition": "",
        "description": null,
        "error": null,
        "hide": 2,
        "includeAll": false,
        "label": "K8s-cluster",
        "multi": false,
        "name": "k8s_cluster",
        "options": [],
        "query": {
          "query": "label_values(tikv_engine_size_bytes, k8s_cluster)",
          "refId": "Prometheus-k8s_cluster-Variable-Query"
        },
        "refresh": 2,
        "regex": "",
        "skipUrlSync": false,
        "sort": 1,
        "tagValuesQuery": "",
        "tags": [],
        "tagsQuery": "",
        "type": "query",
        "useTags": false
      },
      {
        "allValue": ".*",
        "current": {
          "selected": false,
          "text": "",
          "value": ""
        },
        "datasource": "Prometheus",
        "definition": "label_values(tikv_engine_size_bytes, instance)",
        "description": null,
        "error": null,
        "hide": 0,
        "includeAll": true,
        "label": "TiKV",
        "multi": false,
        "name": "instance",
        "options": [],
        "query": {
          "query": "label_values(tikv_engine_size_bytes, instance)",
          "refId": "StandardVariableQuery"
        },
        "refresh": 1,
        "regex": "",
        "skipUrlSync": false,
        "sort": 1,
        "tagValuesQuery": "",
        "tags": [],
        "tagsQuery": "",
        "type": "query",
        "useTags": false
      },
      {
        "allValue": null,
        "current": {
          "selected": false,
          "text": "1373933076658094280",
          "value": "1373933076658094280"
        },
        "datasource": "Prometheus-fake",
        "definition": "label_values({component=\"pd\"}, tidb_cluster)",
        "description": null,
        "error": null,
        "hide": 0,
        "includeAll": false,
        "label": "tidb_cluster",
        "multi": false,
        "name": "tidb_cluster",
        "options": [],
        "query": {
          "query": "label_values({component=\"pd\"}, tidb_cluster)",
          "refId": "StandardVariableQuery"
        },
        "refresh": 2,
        "regex": "",
        "skipUrlSync": false,
        "sort": 0,
        "tagValuesQuery": "",
        "tags": [],
        "tagsQuery": "",
        "type": "query",
        "useTags": false
      },
      {
        "description": null,
        "error": null,
        "hide": 2,
        "label": null,
        "name": "org",
        "query": "",
        "skipUrlSync": false,
        "type": "constant"
      },
      {
        "description": null,
        "error": null,
        "hide": 2,
        "label": null,
        "name": "tidb_version",
        "query": "",
        "skipUrlSync": false,
        "type": "constant"
      },
      {
        "description": null,
        "error": null,
        "hide": 2,
        "label": null,
        "name": "org_type",
        "query": "",
        "skipUrlSync": false,
        "type": "constant"
      }
    ]
  }`
	op := &opClusterProcessor{v: bs}
	result := op.replaceVariables()
	if gjson.Get(result.v, "templating.list.0.query.query").String() != `label_values(tikv_engine_size_bytes, k8s_cluster)` {
		t.Error("replace result incorrect")
	}
	if gjson.Get(result.v, "templating.list.1.query.query").String() != `label_values(tikv_engine_size_bytes{tidb_cluster="$tidb_cluster"}, instance)` {
		t.Error("replace result incorrect")
	}
}

func TestRecordingRulesReplaceWithTiDB(t *testing.T) {

	pattern := "../../tidb-dashboards/*/tidb.json"
	matches, err := filepath.Glob(pattern)
	assert.NoError(t, err)
	assert.NotEqual(t, len(matches), 0)

	baseVersion, err := semver.NewVersion("6.1.0")
	assert.NoError(t, err)

	for _, file := range matches {
		dir := filepath.Base(filepath.Dir(file))
		if !strings.HasPrefix(dir, "v") {
			continue
		}

		version := dir[1:]
		version = strings.Replace(version, ".x", ".0", 1)
		if semver.New(version).LessThan(*baseVersion) {
			continue
		}

		data, err := os.ReadFile(file)
		assert.NoError(t, err, file)
		r := []byte(AddPanelExprUseRecordingRule(file, string(data)))

		assert.Equal(t, strings.Count(string(r), "tidb_server_handle_query_duration_seconds_bucket:le_instance:sum:rate"), 4, file)
		assert.Equal(t, strings.Count(string(r), "tidb_server_handle_query_duration_seconds_bucket:le_sqltype:sum:rate"), 4, file)
		assert.Equal(t, strings.Count(string(r), "tidb_server_handle_query_duration_seconds_bucket:le:sum:rate"), 4, file)
	}
}

func TestRecordingRulesReplaceWithTiKV(t *testing.T) {

	pattern := "../../tidb-dashboards/*/tikv_details.json"
	matches, err := filepath.Glob(pattern)
	assert.NoError(t, err)
	assert.NotEqual(t, len(matches), 0)

	baseVersion, err := semver.NewVersion("6.1.0")
	assert.NoError(t, err)

	for _, file := range matches {
		dir := filepath.Base(filepath.Dir(file))
		if !strings.HasPrefix(dir, "v") {
			continue
		}

		version := dir[1:]
		version = strings.Replace(version, ".x", ".0", 1)
		if semver.New(version).LessThan(*baseVersion) {
			continue
		}

		data, err := os.ReadFile(file)
		assert.NoError(t, err, file)
		r := []byte(AddPanelExprUseRecordingRule(file, string(data)))

		assert.Equal(t, strings.Count(string(r), "tikv_grpc_msg_duration_seconds_bucket:le_type:sum:rate"), 1, file)
	}
}

func TestShouldApplyRecordingRule(t *testing.T) {
	assert.True(t, shouldApplyRecordingRule("./publish/internal-dashboards"))
	assert.True(t, shouldApplyRecordingRule("/tmp/build/publish/internal-dashboards"))
	assert.False(t, shouldApplyRecordingRule("./publish/external-dashboards"))
	assert.False(t, shouldApplyRecordingRule("./publish"))
}

func TestSharedResourceDiagnosisTemplate(t *testing.T) {
	path := "../../tidb-ng-dashboards/v9.0.x/shared_resource_diagnosis.json"
	data, err := os.ReadFile(path)
	assert.NoError(t, err)
	assert.True(t, json.Valid(data))

	source := string(data)
	var dashboard map[string]interface{}
	assert.NoError(t, json.Unmarshal(data, &dashboard))
	for _, panel := range dashboard["panels"].([]interface{}) {
		targets, _ := panel.(map[string]interface{})["targets"].([]interface{})
		for _, target := range targets {
			expr, _ := target.(map[string]interface{})["expr"].(string)
			assert.NotContains(t, expr, `cluster_id=~".+"`)
		}
	}
	assert.Equal(t, "[SharedScope]Shared Resource One Page", gjson.Get(source, "title").String())
	assert.Equal(t, "DS_TEST-CLUSTER", gjson.Get(source, "__inputs.0.name").String())
	assert.Equal(t, `label_values(tidb_server_query_total{sharedpool_id="$tidb_cluster"}, tidb_cluster_id)`, gjson.Get(source, "templating.list.1.query.query").String())
	assert.NotContains(t, source, `scope=\"user\"`)
	assert.NotContains(t, source, `tidb_cluster=~\"$cluster_id\"`)
	assert.NotContains(t, source, `job=\"tidb-tikv\"`)
	assert.NotContains(t, source, `job=\"tidb-pd\"`)
	assert.Contains(t, source, `sharedpool_id=\"$tidb_cluster\"`)
	assert.Contains(t, source, `tidb_cluster_id=~\"$cluster_id\"`)
	assert.Contains(t, source, `component=~\"worker-tidb|tidb\"`)
	assert.Equal(t, 11, strings.Count(source, `cluster-{{tidb_cluster_id}}`))
	assert.Equal(t, 3, strings.Count(source, `keyspace-{{keyspace_name}}`))
	assertSharedResourceScopeVariable(t, source)

	processor := NewNextGenProcessor(source, path)
	processed := processor.insertUID().replaceDS(datasource_name).replaceTitle().replaceVariables().insertDS()
	processed = strings.ReplaceAll(processed, `tidb_cluster=\"$tidb_cluster\"`, `tidb_cluster_id=\"$tidb_cluster\"`)
	processed = strings.ReplaceAll(processed, `tidb_cluster=~\"$tidb_cluster.*\"`, `tidb_cluster_id=\"$tidb_cluster\"`)

	assert.Equal(t, "[SharedScope]Shared Resource One Page-Clinic", gjson.Get(processed, "title").String())
	assert.NotContains(t, processed, "${DS_TEST-CLUSTER}")
	assert.Equal(t, "Prometheus-fake", gjson.Get(processed, "templating.list.0.datasource").String())
	assert.Equal(t, int64(2), gjson.Get(processed, "templating.list.0.hide").Int())
	assert.Equal(t, `label_values(tikv_engine_size_bytes, k8s_cluster)`, gjson.Get(processed, "templating.list.0.query.query").String())
	assert.Equal(t, `label_values({__name__=~"tikv_cpu_token_wait_duration_seconds_count|tikv_yatp_pool_keyspace_task_slice_cpu_duration_seconds_count",k8s_cluster="$k8s_cluster",tidb_cluster_id="$tidb_cluster"}, keyspace_id)`, gjson.Get(processed, "templating.list.2.query.query").String())
	assert.Equal(t, `label_values(tikv_engine_size_bytes{k8s_cluster="$k8s_cluster",tidb_cluster_id="$tidb_cluster"}, instance)`, gjson.Get(processed, "templating.list.3.query.query").String())
	assert.Contains(t, processed, `sharedpool_id=\"$tidb_cluster\"`)
	assert.Contains(t, processed, `tidb_cluster_id=~\"$cluster_id\"`)
	assert.Contains(t, processed, `component=~\"worker-tidb|tidb\"`)
	assert.NotContains(t, processed, `component=\"tidb-tikv\"`)
	assert.NotContains(t, processed, `component=\"tidb-pd\"`)
	assertSharedResourceScopeVariable(t, processed)
	tidbClusterVariables := 0
	for _, variable := range gjson.Get(processed, "templating.list").Array() {
		if variable.Get("name").String() == "tidb_cluster" {
			tidbClusterVariables++
		}
	}
	assert.Equal(t, 1, tidbClusterVariables)
}

func assertSharedResourceScopeVariable(t *testing.T, dashboard string) {
	t.Helper()

	variables := 0
	for _, variable := range gjson.Get(dashboard, "templating.list").Array() {
		if variable.Get("name").String() != "shared_resource_scope" {
			continue
		}
		variables++
		assert.Equal(t, "constant", variable.Get("type").String())
		assert.Equal(t, int64(2), variable.Get("hide").Int())
		assert.Equal(t, "resource_pool", variable.Get("query").String())
		assert.Equal(t, "resource_pool", variable.Get("current.value").String())
		skipURLSync := variable.Get("skipUrlSync")
		assert.True(t, skipURLSync.Exists())
		assert.False(t, skipURLSync.Bool())
	}
	assert.Equal(t, 1, variables)
}
