// SPDX-License-Identifier: GPL-3.0-or-later

package chartengine

import (
	"testing"

	"github.com/netdata/netdata/go/plugins/plugin/framework/chartengine/internal/program"
	"github.com/stretchr/testify/assert"
	"github.com/stretchr/testify/require"
)

func TestRenderChartInstanceIDScenarios(t *testing.T) {
	tests := map[string]struct {
		identity program.ChartIdentity
		labels   map[string]string
		wantID   string
		wantOK   bool
		wantErr  bool
	}{
		"static id without instances": {
			identity: program.ChartIdentity{
				IDTemplate: program.Template{
					Raw: "mysql_queries",
				},
			},
			labels: map[string]string{
				"host": "db1",
			},
			wantID: "mysql_queries",
			wantOK: true,
		},
		"literal id is independent of labels": {
			identity: program.ChartIdentity{
				IDTemplate: program.Template{
					Raw: "win_nic_traffic",
				},
			},
			labels: map[string]string{
				"nic": "eth0",
			},
			wantID: "win_nic_traffic",
			wantOK: true,
		},
		"instances explicit appends stable suffix": {
			identity: program.ChartIdentity{
				IDTemplate: program.Template{
					Raw: "win_nic_traffic",
				},
				InstanceByLabels: []program.InstanceLabelSelector{
					{Key: "nic"},
				},
			},
			labels: map[string]string{
				"nic": "eth0",
			},
			wantID: "win_nic_traffic_eth0",
			wantOK: true,
		},
		"instances wildcard excludes key": {
			identity: program.ChartIdentity{
				IDTemplate: program.Template{
					Raw: "latency_bucket",
				},
				InstanceByLabels: []program.InstanceLabelSelector{
					{IncludeAll: true},
					{Exclude: true, Key: "le"},
				},
			},
			labels: map[string]string{
				"service": "api",
				"zone":    "a",
				"le":      "1",
			},
			wantID: "latency_bucket_api_a",
			wantOK: true,
		},
		"instances explicit missing key drops series": {
			identity: program.ChartIdentity{
				IDTemplate: program.Template{
					Raw: "win_nic_traffic",
				},
				InstanceByLabels: []program.InstanceLabelSelector{
					{Key: "nic"},
				},
			},
			labels: map[string]string{
				"device": "eth0",
			},
			wantOK: false,
		},
	}

	for name, tc := range tests {
		t.Run(name, func(t *testing.T) {
			got, ok, err := renderChartInstanceID(tc.identity, tc.labels)
			if tc.wantErr {
				require.Error(t, err)
				return
			}
			require.NoError(t, err)
			assert.Equal(t, tc.wantOK, ok)
			assert.Equal(t, tc.wantID, got)
		})
	}
}

func TestRenderChartInstanceIDSanitizesDotLabelValues(t *testing.T) {
	identity := program.ChartIdentity{
		IDTemplate: program.Template{Raw: "win_nic_traffic"},
		InstanceByLabels: []program.InstanceLabelSelector{
			{Key: "nic"},
		},
	}

	got, ok, err := renderChartInstanceID(identity, map[string]string{"nic": "eth0.100"})
	require.NoError(t, err)
	assert.True(t, ok)
	assert.Equal(t, "win_nic_traffic_eth0_100", got)
}

func TestSanitizeChartIDLabelValue(t *testing.T) {
	tests := map[string]struct {
		value string
		want  string
	}{
		"empty": {
			value: "",
			want:  "",
		},
		"no dots unchanged": {
			value: "eth0",
			want:  "eth0",
		},
		"single dot replaced": {
			value: "db1.eu",
			want:  "db1_eu",
		},
		"multiple dots replaced": {
			value: "a.b.c",
			want:  "a_b_c",
		},
	}

	for name, tc := range tests {
		t.Run(name, func(t *testing.T) {
			assert.Equal(t, tc.want, sanitizeChartIDLabelValue(tc.value))
		})
	}
}

func TestRenderChartInstanceIDSanitizesLegacyLabelChars(t *testing.T) {
	identity := program.ChartIdentity{
		IDTemplate: program.Template{Raw: "win_nic_traffic"},
		InstanceByLabels: []program.InstanceLabelSelector{
			{Key: "nic"},
		},
	}

	tests := []struct {
		name  string
		value string
		want  string
	}{
		{name: "space", value: "a b", want: "a_b"},
		{name: "backslash", value: "a\\b", want: "a_b"},
		{name: "apostrophe", value: "a'b", want: "ab"},
	}

	for _, tc := range tests {
		t.Run(tc.name, func(t *testing.T) {
			got, ok, err := renderChartInstanceID(identity, map[string]string{"nic": tc.value})
			require.NoError(t, err)
			assert.True(t, ok)
			assert.Equal(t, "win_nic_traffic_"+tc.want, got)
		})
	}
}

func TestRenderChartInstanceIDExcludeWinsRegardlessOfTokenOrder(t *testing.T) {
	tests := map[string]struct {
		selectors []program.InstanceLabelSelector
		wantID    string
	}{
		"exclude after explicit": {
			selectors: []program.InstanceLabelSelector{
				{Key: "host"},
				{Exclude: true, Key: "host"},
			},
			wantID: "mysql_queries",
		},
		"exclude before explicit": {
			selectors: []program.InstanceLabelSelector{
				{Exclude: true, Key: "host"},
				{Key: "host"},
			},
			wantID: "mysql_queries",
		},
	}

	for name, tc := range tests {
		t.Run(name, func(t *testing.T) {
			got, ok, err := renderChartInstanceID(program.ChartIdentity{
				IDTemplate:       program.Template{Raw: "mysql_queries"},
				InstanceByLabels: tc.selectors,
			}, map[string]string{"host": "db1"})
			require.NoError(t, err)
			assert.True(t, ok)
			assert.Equal(t, tc.wantID, got)
		})
	}
}
