package statsd

import (
	"testing"
	"time"

	"github.com/influxdata/telegraf/testutil"
	"github.com/stretchr/testify/require"
)

func TestEventGather(t *testing.T) {
	now := time.Now()
	type expected struct {
		title  string
		tags   map[string]string
		fields map[string]interface{}
	}
	tests := []struct {
		name     string
		message  string
		hostname string
		now      time.Time
		err      bool
		expected expected
	}{{
		name:     "basic",
		message:  "_e{10,9}:test title|test text",
		hostname: "default-hostname",
		now:      now,
		err:      false,
		expected: expected{
			title: "test title",
			tags:  map[string]string{"source": "default-hostname"},
			fields: map[string]interface{}{
				"priority":   priorityNormal,
				"alert_type": "info",
				"text":       "test text",
			},
		},
	},
		{
			name:     "escape some stuff",
			message:  "_e{10,24}:test title|test\\line1\\nline2\\nline3",
			hostname: "default-hostname",
			now:      now.Add(1),
			err:      false,
			expected: expected{
				title: "test title",
				tags:  map[string]string{"source": "default-hostname"},
				fields: map[string]interface{}{
					"priority":   priorityNormal,
					"alert_type": "info",
					"text":       "test\\line1\nline2\nline3",
				},
			},
		},
		{
			name:     "custom time",
			message:  "_e{10,9}:test title|test text|d:21",
			hostname: "default-hostname",
			now:      now.Add(2),
			err:      false,
			expected: expected{
				title: "test title",
				tags:  map[string]string{"source": "default-hostname"},
				fields: map[string]interface{}{
					"priority":   priorityNormal,
					"alert_type": "info",
					"text":       "test text",
					"ts":         int64(21),
				},
			},
		},
	}
	acc := &testutil.Accumulator{}
	s := NewTestStatsd()
	require.NoError(t, s.Start(acc))
	defer s.Stop()

	for i := range tests {
		t.Run(tests[i].name, func(t *testing.T) {
			err := s.parseEventMessage(tests[i].now, tests[i].message, tests[i].hostname)
			if tests[i].err {
				require.NotNil(t, err)
			} else {
				require.Nil(t, err)
			}
			require.Equal(t, uint64(i+1), acc.NMetrics())

			require.Nil(t, err)
			require.Equal(t, tests[i].expected.title, acc.Metrics[i].Measurement)
			require.Equal(t, tests[i].expected.tags, acc.Metrics[i].Tags)
			require.Equal(t, tests[i].expected.fields, acc.Metrics[i].Fields)
		})
	}
}

// These tests adapted from tests in
// https://github.com/DataDog/datadog-agent/blob/master/pkg/dogstatsd/parser_test.go
// to ensure compatibility with the datadog-agent parser

func TestEvents(t *testing.T) {
	now := time.Now()
	type args struct {
		now      time.Time
		message  string
		hostname string
	}
	type expected struct {
		title          string
		text           interface{}
		now            time.Time
		ts             interface{}
		priority       string
		source         string
		alertType      interface{}
		aggregationKey string
		sourceTypeName interface{}
		checkTags      map[string]string
	}

	tests := []struct {
		name     string
		args     args
		expected expected
	}{
		{
			name: "event minimal",
			args: args{
				now:      now,
				message:  "_e{10,9}:test title|test text",
				hostname: "default-hostname",
			},
			expected: expected{
				title:          "test title",
				text:           "test text",
				now:            now,
				priority:       priorityNormal,
				source:         "default-hostname",
				alertType:      eventInfo,
				aggregationKey: "",
			},
		},
		{
			name: "event multilines text",
			args: args{
				now:      now.Add(1),
				message:  "_e{10,24}:test title|test\\line1\\nline2\\nline3",
				hostname: "default-hostname",
			},
			expected: expected{
				title:          "test title",
				text:           "test\\line1\nline2\nline3",
				now:            now.Add(1),
				priority:       priorityNormal,
				source:         "default-hostname",
				alertType:      eventInfo,
				aggregationKey: "",
			},
		},
		{
			name: "event pipe in title",
			args: args{
				now:      now.Add(2),
				message:  "_e{10,24}:test|title|test\\line1\\nline2\\nline3",
				hostname: "default-hostname",
			},
			expected: expected{
				title:          "test|title",
				text:           "test\\line1\nline2\nline3",
				now:            now.Add(2),
				priority:       priorityNormal,
				source:         "default-hostname",
				alertType:      eventInfo,
				aggregationKey: "",
			},
		},
		{
			name: "event metadata timestamp",
			args: args{
				now:      now.Add(3),
				message:  "_e{10,9}:test title|test text|d:21",
				hostname: "default-hostname",
			},
			expected: expected{
				title:          "test title",
				text:           "test text",
				now:            now.Add(3),
				priority:       priorityNormal,
				source:         "default-hostname",
				alertType:      eventInfo,
				aggregationKey: "",
				ts:             int64(21),
			},
		},
		{
			name: "event metadata priority",
			args: args{
				now:      now.Add(4),
				message:  "_e{10,9}:test title|test text|p:low",
				hostname: "default-hostname",
			},
			expected: expected{
				title:     "test title",
				text:      "test text",
				now:       now.Add(4),
				priority:  priorityLow,
				source:    "default-hostname",
				alertType: eventInfo,
			},
		},
		{
			name: "event metadata hostname",
			args: args{
				now:      now.Add(5),
				message:  "_e{10,9}:test title|test text|h:localhost",
				hostname: "default-hostname",
			},
			expected: expected{
				title:     "test title",
				text:      "test text",
				now:       now.Add(5),
				priority:  priorityNormal,
				source:    "localhost",
				alertType: eventInfo,
			},
		},
		{
			name: "event metadata hostname in tag",
			args: args{
				now:      now.Add(6),
				message:  "_e{10,9}:test title|test text|#host:localhost",
				hostname: "default-hostname",
			},
			expected: expected{
				title:     "test title",
				text:      "test text",
				now:       now.Add(6),
				priority:  priorityNormal,
				source:    "localhost",
				alertType: eventInfo,
			},
		},
		{
			name: "event metadata empty host tag",
			args: args{
				now:      now.Add(7),
				message:  "_e{10,9}:test title|test text|#host:,other:tag",
				hostname: "default-hostname",
			},
			expected: expected{
				title:     "test title",
				text:      "test text",
				now:       now.Add(7),
				priority:  priorityNormal,
				source:    "true",
				alertType: eventInfo,
				checkTags: map[string]string{"other": "tag", "source": "true"},
			},
		},
		{
			name: "event metadata alert type",
			args: args{
				now:      now.Add(8),
				message:  "_e{10,9}:test title|test text|t:warning",
				hostname: "default-hostname",
			},
			expected: expected{
				title:     "test title",
				text:      "test text",
				now:       now.Add(8),
				priority:  priorityNormal,
				source:    "default-hostname",
				alertType: eventWarning,
			},
		},
		{
			name: "event metadata aggregation key",
			args: args{
				now:      now.Add(9),
				message:  "_e{10,9}:test title|test text|k:some aggregation key",
				hostname: "default-hostname",
			},
			expected: expected{
				title:          "test title",
				text:           "test text",
				now:            now.Add(9),
				priority:       priorityNormal,
				source:         "default-hostname",
				alertType:      eventInfo,
				aggregationKey: "some aggregation key",
			},
		},
		{
			name: "event metadata aggregation key",
			args: args{
				now:      now.Add(10),
				message:  "_e{10,9}:test title|test text|k:some aggregation key",
				hostname: "default-hostname",
			},
			expected: expected{
				title:          "test title",
				text:           "test text",
				now:            now.Add(10),
				priority:       priorityNormal,
				source:         "default-hostname",
				alertType:      eventInfo,
				aggregationKey: "some aggregation key",
			},
		},
		{
			name: "event metadata source type",
			args: args{
				now:      now.Add(11),
				message:  "_e{10,9}:test title|test text|s:this is the source",
				hostname: "default-hostname",
			},
			expected: expected{
				title:          "test title",
				text:           "test text",
				now:            now.Add(11),
				priority:       priorityNormal,
				source:         "default-hostname",
				sourceTypeName: "this is the source",
				alertType:      eventInfo,
			},
		},
		{
			name: "event metadata source type",
			args: args{
				now:      now.Add(11),
				message:  "_e{10,9}:test title|test text|s:this is the source",
				hostname: "default-hostname",
			},
			expected: expected{
				title:          "test title",
				text:           "test text",
				now:            now.Add(11),
				priority:       priorityNormal,
				source:         "default-hostname",
				sourceTypeName: "this is the source",
				alertType:      eventInfo,
			},
		},
		{
			name: "event metadata source tags",
			args: args{
				now:      now.Add(11),
				message:  "_e{10,9}:test title|test text|#tag1,tag2:test",
				hostname: "default-hostname",
			},
			expected: expected{
				title:     "test title",
				text:      "test text",
				now:       now.Add(11),
				priority:  priorityNormal,
				source:    "default-hostname",
				alertType: eventInfo,
				checkTags: map[string]string{"tag1": "true", "tag2": "test", "source": "default-hostname"},
			},
		},
		{
			name: "event metadata multiple",
			args: args{
				now:      now.Add(11),
				message:  "_e{10,9}:test title|test text|t:warning|d:12345|p:low|h:some.host|k:aggKey|s:source test|#tag1,tag2:test",
				hostname: "default-hostname",
			},
			expected: expected{
				title:          "test title",
				text:           "test text",
				now:            now.Add(11),
				priority:       priorityLow,
				source:         "some.host",
				ts:             int64(12345),
				alertType:      eventWarning,
				aggregationKey: "aggKey",
				sourceTypeName: "source test",
				checkTags:      map[string]string{"aggregation_key": "aggKey", "tag1": "true", "tag2": "test", "source": "some.host"},
			},
		},
	}
	s := NewTestStatsd()
	acc := &testutil.Accumulator{}
	require.NoError(t, s.Start(acc))
	defer s.Stop()
	for i := range tests {
		t.Run(tests[i].name, func(t *testing.T) {
			acc.ClearMetrics()
			err := s.parseEventMessage(tests[i].args.now, tests[i].args.message, tests[i].args.hostname)
			require.Nil(t, err)
			m := acc.Metrics[0]
			require.Equal(t, tests[i].expected.title, m.Measurement)
			require.Equal(t, tests[i].expected.text, m.Fields["text"])
			require.Equal(t, tests[i].expected.now, m.Time)
			require.Equal(t, tests[i].expected.ts, m.Fields["ts"])
			require.Equal(t, tests[i].expected.priority, m.Fields["priority"])
			require.Equal(t, tests[i].expected.source, m.Tags["source"])
			require.Equal(t, tests[i].expected.alertType, m.Fields["alert_type"])
			require.Equal(t, tests[i].expected.aggregationKey, m.Tags["aggregation_key"])
			require.Equal(t, tests[i].expected.sourceTypeName, m.Fields["source_type_name"])
			if tests[i].expected.checkTags != nil {
				require.Equal(t, tests[i].expected.checkTags, m.Tags)
			}
		})
	}
}

func TestEventError(t *testing.T) {
	now := time.Now()
	s := NewTestStatsd()
	acc := &testutil.Accumulator{}
	require.NoError(t, s.Start(acc))
	defer s.Stop()

	// missing length header
	err := s.parseEventMessage(now, "_e:title|text", "default-hostname")
	require.Error(t, err)

	// greater length than packet
	err = s.parseEventMessage(now, "_e{10,10}:title|text", "default-hostname")
	require.Error(t, err)

	// zero length
	err = s.parseEventMessage(now, "_e{0,0}:a|a", "default-hostname")
	require.Error(t, err)

	// missing title or text length
	err = s.parseEventMessage(now, "_e{5555:title|text", "default-hostname")
	require.Error(t, err)

	// missing wrong len format
	err = s.parseEventMessage(now, "_e{a,1}:title|text", "default-hostname")
	require.Error(t, err)

	err = s.parseEventMessage(now, "_e{1,a}:title|text", "default-hostname")
	require.Error(t, err)

	// missing title or text length
	err = s.parseEventMessage(now, "_e{5,}:title|text", "default-hostname")
	require.Error(t, err)

	err = s.parseEventMessage(now, "_e{100,:title|text", "default-hostname")
	require.Error(t, err)

	err = s.parseEventMessage(now, "_e,100:title|text", "default-hostname")
	require.Error(t, err)

	err = s.parseEventMessage(now, "_e{,4}:title|text", "default-hostname")
	require.Error(t, err)

	err = s.parseEventMessage(now, "_e{}:title|text", "default-hostname")
	require.Error(t, err)

	err = s.parseEventMessage(now, "_e{,}:title|text", "default-hostname")
	require.Error(t, err)

	// not enough information
	err = s.parseEventMessage(now, "_e|text", "default-hostname")
	require.Error(t, err)

	err = s.parseEventMessage(now, "_e:|text", "default-hostname")
	require.Error(t, err)

	// invalid timestamp
	err = s.parseEventMessage(now, "_e{5,4}:title|text|d:abc", "default-hostname")
	require.NoError(t, err)

	// invalid priority
	err = s.parseEventMessage(now, "_e{5,4}:title|text|p:urgent", "default-hostname")
	require.NoError(t, err)

	// invalid priority
	err = s.parseEventMessage(now, "_e{5,4}:title|text|p:urgent", "default-hostname")
	require.NoError(t, err)

	// invalid alert type
	err = s.parseEventMessage(now, "_e{5,4}:title|text|t:test", "default-hostname")
	require.NoError(t, err)

	// unknown metadata
	err = s.parseEventMessage(now, "_e{5,4}:title|text|x:1234", "default-hostname")
	require.Error(t, err)
}
