package msgpack

import (
	"encoding/hex"
	"math"
	"testing"
	"time"

	"github.com/stretchr/testify/require"
)

func TestMsgPackTime32(t *testing.T) {
	// Maximum of 4 bytes encodable time
	var sec int64 = 0xFFFFFFFF
	var nsec int64
	t1 := MessagePackTime{time: time.Unix(sec, nsec)}

	require.Equal(t, 4, t1.Len())

	buf := make([]byte, t1.Len())
	require.NoError(t, t1.MarshalBinaryTo(buf))

	t2 := new(MessagePackTime)
	err := t2.UnmarshalBinary(buf)
	require.NoError(t, err)

	require.Equal(t, t1.time, t2.time)
}

func TestMsgPackTime64(t *testing.T) {
	// Maximum of 8 bytes encodable time
	var sec int64 = 0x3FFFFFFFF
	var nsec int64 = 999999999
	t1 := MessagePackTime{time: time.Unix(sec, nsec)}

	require.Equal(t, 8, t1.Len())

	buf := make([]byte, t1.Len())
	require.NoError(t, t1.MarshalBinaryTo(buf))

	t2 := new(MessagePackTime)
	err := t2.UnmarshalBinary(buf)
	require.NoError(t, err)

	require.Equal(t, t1.time, t2.time)
}

func TestMsgPackTime96(t *testing.T) {
	// Testing 12 bytes timestamp
	var sec int64 = 0x400000001
	var nsec int64 = 111111111
	t1 := MessagePackTime{time: time.Unix(sec, nsec)}

	require.Equal(t, 12, t1.Len())

	buf := make([]byte, t1.Len())
	require.NoError(t, t1.MarshalBinaryTo(buf))

	t2 := new(MessagePackTime)
	err := t2.UnmarshalBinary(buf)
	require.NoError(t, err)

	require.True(t, t1.time.Equal(t2.time))

	// Testing the default value: 0001-01-01T00:00:00Z
	t1 = MessagePackTime{}

	require.Equal(t, 12, t1.Len())
	require.NoError(t, t1.MarshalBinaryTo(buf))

	t2 = new(MessagePackTime)
	err = t2.UnmarshalBinary(buf)
	require.NoError(t, err)

	require.True(t, t1.time.Equal(t2.time))
}

func TestMsgPackTimeEdgeCases(t *testing.T) {
	times := make([]time.Time, 0, 8)
	expected := make([][]byte, 0, 8)

	// Unix epoch. Begin of 4bytes dates
	// Nanoseconds: 0x00000000, Seconds: 0x0000000000000000
	ts, err := time.Parse(time.RFC3339, "1970-01-01T00:00:00Z")
	require.NoError(t, err)
	bs, err := hex.DecodeString("d6ff00000000")
	require.NoError(t, err)
	times = append(times, ts)
	expected = append(expected, bs)

	// End of 4bytes dates
	// Nanoseconds: 0x00000000, Seconds: 0x00000000ffffffff
	ts, err = time.Parse(time.RFC3339, "2106-02-07T06:28:15Z")
	require.NoError(t, err)
	bs, err = hex.DecodeString("d6ffffffffff")
	require.NoError(t, err)
	times = append(times, ts)
	expected = append(expected, bs)

	// Begin of 8bytes dates
	// Nanoseconds: 0x00000000, Seconds: 0x0000000100000000
	ts, err = time.Parse(time.RFC3339, "2106-02-07T06:28:16Z")
	require.NoError(t, err)
	bs, err = hex.DecodeString("d7ff0000000100000000")
	require.NoError(t, err)
	times = append(times, ts)
	expected = append(expected, bs)

	// Just after Unix epoch. Non zero nanoseconds
	// Nanoseconds: 0x00000001, Seconds: 0x0000000000000000
	ts, err = time.Parse(time.RFC3339Nano, "1970-01-01T00:00:00.000000001Z")
	require.NoError(t, err)
	bs, err = hex.DecodeString("d7ff0000000400000000")
	require.NoError(t, err)
	times = append(times, ts)
	expected = append(expected, bs)

	// End of 8bytes dates
	// Nanoseconds: 0x00000000, Seconds: 0x00000003ffffffff
	ts, err = time.Parse(time.RFC3339Nano, "2514-05-30T01:53:03.000000000Z")
	require.NoError(t, err)
	bs, err = hex.DecodeString("d7ff00000003ffffffff")
	require.NoError(t, err)
	times = append(times, ts)
	expected = append(expected, bs)

	// Begin of 12bytes date
	// Nanoseconds: 0x00000000, Seconds: 0x0000000400000000
	ts, err = time.Parse(time.RFC3339Nano, "2514-05-30T01:53:04.000000000Z")
	require.NoError(t, err)
	bs, err = hex.DecodeString("c70cff000000000000000400000000")
	require.NoError(t, err)
	times = append(times, ts)
	expected = append(expected, bs)

	// Zero value, 0001-01-01T00:00:00Z
	// Nanoseconds: 0x00000000, Seconds: 0xfffffff1886e0900
	ts = time.Time{}
	bs, err = hex.DecodeString("c70cff00000000fffffff1886e0900")
	require.NoError(t, err)
	times = append(times, ts)
	expected = append(expected, bs)

	// Max value
	// Nanoseconds: 0x3b9ac9ff, Seconds: 0x7fffffffffffffff
	ts = time.Unix(math.MaxInt64, 999_999_999).UTC()
	bs, err = hex.DecodeString("c70cff3b9ac9ff7fffffffffffffff")
	require.NoError(t, err)
	times = append(times, ts)
	expected = append(expected, bs)

	buf := make([]byte, 0)
	for i, ts := range times {
		t1 := MessagePackTime{time: ts}
		m := Metric{Time: t1}

		buf = buf[:0]
		buf, err = m.MarshalMsg(buf)
		require.NoError(t, err)
		require.Equal(t, expected[i], buf[12:len(buf)-14])
	}
}
