package prom

import (
	"bytes"
	"strings"
	"testing"

	"github.com/prometheus/common/model"
)

func TestAsInstanceLevelElement(t *testing.T) {
	elem := &VecMetricElement{
		Labels: []MetricLabel{
			{Name: "sql_type", Value: "Select"},
			{Name: "instance", Value: "tidb-0"},
			{Name: "component", Value: "tidb"},
			{Name: "cluster_name", Value: "my-premium"},
			{Name: "cluster_id", Value: "98765"},
			{Name: "project_id", Value: "67890"},
		},
		Value: model.SampleValue(142.5),
	}

	got := elem.asInstanceLevelElement()

	// Should have: sql_type, instance_name, instance_id
	// Should NOT have: instance, component, cluster_name, cluster_id, project_id
	labelMap := make(map[string]string)
	for _, l := range got.Labels {
		labelMap[l.Name] = l.Value
	}

	if _, ok := labelMap["instance"]; ok {
		t.Error("Premium element should not have 'instance' label")
	}
	if _, ok := labelMap["component"]; ok {
		t.Error("Premium element should not have 'component' label")
	}
	if _, ok := labelMap["project_id"]; ok {
		t.Error("Premium element should not have 'project_id' label")
	}
	if _, ok := labelMap["cluster_id"]; ok {
		t.Error("Premium element should not have 'cluster_id' label (should be renamed)")
	}
	if _, ok := labelMap["cluster_name"]; ok {
		t.Error("Premium element should not have 'cluster_name' label (should be renamed)")
	}

	if v, ok := labelMap["instance_id"]; !ok || v != "98765" {
		t.Errorf("Premium element 'instance_id' = %q, want %q", v, "98765")
	}
	if v, ok := labelMap["instance_name"]; !ok || v != "my-premium" {
		t.Errorf("Premium element 'instance_name' = %q, want %q", v, "my-premium")
	}
	if v, ok := labelMap["sql_type"]; !ok || v != "Select" {
		t.Errorf("Premium element 'sql_type' = %q, want %q", v, "Select")
	}
}

func TestAsInstanceLevelElement_CDC_Rename(t *testing.T) {
	elem := &VecMetricElement{
		Labels: []MetricLabel{
			{Name: "changefeed_id", Value: "cf-001"},
			{Name: "cluster_name", Value: "my-nextgen"},
			{Name: "cluster_id", Value: "55555"},
			{Name: "project_id", Value: "proj-001"},
		},
		Value: model.SampleValue(0.42),
	}

	got := elem.asInstanceLevelElement()

	labelMap := make(map[string]string)
	for _, l := range got.Labels {
		labelMap[l.Name] = l.Value
	}

	if _, ok := labelMap["cluster_id"]; ok {
		t.Error("instance-level element should not have cluster_id")
	}
	if _, ok := labelMap["cluster_name"]; ok {
		t.Error("instance-level element should not have cluster_name")
	}
	if _, ok := labelMap["project_id"]; ok {
		t.Error("instance-level element should not have project_id")
	}
	if v := labelMap["instance_id"]; v != "55555" {
		t.Errorf("instance_id = %q, want %q", v, "55555")
	}
	if v := labelMap["instance_name"]; v != "my-nextgen" {
		t.Errorf("instance_name = %q, want %q", v, "my-nextgen")
	}
}

func TestAsInstanceLevelElement_DropsNodeAndProjectLabels(t *testing.T) {
	elem := &VecMetricElement{
		Labels: []MetricLabel{
			{Name: "type", Value: "Select"},
			{Name: "instance", Value: "tidb-0"},
			{Name: "component", Value: "tidb"},
			{Name: "cluster_name", Value: "my-premium"},
			{Name: "cluster_id", Value: "98765"},
			{Name: "project_id", Value: "67890"},
		},
		Value: model.SampleValue(142.5),
	}

	got := elem.asInstanceLevelElement()

	labelMap := make(map[string]string)
	for _, l := range got.Labels {
		labelMap[l.Name] = l.Value
	}

	for _, removed := range []string{"instance", "component", "project_id"} {
		if _, ok := labelMap[removed]; ok {
			t.Errorf("instance-level element should not have %q", removed)
		}
	}
	if v := labelMap["type"]; v != "Select" {
		t.Errorf("type = %q, want %q", v, "Select")
	}
	if v := labelMap["instance_id"]; v != "98765" {
		t.Errorf("instance_id = %q, want %q", v, "98765")
	}
	if v := labelMap["instance_name"]; v != "my-premium" {
		t.Errorf("instance_name = %q, want %q", v, "my-premium")
	}
}

func TestVecMetric_EncodePrometheusFormat_Premium(t *testing.T) {
	m := &VecMetric{
		Name:        "tidbcloud_db_query_per_second",
		Help:        "QPS",
		Type:        "gauge",
		Timestamp:   1740211200,
		ServicePlan: "Premium",
		Metrics: []*VecMetricElement{
			{
				Labels: []MetricLabel{
					{Name: "instance", Value: "tidb-0"},
					{Name: "component", Value: "tidb"},
					{Name: "cluster_name", Value: "my-premium"},
					{Name: "cluster_id", Value: "98765"},
					{Name: "project_id", Value: "67890"},
					{Name: "type", Value: "Select"},
				},
				Value: model.SampleValue(142.5),
			},
		},
	}

	var buf bytes.Buffer
	if err := m.EncodePrometheusFormat(&buf); err != nil {
		t.Fatal(err)
	}

	output := buf.String()

	// Should contain renamed labels
	if !strings.Contains(output, `instance_id="98765"`) {
		t.Error("Premium output should contain instance_id")
	}
	if !strings.Contains(output, `instance_name="my-premium"`) {
		t.Error("Premium output should contain instance_name")
	}
	// Should NOT contain original/removed labels
	if strings.Contains(output, `cluster_id=`) {
		t.Error("Premium output should not contain cluster_id")
	}
	if strings.Contains(output, `project_id=`) {
		t.Error("Premium output should not contain project_id")
	}
	if strings.Contains(output, `component=`) {
		t.Error("Premium output should not contain component")
	}
	// The "instance" key should only appear as "instance_id" or "instance_name"
	if strings.Contains(output, `instance="tidb-0"`) {
		t.Error("Premium output should not contain instance label with node value")
	}
}

func TestVecMetric_EncodePrometheusFormat_EssentialV2MatchesPremiumLabels(t *testing.T) {
	metric := &VecMetric{
		Name:        "test_metric",
		Help:        "test help",
		Type:        "gauge",
		Timestamp:   123,
		ServicePlan: "Essential_V2",
		Metrics: []*VecMetricElement{{
			Labels: []MetricLabel{
				{Name: "cluster_name", Value: "essential-v2"},
				{Name: "cluster_id", Value: "123"},
				{Name: "project_id", Value: "456"},
				{Name: "instance", Value: "tidb-0"},
				{Name: "component", Value: "tidb"},
			},
			Value: model.SampleValue(1),
		}},
	}

	var buf bytes.Buffer
	if err := metric.EncodePrometheusFormat(&buf); err != nil {
		t.Fatalf("EncodePrometheusFormat() error = %v", err)
	}
	got := buf.String()
	for _, want := range []string{`instance_id="123"`, `instance_name="essential-v2"`} {
		if !strings.Contains(got, want) {
			t.Fatalf("output %q does not contain %q", got, want)
		}
	}
	for _, unwanted := range []string{"cluster_id=", "cluster_name=", "project_id=", "component=", "instance="} {
		if strings.Contains(got, unwanted) {
			t.Fatalf("output %q unexpectedly contains %q", got, unwanted)
		}
	}
}

func TestVecMetric_EncodePrometheusFormat_Essential(t *testing.T) {
	m := &VecMetric{
		Name:        "tidbcloud_db_query_per_second",
		Help:        "QPS",
		Type:        "gauge",
		Timestamp:   1740211200,
		ServicePlan: "Essential",
		Metrics: []*VecMetricElement{
			{
				Labels: []MetricLabel{
					{Name: "instance", Value: "tidb-0"},
					{Name: "component", Value: "tidb"},
					{Name: "cluster_name", Value: "my-essential"},
					{Name: "cluster_id", Value: "55555"},
					{Name: "project_id", Value: "proj-001"},
					{Name: "type", Value: "Select"},
				},
				Value: model.SampleValue(89.2),
			},
		},
	}

	var buf bytes.Buffer
	if err := m.EncodePrometheusFormat(&buf); err != nil {
		t.Fatal(err)
	}

	output := buf.String()

	// Essential is also instance-level and should use renamed instance labels.
	if !strings.Contains(output, `instance_id="55555"`) {
		t.Error("Essential output should contain instance_id")
	}
	if !strings.Contains(output, `instance_name="my-essential"`) {
		t.Error("Essential output should contain instance_name")
	}
	if strings.Contains(output, `cluster_id=`) {
		t.Error("Essential output should not contain cluster_id")
	}
	if strings.Contains(output, `project_id=`) {
		t.Error("Essential output should not contain project_id")
	}
	if strings.Contains(output, `instance="tidb-0"`) {
		t.Error("Essential output should not contain instance label")
	}
	if strings.Contains(output, `component="tidb"`) {
		t.Error("Essential output should not contain component label")
	}
}

func TestVecMetric_EncodePrometheusFormat_Starter(t *testing.T) {
	m := &VecMetric{
		Name:        "tidbcloud_db_total_connection",
		Help:        "Connections",
		Type:        "gauge",
		Timestamp:   1740211200,
		ServicePlan: "Starter",
		Metrics: []*VecMetricElement{
			{
				Labels: []MetricLabel{
					{Name: "instance", Value: "tidb-0"},
					{Name: "component", Value: "tidb"},
					{Name: "cluster_name", Value: "my-starter"},
					{Name: "cluster_id", Value: "44444"},
					{Name: "project_id", Value: "proj-002"},
				},
				Value: model.SampleValue(10),
			},
		},
	}

	var buf bytes.Buffer
	if err := m.EncodePrometheusFormat(&buf); err != nil {
		t.Fatal(err)
	}

	output := buf.String()

	// Starter behaves the same as other instance-level plans.
	if !strings.Contains(output, `instance_id="44444"`) {
		t.Error("Starter output should contain instance_id")
	}
	if !strings.Contains(output, `instance_name="my-starter"`) {
		t.Error("Starter output should contain instance_name")
	}
	if strings.Contains(output, `cluster_id=`) {
		t.Error("Starter output should not contain cluster_id")
	}
	if strings.Contains(output, `project_id=`) {
		t.Error("Starter output should not contain project_id")
	}
	if strings.Contains(output, `instance="tidb-0"`) {
		t.Error("Starter output should not contain instance label")
	}
	if strings.Contains(output, `component=`) {
		t.Error("Starter output should not contain component label")
	}
}

func TestVecMetric_EncodePrometheusFormat_Dedicated_Unchanged(t *testing.T) {
	m := &VecMetric{
		Name:        "tidbcloud_db_query_per_second",
		Help:        "QPS",
		Type:        "gauge",
		Timestamp:   1740211200,
		ServicePlan: "Dedicated",
		Metrics: []*VecMetricElement{
			{
				Labels: []MetricLabel{
					{Name: "instance", Value: "tidb-0"},
					{Name: "component", Value: "tidb"},
					{Name: "cluster_name", Value: "my-dedicated"},
					{Name: "cluster_id", Value: "12345"},
					{Name: "project_id", Value: "67890"},
					{Name: "type", Value: "Select"},
				},
				Value: model.SampleValue(142.5),
			},
		},
	}

	var buf bytes.Buffer
	if err := m.EncodePrometheusFormat(&buf); err != nil {
		t.Fatal(err)
	}

	output := buf.String()

	// Dedicated should keep ALL labels unchanged
	for _, label := range []string{
		`instance="tidb-0"`,
		`component="tidb"`,
		`cluster_name="my-dedicated"`,
		`cluster_id="12345"`,
		`project_id="67890"`,
		`type="Select"`,
	} {
		if !strings.Contains(output, label) {
			t.Errorf("Dedicated output should contain %s", label)
		}
	}
	if strings.Contains(output, "# TIMESTAMP ") {
		t.Error("Dedicated output should not contain # TIMESTAMP")
	}
}

func TestVecMetric_EncodePrometheusFormat_Dedicated_EmptyStillEmitsHelpAndType(t *testing.T) {
	m := &VecMetric{
		Name:        "tidbcloud_changefeed_status",
		Help:        "The status of a changefeed",
		Type:        "gauge",
		Timestamp:   1740211200,
		ServicePlan: "Dedicated",
	}

	var buf bytes.Buffer
	if err := m.EncodePrometheusFormat(&buf); err != nil {
		t.Fatal(err)
	}

	output := buf.String()
	if !strings.Contains(output, "# HELP tidbcloud_changefeed_status The status of a changefeed") {
		t.Error("Dedicated empty metric family should still emit # HELP")
	}
	if !strings.Contains(output, "# TYPE tidbcloud_changefeed_status gauge") {
		t.Error("Dedicated empty metric family should still emit # TYPE")
	}
	if strings.Contains(output, "# TIMESTAMP ") {
		t.Error("Dedicated empty metric family should not emit # TIMESTAMP")
	}
}

func TestIsInstanceLevelPlan(t *testing.T) {
	tests := []struct {
		plan string
		want bool
	}{
		{"Premium", true},
		{"premium", true},
		{" Premium ", true},
		{"Essential", true},
		{"Starter", true},
		{"PREMIUM", true},
		{"Dedicated", false},
		{"", false},
	}
	for _, tt := range tests {
		if got := isInstanceLevelPlan(tt.plan); got != tt.want {
			t.Errorf("isInstanceLevelPlan(%q) = %v, want %v", tt.plan, got, tt.want)
		}
	}
}
