package carbon2

import (
	"fmt"
	"testing"
	"time"

	"github.com/stretchr/testify/require"

	"github.com/influxdata/telegraf"
	"github.com/influxdata/telegraf/metric"
	"github.com/influxdata/telegraf/plugins/serializers"
)

func TestSerializeMetricFloat(t *testing.T) {
	now := time.Now()
	tags := map[string]string{
		"cpu": "cpu0",
	}
	fields := map[string]interface{}{
		"usage_idle": float64(91.5),
	}
	m := metric.New("cpu", tags, fields, now)

	testcases := []struct {
		format   string
		expected string
	}{
		{
			format:   "field_separate",
			expected: fmt.Sprintf("metric=cpu field=usage_idle cpu=cpu0  91.5 %d\n", now.Unix()),
		},
		{
			format:   "metric_includes_field",
			expected: fmt.Sprintf("metric=cpu_usage_idle cpu=cpu0  91.5 %d\n", now.Unix()),
		},
	}

	for _, tc := range testcases {
		t.Run(tc.format, func(t *testing.T) {
			s := &Serializer{
				Format: tc.format,
			}
			require.NoError(t, s.Init())

			buf, err := s.Serialize(m)
			require.NoError(t, err)

			require.Equal(t, tc.expected, string(buf))
		})
	}
}

func TestSerializeMetricWithEmptyStringTag(t *testing.T) {
	now := time.Now()
	tags := map[string]string{
		"cpu": "",
	}
	fields := map[string]interface{}{
		"usage_idle": float64(91.5),
	}
	m := metric.New("cpu", tags, fields, now)

	testcases := []struct {
		format   string
		expected string
	}{
		{
			format:   "field_separate",
			expected: fmt.Sprintf("metric=cpu field=usage_idle cpu=null  91.5 %d\n", now.Unix()),
		},
		{
			format:   "metric_includes_field",
			expected: fmt.Sprintf("metric=cpu_usage_idle cpu=null  91.5 %d\n", now.Unix()),
		},
	}

	for _, tc := range testcases {
		t.Run(tc.format, func(t *testing.T) {
			s := &Serializer{
				Format: tc.format,
			}
			require.NoError(t, s.Init())

			buf, err := s.Serialize(m)
			require.NoError(t, err)

			require.Equal(t, tc.expected, string(buf))
		})
	}
}

func TestSerializeWithSpaces(t *testing.T) {
	now := time.Now()
	tags := map[string]string{
		"cpu 0": "cpu 0",
	}
	fields := map[string]interface{}{
		"usage_idle 1": float64(91.5),
	}
	m := metric.New("cpu metric", tags, fields, now)

	testcases := []struct {
		format   string
		expected string
	}{
		{
			format:   "field_separate",
			expected: fmt.Sprintf("metric=cpu_metric field=usage_idle_1 cpu_0=cpu_0  91.5 %d\n", now.Unix()),
		},
		{
			format:   "metric_includes_field",
			expected: fmt.Sprintf("metric=cpu_metric_usage_idle_1 cpu_0=cpu_0  91.5 %d\n", now.Unix()),
		},
	}

	for _, tc := range testcases {
		t.Run(tc.format, func(t *testing.T) {
			s := &Serializer{
				Format: tc.format,
			}
			require.NoError(t, s.Init())

			buf, err := s.Serialize(m)
			require.NoError(t, err)

			require.Equal(t, tc.expected, string(buf))
		})
	}
}

func TestSerializeMetricInt(t *testing.T) {
	now := time.Now()
	tags := map[string]string{
		"cpu": "cpu0",
	}
	fields := map[string]interface{}{
		"usage_idle": int64(90),
	}
	m := metric.New("cpu", tags, fields, now)

	testcases := []struct {
		format   string
		expected string
	}{
		{
			format:   "field_separate",
			expected: fmt.Sprintf("metric=cpu field=usage_idle cpu=cpu0  90 %d\n", now.Unix()),
		},
		{
			format:   "metric_includes_field",
			expected: fmt.Sprintf("metric=cpu_usage_idle cpu=cpu0  90 %d\n", now.Unix()),
		},
	}

	for _, tc := range testcases {
		t.Run(tc.format, func(t *testing.T) {
			s := &Serializer{
				Format: tc.format,
			}
			require.NoError(t, s.Init())

			buf, err := s.Serialize(m)
			require.NoError(t, err)

			require.Equal(t, tc.expected, string(buf))
		})
	}
}

func TestSerializeMetricString(t *testing.T) {
	now := time.Now()
	tags := map[string]string{
		"cpu": "cpu0",
	}
	fields := map[string]interface{}{
		"usage_idle": "foobar",
	}
	m := metric.New("cpu", tags, fields, now)

	testcases := []struct {
		format   string
		expected string
	}{
		{
			format:   "field_separate",
			expected: "",
		},
		{
			format:   "metric_includes_field",
			expected: "",
		},
	}

	for _, tc := range testcases {
		t.Run(tc.format, func(t *testing.T) {
			s := &Serializer{
				Format: tc.format,
			}
			require.NoError(t, s.Init())

			buf, err := s.Serialize(m)
			require.NoError(t, err)

			require.Equal(t, tc.expected, string(buf))
		})
	}
}

func TestSerializeMetricBool(t *testing.T) {
	requireMetric := func(tim time.Time, value bool) telegraf.Metric {
		tags := map[string]string{
			"tag_name": "tag_value",
		}
		fields := map[string]interface{}{
			"java_lang_GarbageCollector_Valid": value,
		}

		m := metric.New("cpu", tags, fields, tim)

		return m
	}

	now := time.Now()

	testcases := []struct {
		metric   telegraf.Metric
		format   string
		expected string
	}{
		{
			metric:   requireMetric(now, false),
			format:   "field_separate",
			expected: fmt.Sprintf("metric=cpu field=java_lang_GarbageCollector_Valid tag_name=tag_value  0 %d\n", now.Unix()),
		},
		{
			metric:   requireMetric(now, false),
			format:   "metric_includes_field",
			expected: fmt.Sprintf("metric=cpu_java_lang_GarbageCollector_Valid tag_name=tag_value  0 %d\n", now.Unix()),
		},
		{
			metric:   requireMetric(now, true),
			format:   "field_separate",
			expected: fmt.Sprintf("metric=cpu field=java_lang_GarbageCollector_Valid tag_name=tag_value  1 %d\n", now.Unix()),
		},
		{
			metric:   requireMetric(now, true),
			format:   "metric_includes_field",
			expected: fmt.Sprintf("metric=cpu_java_lang_GarbageCollector_Valid tag_name=tag_value  1 %d\n", now.Unix()),
		},
	}

	for _, tc := range testcases {
		t.Run(tc.format, func(t *testing.T) {
			s := &Serializer{
				Format: tc.format,
			}
			require.NoError(t, s.Init())

			buf, err := s.Serialize(tc.metric)
			require.NoError(t, err)

			require.Equal(t, tc.expected, string(buf))
		})
	}
}

func TestSerializeBatch(t *testing.T) {
	m := metric.New(
		"cpu",
		map[string]string{},
		map[string]interface{}{
			"value": 42,
		},
		time.Unix(0, 0),
	)

	metrics := []telegraf.Metric{m, m}

	testcases := []struct {
		format   string
		expected string
	}{
		{
			format: "field_separate",
			expected: `metric=cpu field=value  42 0
metric=cpu field=value  42 0
`,
		},
		{
			format: "metric_includes_field",
			expected: `metric=cpu_value  42 0
metric=cpu_value  42 0
`,
		},
	}

	for _, tc := range testcases {
		t.Run(tc.format, func(t *testing.T) {
			s := &Serializer{
				Format: tc.format,
			}
			require.NoError(t, s.Init())

			buf, err := s.SerializeBatch(metrics)
			require.NoError(t, err)

			require.Equal(t, tc.expected, string(buf))
		})
	}
}

func TestSerializeMetricIsProperlySanitized(t *testing.T) {
	now := time.Now()

	testcases := []struct {
		metricFunc  func() telegraf.Metric
		format      string
		expected    string
		replaceChar string
		expectedErr bool
	}{
		{
			metricFunc: func() telegraf.Metric {
				fields := map[string]interface{}{
					"usage_idle": float64(91.5),
				}
				return metric.New("cpu=1", nil, fields, now)
			},
			format:   "field_separate",
			expected: fmt.Sprintf("metric=cpu:1 field=usage_idle  91.5 %d\n", now.Unix()),
		},
		{
			metricFunc: func() telegraf.Metric {
				fields := map[string]interface{}{
					"usage_idle": float64(91.5),
				}
				return metric.New("cpu=1", nil, fields, now)
			},
			format:      "field_separate",
			expected:    fmt.Sprintf("metric=cpu_1 field=usage_idle  91.5 %d\n", now.Unix()),
			replaceChar: "_",
		},
		{
			metricFunc: func() telegraf.Metric {
				fields := map[string]interface{}{
					"usage_idle": float64(91.5),
				}
				return metric.New("cpu=1=tmp$custom", nil, fields, now)
			},
			format:   "field_separate",
			expected: fmt.Sprintf("metric=cpu:1:tmp:custom field=usage_idle  91.5 %d\n", now.Unix()),
		},
		{
			metricFunc: func() telegraf.Metric {
				fields := map[string]interface{}{
					"usage_idle": float64(91.5),
				}
				return metric.New("cpu=1=tmp$custom%namespace", nil, fields, now)
			},
			format:   "field_separate",
			expected: fmt.Sprintf("metric=cpu:1:tmp:custom:namespace field=usage_idle  91.5 %d\n", now.Unix()),
		},
		{
			metricFunc: func() telegraf.Metric {
				fields := map[string]interface{}{
					"usage_idle": float64(91.5),
				}
				return metric.New("cpu=1=tmp$custom%namespace", nil, fields, now)
			},
			format:   "metric_includes_field",
			expected: fmt.Sprintf("metric=cpu:1:tmp:custom:namespace_usage_idle  91.5 %d\n", now.Unix()),
		},
		{
			metricFunc: func() telegraf.Metric {
				fields := map[string]interface{}{
					"usage_idle": float64(91.5),
				}
				return metric.New("cpu=1=tmp$custom%namespace", nil, fields, now)
			},
			format:      "metric_includes_field",
			expected:    fmt.Sprintf("metric=cpu_1_tmp_custom_namespace_usage_idle  91.5 %d\n", now.Unix()),
			replaceChar: "_",
		},
		{
			metricFunc: func() telegraf.Metric {
				fields := map[string]interface{}{
					"usage_idle": float64(91.5),
				}
				return metric.New("cpu=1=tmp$custom%namespace", nil, fields, now)
			},
			format:      "metric_includes_field",
			expectedErr: true,
			replaceChar: "___",
		},
	}

	for _, tc := range testcases {
		t.Run(tc.format, func(t *testing.T) {
			m := tc.metricFunc()

			s := &Serializer{
				Format:              tc.format,
				SanitizeReplaceChar: tc.replaceChar,
			}
			err := s.Init()
			if tc.expectedErr {
				require.Error(t, err)
				return
			}
			require.NoError(t, err)

			buf, err := s.Serialize(m)
			require.NoError(t, err)

			require.Equal(t, tc.expected, string(buf))
		})
	}
}

func BenchmarkSerialize(b *testing.B) {
	s := &Serializer{}
	require.NoError(b, s.Init())
	metrics := serializers.BenchmarkMetrics(b)
	b.ResetTimer()
	for i := 0; i < b.N; i++ {
		_, err := s.Serialize(metrics[i%len(metrics)])
		require.NoError(b, err)
	}
}

func BenchmarkSerializeBatch(b *testing.B) {
	s := &Serializer{}
	require.NoError(b, s.Init())
	m := serializers.BenchmarkMetrics(b)
	metrics := m[:]
	b.ResetTimer()
	for i := 0; i < b.N; i++ {
		_, err := s.SerializeBatch(metrics)
		require.NoError(b, err)
	}
}
