package storage

import (
	"fmt"
	"reflect"
	"testing"
)

func TestMetricNameString(t *testing.T) {
	f := func(mn *MetricName, resultExpected string) {
		t.Helper()
		result := mn.String()
		if result != resultExpected {
			t.Fatalf("unexpected result\ngot\n%s\nwant\n%s", result, resultExpected)
		}
	}
	f(&MetricName{
		AccountID:   123,
		ProjectID:   456,
		MetricGroup: []byte("foobar"),
	}, "AccountID=123, ProjectID=456, foobar{}")
	f(&MetricName{
		MetricGroup: []byte("abc"),
		Tags: []Tag{
			{
				Key:   []byte("foo"),
				Value: []byte("bar"),
			},
			{
				Key:   []byte("baz"),
				Value: []byte("123"),
			},
		},
	}, `AccountID=0, ProjectID=0, abc{baz="123",foo="bar"}`)
}

func TestMetricNameSortTags(t *testing.T) {
	testMetricNameSortTags(t, []string{}, []string{})
	testMetricNameSortTags(t, []string{"foo"}, []string{"foo"})
	testMetricNameSortTags(t, []string{"job"}, []string{"job"})
	testMetricNameSortTags(t, []string{"server"}, []string{"server"})
	testMetricNameSortTags(t, []string{"host", "foo", "bar", "service"}, []string{"service", "host", "bar", "foo"})
	testMetricNameSortTags(t, []string{"model", "foo", "job", "host", "server", "instance"},
		[]string{"job", "model", "instance", "host", "server", "foo"})
}

func testMetricNameSortTags(t *testing.T, tags, expectedTags []string) {
	t.Helper()

	var mn MetricName
	for _, t := range tags {
		mn.AddTag(t, "")
	}
	mn.sortTags()

	resultTags := []string{}
	for i := range mn.Tags {
		resultTags = append(resultTags, string(mn.Tags[i].Key))
	}
	if !reflect.DeepEqual(resultTags, expectedTags) {
		t.Fatalf("unexpected resultTags\ngot\n%q\nwant\n%q", resultTags, expectedTags)
	}
}

func TestMetricNameMarshalDuplicateKeys(t *testing.T) {
	var mn MetricName
	mn.AccountID = 123
	mn.ProjectID = 324
	mn.MetricGroup = []byte("xxx")
	mn.AddTag("foo", "bar")
	mn.AddTag("duplicate", "tag1")
	mn.AddTag("duplicate", "tag2")
	mn.AddTag("tt", "xx")
	mn.AddTag("foo", "abc")
	mn.AddTag("duplicate", "tag3")

	var mnExpected MetricName
	mnExpected.AccountID = 123
	mnExpected.ProjectID = 324
	mnExpected.MetricGroup = []byte("xxx")
	mnExpected.AddTag("duplicate", "tag3")
	mnExpected.AddTag("foo", "abc")
	mnExpected.AddTag("tt", "xx")

	mn.sortTags()
	data := mn.Marshal(nil)
	var mn1 MetricName
	if err := mn1.Unmarshal(data); err != nil {
		t.Fatalf("cannot unmarshal mn %s: %s", &mn, err)
	}
	if !reflect.DeepEqual(&mnExpected, &mn1) {
		t.Fatalf("unexpected mn unmarshaled;\ngot\n%+v\nwant\n%+v", &mn1, &mnExpected)
	}
}

func TestMetricNameMarshalUnmarshal(t *testing.T) {
	for i := 0; i < 10; i++ {
		for tagsCount := 0; tagsCount < 10; tagsCount++ {
			var mn MetricName
			mn.AccountID = uint32(i)
			mn.ProjectID = uint32(i + 1)
			for j := 0; j < tagsCount; j++ {
				key := fmt.Sprintf("key_%d_%d_\x00\x01\x02", i, j)
				value := fmt.Sprintf("\x02\x00\x01value_%d_%d", i, j)
				mn.AddTag(key, value)
			}
			mn.sortTags()
			data := mn.Marshal(nil)
			var mn1 MetricName
			if err := mn1.Unmarshal(data); err != nil {
				t.Fatalf("cannot unmarshal mn %s: %s", &mn, err)
			}
			if !reflect.DeepEqual(&mn, &mn1) {
				t.Fatalf("unexpected mn unmarshaled;\ngot\n%+v\nwant\n%+v", &mn1, &mn)
			}

			// Try unmarshaling MetricName without tag value.
			brokenData := marshalTagValue(data, []byte("foobar"))
			if err := mn1.Unmarshal(brokenData); err == nil {
				t.Fatalf("expecting non-zero error when unmarshaling MetricName without tag value")
			}

			// Try unmarshaling MetricName with invalid tag key.
			brokenData[len(brokenData)-1] = 123
			if err := mn1.Unmarshal(brokenData); err == nil {
				t.Fatalf("expecting non-zero error when unmarshaling MetricName with invalid tag key")
			}

			// Try unmarshaling MetricName with invalid tag value.
			brokenData = marshalTagValue(data, []byte("foobar"))
			brokenData = marshalTagValue(brokenData, []byte("aaa"))
			brokenData[len(brokenData)-1] = 123
			if err := mn1.Unmarshal(brokenData); err == nil {
				t.Fatalf("expecting non-zero error when unmarshaling MetricName with invalid tag value")
			}
		}
	}
}

func TestMetricNameMarshalUnmarshalRaw(t *testing.T) {
	for i := 0; i < 10; i++ {
		for tagsCount := 0; tagsCount < 10; tagsCount++ {
			var mn MetricName
			mn.AccountID = uint32(i)
			mn.ProjectID = uint32(tagsCount)
			for j := 0; j < tagsCount; j++ {
				key := fmt.Sprintf("key_%d_%d_\x00\x01\x02", i, j)
				value := fmt.Sprintf("\x02\x00\x01value_%d_%d", i, j)
				mn.AddTag(key, value)
			}
			data := mn.marshalRaw(nil)
			var mn1 MetricName
			if err := mn1.UnmarshalRaw(data); err != nil {
				t.Fatalf("cannot unmarshal mn %s: %s", &mn, err)
			}
			if !reflect.DeepEqual(&mn, &mn1) {
				t.Fatalf("unexpected mn unmarshaled;\ngot\n%+v\nwant\n%+v", &mn1, &mn)
			}

			// Try unmarshaling MetricName without tag value.
			brokenData := marshalTagValue(data, []byte("foobar"))
			if err := mn1.UnmarshalRaw(brokenData); err == nil {
				t.Fatalf("expecting non-zero error when unmarshaling MetricName without tag value")
			}

			// Try unmarshaling MetricName with invalid tag key.
			brokenData[len(brokenData)-1] = 123
			if err := mn1.UnmarshalRaw(brokenData); err == nil {
				t.Fatalf("expecting non-zero error when unmarshaling MetricName with invalid tag key")
			}

			// Try unmarshaling MetricName with invalid tag value.
			brokenData = marshalTagValue(data, []byte("foobar"))
			brokenData = marshalTagValue(brokenData, []byte("aaa"))
			brokenData[len(brokenData)-1] = 123
			if err := mn1.UnmarshalRaw(brokenData); err == nil {
				t.Fatalf("expecting non-zero error when unmarshaling MetricName with invalid tag value")
			}
		}
	}
}

func TestMetricNameCopyFrom(t *testing.T) {
	var from MetricName
	from.MetricGroup = []byte("group")
	from.AddTag("key", "value")

	var to MetricName
	to.CopyFrom(&from)

	var expected MetricName
	expected.MetricGroup = []byte("group")
	expected.AddTag("key", "value")

	if !reflect.DeepEqual(expected, to) {
		t.Fatalf("expecting equal metics exp: %s, got %s", &expected, &to)
	}
}

func TestMetricNameRemoveTagsOn(t *testing.T) {
	var emptyMN MetricName
	emptyMN.MetricGroup = []byte("name")
	emptyMN.AddTag("key", "value")
	emptyMN.RemoveTagsOn(nil)
	if len(emptyMN.MetricGroup) != 0 || len(emptyMN.Tags) != 0 {
		t.Fatalf("expecitng empty metric name got %s", &emptyMN)
	}

	var asIsMN MetricName
	asIsMN.MetricGroup = []byte("name")
	asIsMN.AddTag("key", "value")
	asIsMN.RemoveTagsOn([]string{"__name__", "key"})
	var expAsIsMN MetricName
	expAsIsMN.MetricGroup = []byte("name")
	expAsIsMN.AddTag("key", "value")
	if !reflect.DeepEqual(expAsIsMN, asIsMN) {
		t.Fatalf("expecitng %s got %s", &expAsIsMN, &asIsMN)
	}

	var mn MetricName
	mn.MetricGroup = []byte("name")
	mn.AddTag("foo", "bar")
	mn.AddTag("baz", "qux")
	mn.RemoveTagsOn([]string{"baz"})
	var expMN MetricName
	expMN.AddTag("baz", "qux")
	if !reflect.DeepEqual(expMN.Tags, mn.Tags) || len(mn.MetricGroup) != len(expMN.MetricGroup) {
		t.Fatalf("expecitng %s got %s", &expMN, &mn)
	}
}

func TestMetricNameRemoveTag(t *testing.T) {
	var mn MetricName
	mn.MetricGroup = []byte("name")
	mn.AddTag("foo", "bar")
	mn.AddTag("baz", "qux")
	mn.RemoveTag("__name__")
	if len(mn.MetricGroup) != 0 {
		t.Fatalf("expecting empty metric group got %s", &mn)
	}
	mn.RemoveTag("foo")
	var expMN MetricName
	expMN.AddTag("baz", "qux")
	if !reflect.DeepEqual(expMN.Tags, mn.Tags) || len(mn.MetricGroup) != len(expMN.MetricGroup) {
		t.Fatalf("expecitng %s got %s", &expMN, &mn)
	}
}

func TestMetricNameRemoveTagsIgnoring(t *testing.T) {
	var mn MetricName
	mn.MetricGroup = []byte("name")
	mn.AddTag("foo", "bar")
	mn.AddTag("baz", "qux")
	mn.RemoveTagsIgnoring([]string{"__name__", "foo"})
	var expMN MetricName
	expMN.AddTag("baz", "qux")
	if !reflect.DeepEqual(expMN.Tags, mn.Tags) || len(mn.MetricGroup) != len(expMN.MetricGroup) {
		t.Fatalf("expecitng %s got %s", &expMN, &mn)
	}
}
