package logstorage

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

func TestValuesEncoder(t *testing.T) {
	f := func(values []string, expectedValueType valueType, expectedMinValue, expectedMaxValue uint64) {
		t.Helper()
		ve := getValuesEncoder()
		var dict valuesDict
		vt, minValue, maxValue := ve.encode(values, &dict)
		if vt != expectedValueType {
			t.Fatalf("unexpected value type; got %d; want %d", vt, expectedValueType)
		}
		if minValue != expectedMinValue {
			t.Fatalf("unexpected minValue; got %d; want %d", minValue, expectedMinValue)
		}
		if maxValue != expectedMaxValue {
			t.Fatalf("unexpected maxValue; got %d; want %d", maxValue, expectedMaxValue)
		}
		encodedValues := append([]string{}, ve.values...)
		putValuesEncoder(ve)

		vd := getValuesDecoder()
		if err := vd.decodeInplace(encodedValues, vt, &dict); err != nil {
			t.Fatalf("unexpected error in decodeInplace(): %s", err)
		}
		if len(values) == 0 {
			values = []string{}
		}
		if !reflect.DeepEqual(values, encodedValues) {
			t.Fatalf("unexpected values decoded\ngot\n%q\nwant\n%q", encodedValues, values)
		}
		putValuesDecoder(vd)
	}

	// An empty values list
	f(nil, valueTypeString, 0, 0)

	// string values
	values := make([]string, maxDictLen+1)
	for i := range values {
		values[i] = fmt.Sprintf("value_%d", i)
	}
	f(values, valueTypeString, 0, 0)

	// dict values
	f([]string{"foobar"}, valueTypeDict, 0, 0)
	f([]string{"foo", "bar"}, valueTypeDict, 0, 0)
	f([]string{"1", "2foo"}, valueTypeDict, 0, 0)

	// uint8 values
	for i := range values {
		values[i] = fmt.Sprintf("%d", uint64(i+1))
	}
	f(values, valueTypeUint8, 1, uint64(len(values)))

	// uint16 values
	for i := range values {
		values[i] = fmt.Sprintf("%d", uint64(i+1)<<8)
	}
	f(values, valueTypeUint16, 1<<8, uint64(len(values))<<8)

	// uint32 values
	for i := range values {
		values[i] = fmt.Sprintf("%d", uint64(i+1)<<16)
	}
	f(values, valueTypeUint32, 1<<16, uint64(len(values))<<16)

	// uint64 values
	for i := range values {
		values[i] = fmt.Sprintf("%d", uint64(i+1)<<32)
	}
	f(values, valueTypeUint64, 1<<32, uint64(len(values))<<32)

	// ipv4 values
	for i := range values {
		values[i] = fmt.Sprintf("1.2.3.%d", i)
	}
	f(values, valueTypeIPv4, 16909056, 16909064)

	// iso8601 timestamps
	for i := range values {
		values[i] = fmt.Sprintf("2011-04-19T03:44:01.%03dZ", i)
	}
	f(values, valueTypeTimestampISO8601, 1303184641000000000, 1303184641008000000)

	// float64 values
	for i := range values {
		values[i] = fmt.Sprintf("%g", math.Sqrt(float64(i+1)))
	}
	f(values, valueTypeFloat64, 4607182418800017408, 4613937818241073152)
}

func TestTryParseIPv4(t *testing.T) {
	f := func(s string, nExpected uint32, okExpected bool) {
		t.Helper()
		n, ok := tryParseIPv4(s)
		if n != nExpected {
			t.Fatalf("unexpected n; got %d; want %d", n, nExpected)
		}
		if ok != okExpected {
			t.Fatalf("unexpected ok; got %v; want %v", ok, okExpected)
		}
	}

	f("", 0, false)
	f("foo", 0, false)
	f("a.b.c.d", 0, false)
	f("1.2.3.4", 0x01020304, true)
	f("255.255.255.255", 0xffffffff, true)
	f("0.0.0.0", 0, true)
	f("127.0.0.1", 0x7f000001, true)
	f("127.0.0.x", 0, false)
	f("127.0.x.0", 0, false)
	f("127.x.0.0", 0, false)
	f("x.0.0.0", 0, false)
	f("127.127.127.256", 0, false)
	f("127.127.256.127", 0, false)
	f("127.256.127.127", 0, false)
	f("256.127.127.127", 0, false)
	f("-1.127.127.127", 0, false)
	f("127.-1.127.127", 0, false)
	f("127.127.-1.127", 0, false)
	f("127.127.127.-1", 0, false)
}

func TestTryParseTimestampISO8601(t *testing.T) {
	f := func(s string, timestampExpected uint64, okExpected bool) {
		t.Helper()
		timestamp, ok := tryParseTimestampISO8601(s)
		if timestamp != timestampExpected {
			t.Fatalf("unexpected timestamp; got %d; want %d", timestamp, timestampExpected)
		}
		if ok != okExpected {
			t.Fatalf("unexpected ok; got %v; want %v", ok, okExpected)
		}
	}

	f("2023-01-15T23:45:51.123Z", 1673826351123000000, true)

	// Invalid milliseconds
	f("2023-01-15T22:15:51.12345Z", 0, false)
	f("2023-01-15T22:15:51.12Z", 0, false)
	f("2023-01-15T22:15:51Z", 0, false)

	// Missing Z
	f("2023-01-15T23:45:51.123", 0, false)

	// Invalid timestamp
	f("foo", 0, false)
	f("2023-01-15T23:45:51.123Zxyabcd", 0, false)
	f("2023-01-15T23:45:51.123Z01:00", 0, false)

	// timestamp with timezone
	f("2023-01-16T00:45:51.123+01:00", 0, false)
}

func TestTryParseFloat64(t *testing.T) {
	f := func(s string, valueExpected float64, okExpected bool) {
		t.Helper()

		value, ok := tryParseFloat64(s)
		if value != valueExpected {
			t.Fatalf("unexpected value; got %v; want %v", value, valueExpected)
		}
		if ok != okExpected {
			t.Fatalf("unexpected ok; got %v; want %v", ok, okExpected)
		}
	}

	f("0", 0, true)
	f("1234567890", 1234567890, true)
	f("-1.234567", -1.234567, true)

	// Empty value
	f("", 0, false)

	// Plus in the value isn't allowed, since it cannot be convered back to the same string representation
	f("+123", 0, false)

	// Dot at the beginning and the end of value isn't allowed, since it cannot converted back to the same string representation
	f(".123", 0, false)
	f("123.", 0, false)

	// Multiple dots aren't allowed
	f("123.434.55", 0, false)

	// Invalid dots
	f("-.123", 0, false)
	f(".", 0, false)

	// Scientific notation isn't allowed, since it cannot be converted back to the same string representation
	f("12e5", 0, false)

	// Minus in the middle of string isn't allowed
	f("12-5", 0, false)
}

func TestTryParseUint64(t *testing.T) {
	f := func(s string, valueExpected uint64, okExpected bool) {
		t.Helper()

		value, ok := tryParseUint64(s)
		if value != valueExpected {
			t.Fatalf("unexpected value; got %d; want %d", value, valueExpected)
		}
		if ok != okExpected {
			t.Fatalf("unexpected ok; got %v; want %v", ok, okExpected)
		}
	}

	f("0", 0, true)
	f("123456789012345678", 123456789012345678, true)

	// empty value
	f("", 0, false)

	// too big value
	f("1234567890123456789", 0, false)

	// invalid value
	f("foo", 0, false)
}