// KEKS -- Go KEKS codec implementation
// Copyright (C) 2024-2025 Anton Rudenko <rudenko.ad@phystech.edu>
//                         Sergey Matveev <stargrave@stargrave.org>
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Lesser General Public License as
// published by the Free Software Foundation, version 3 of the License.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
// GNU Lesser General Public License for more details.
//
// You should have received a copy of the GNU Lesser General Public
// License along with this program.  If not, see <http://www.gnu.org/licenses/>.

package keks

import (
	"bytes"
	"io"
	"strings"
	"testing"
	"testing/quick"
	"time"

	"go.cypherpunks.su/keks/be"
	"go.cypherpunks.su/tai64n/v4"
)

const hex1G = "3B9ACA00" // one billion

func TestTAINotEnoughData(t *testing.T) {
	_, err := NewDecoderFromBytes(mustHexDec(
		"18"+strings.Repeat("0", 2*8-2)), nil).Decode()
	if err != io.ErrUnexpectedEOF {
		t.Fatal(err)
	}
}

func TestTAINNotEnoughData(t *testing.T) {
	_, err := NewDecoderFromBytes(mustHexDec(
		"19"+strings.Repeat("0", 2*12-2)), nil).Decode()
	if err != io.ErrUnexpectedEOF {
		t.Fatal(err)
	}
}

func TestTAINANotEnoughData(t *testing.T) {
	_, err := NewDecoderFromBytes(mustHexDec(
		"1A"+strings.Repeat("0", 2*16-2)), nil).Decode()
	if err != io.ErrUnexpectedEOF {
		t.Fatal(err)
	}
}

func TestTAILargeNumberOfSeconds(t *testing.T) {
	_, err := NewDecoderFromBytes(mustHexDec(
		"18"+"7000000065195F65"), nil).Decode()
	if err != nil {
		t.Fatal(err)
	}
}

func TestTAINLargeNumberOfSeconds(t *testing.T) {
	_, err := NewDecoderFromBytes(mustHexDec(
		"19"+"7000000065195F65"+"00010000"), nil).Decode()
	if err != nil {
		t.Fatal(err)
	}
}

func TestTAINALargeNumberOfSeconds(t *testing.T) {
	_, err := NewDecoderFromBytes(mustHexDec(
		"1A"+"7000000065195F65"+"00010000"+"00010000"), nil).Decode()
	if err != nil {
		t.Fatal(err)
	}
}

func TestTAIMsbIsSet(t *testing.T) {
	_, err := NewDecoderFromBytes(mustHexDec(
		"18"+"8000000065195F65"), nil).Decode()
	if err != ErrTAIReserved {
		t.Fatal(err)
	}
}

func TestTAINMsbIsSet(t *testing.T) {
	_, err := NewDecoderFromBytes(mustHexDec(
		"19"+"8000000065195F65"+"00010000"), nil).Decode()
	if err != ErrTAIReserved {
		t.Fatal(err)
	}
}

func TestTAINAMsbIsSet(t *testing.T) {
	_, err := NewDecoderFromBytes(mustHexDec(
		"19"+"8000000065195F65"+"00010000"+"00010000"), nil).Decode()
	if err != ErrTAIReserved {
		t.Fatal(err)
	}
}

func TestTAINTooManyNanosecs(t *testing.T) {
	bin := mustHexDec("19" + "4000000000000000" + hex1G)
	_, err := NewDecoderFromBytes(bin, nil).Decode()
	if err != ErrTAITooManyNsecs {
		t.Fatal(err)
	}
}

func TestTAINATooManyNanosecs(t *testing.T) {
	bin := mustHexDec("1A" + "4000000000000000" + hex1G + "00010000")
	_, err := NewDecoderFromBytes(bin, nil).Decode()
	if err != ErrTAITooManyNsecs {
		t.Fatal(err)
	}
}

func TestTAINATooManyAttosecs(t *testing.T) {
	bin := mustHexDec("1A" + "4000000000000000" + "00010000" + hex1G)
	_, err := NewDecoderFromBytes(bin, nil).Decode()
	if err != ErrTAITooManyAsecs {
		t.Fatal(err)
	}
}

func TestTAINZeroNanosecs(t *testing.T) {
	bin := mustHexDec("19" + "4000000000000000" + "00000000")
	_, err := NewDecoderFromBytes(bin, nil).Decode()
	if err != ErrTAINonMinimal {
		t.Fatal(err)
	}
}

func TestTAINAZeroNanosecsOk(t *testing.T) {
	bin := mustHexDec("1A" + "4000000000000000" + "00000000" + "00000001")
	_, err := NewDecoderFromBytes(bin, nil).Decode()
	if err != nil {
		t.Fatal(err)
	}
}

func TestTAINAZeroAttosecs(t *testing.T) {
	bin := mustHexDec("1A" + "4000000000000000" + "00000000" + "00000000")
	_, err := NewDecoderFromBytes(bin, nil).Decode()
	if err != ErrTAINonMinimal {
		t.Fatal(err)
	}
}

func TestTAI19700101(t *testing.T) {
	decoded, err := NewDecoderFromBytes(mustHexDec(
		"18400000000000000A"), nil).Decode()
	if err != nil {
		t.Fatal(err)
	}
	casted, ok := decoded.(time.Time)
	if !ok {
		t.Fatal("failed to cast")
	}
	if !casted.Equal(time.Date(1970, 1, 1, 0, 0, 0, 0, time.UTC)) {
		t.Fatal("casted differs")
	}
}

func TestTAI19920602(t *testing.T) {
	decoded, err := NewDecoderFromBytes(mustHexDec(
		"18400000002A2B2C2D"), nil).Decode()
	if err != nil {
		t.Fatal(err)
	}
	casted, ok := decoded.(time.Time)
	if !ok {
		t.Fatal("failed to cast")
	}
	if !casted.Equal(time.Date(1992, 6, 2, 8, 6, 43, 0, time.UTC)) {
		t.Fatal("casted differs")
	}
}

func TestTAI19971003(t *testing.T) {
	decoded, err := NewDecoderFromBytes(mustHexDec(
		"184000000034353637"), nil).Decode()
	if err != nil {
		t.Fatal(err)
	}
	casted, ok := decoded.(time.Time)
	if !ok {
		t.Fatal("failed to cast")
	}
	if !casted.Equal(time.Date(1997, 10, 3, 18, 14, 48, 0, time.UTC)) {
		t.Fatal("casted differs")
	}
}

func TestTAI20161231(t *testing.T) {
	decoded, err := NewDecoderFromBytes(mustHexDec(
		"1840000000586846A3"), nil).Decode()
	if err != nil {
		t.Fatal(err)
	}
	casted, ok := decoded.(time.Time)
	if !ok {
		t.Fatal("failed to cast")
	}
	if !casted.Equal(time.Date(2016, 12, 31, 23, 59, 59, 0, time.UTC)) {
		t.Fatal("casted differs")
	}
}

func TestTAI20170101(t *testing.T) {
	decoded, err := NewDecoderFromBytes(mustHexDec(
		"1840000000586846A5"), nil).Decode()
	if err != nil {
		t.Fatal(err)
	}
	casted, ok := decoded.(time.Time)
	if !ok {
		t.Fatal("failed to cast")
	}
	if !casted.Equal(time.Date(2017, 1, 1, 0, 0, 0, 0, time.UTC)) {
		t.Fatal("casted differs")
	}
}

func TestTAI20241120(t *testing.T) {
	decoded, err := NewDecoderFromBytes(mustHexDec(
		"1940000000673DD3E136F11FE0"), nil).Decode()
	if err != nil {
		t.Fatal(err)
	}
	casted, ok := decoded.(time.Time)
	if !ok {
		t.Fatal("failed to cast")
	}
	if !casted.Equal(time.Date(2024, 11, 20, 12, 19, 8, 921772000, time.UTC)) {
		t.Fatal("casted differs")
	}
}

func TestTAISymmetric(t *testing.T) {
	f := func(secs int64) bool {
		if (secs > (1<<62)-1) || (secs < -(1 << 62)) {
			return true
		}
		tm := time.Unix(secs, 0)
		encoded, err := EncodeBuf(tm, nil)
		if err != nil {
			t.Fatal(err)
		}
		decoder := NewDecoderFromBytes(append(encoded, Junk...), nil)
		decoded, err := decoder.Decode()
		if err != nil {
			t.Fatal(err)
		}
		casted, ok := decoded.(time.Time)
		if !ok {
			t.Fatal("failed to cast")
		}
		if !casted.Equal(tm) {
			return false
		}
		if !bytes.Equal(decoder.B, Junk) {
			return false
		}
		decoder = NewDecoderFromBytes(append(encoded, Junk...), &DecodeOpts{
			LeaveTAI: true,
		})
		decoded, err = decoder.Decode()
		if err != nil {
			t.Fatal(err)
		}
		casted, ok = decoded.(time.Time)
		if !ok {
			t.Fatal("failed to cast")
		}
		{
			tai, isLeap := tai64n.Leapsecs.Sub(casted)
			if isLeap {
				return false
			}
			if !tai.Equal(tm) {
				return false
			}
			if !bytes.Equal(decoder.B, Junk) {
				return false
			}
			{
				var encoded2 []byte
				encoded2, err = EncodeBuf(casted, nil)
				if err != nil {
					t.Fatal(err)
				}
				if bytes.Equal(encoded, encoded2) {
					return false
				}
				encoded2, err = EncodeBuf(casted, &EncodeOpts{LeaveTAI: true})
				if err != nil {
					t.Fatal(err)
				}
				if !bytes.Equal(encoded, encoded2) {
					return false
				}
			}
		}
		decoder = NewDecoderFromBytes(append(encoded, Junk...), &DecodeOpts{
			LeaveTAI64: true,
		})
		decoded, err = decoder.Decode()
		if err != nil {
			t.Fatal(err)
		}
		{
			tai, ok := decoded.(*tai64n.TAI64)
			if !ok {
				t.Fatal("failed to cast")
			}
			if !bytes.Equal(encoded[1:], tai[:]) {
				return false
			}
			var raw []byte
			raw, err = EncodeBuf(tai, nil)
			if err != nil {
				t.Fatal(err)
			}
			if !bytes.Equal(raw, encoded) {
				return false
			}
		}
		return bytes.Equal(decoder.B, Junk)
	}
	if err := quick.Check(f, nil); err != nil {
		t.Fatal(err)
	}
}

func TestTAINSymmetric(t *testing.T) {
	f := func(secs int64, nsecs uint32) bool {
		if (secs > (1<<62)-1) || (secs < -(1 << 62)) {
			return true
		}
		nsecs %= 1000
		if nsecs == 0 {
			return true
		}
		tm := time.Unix(secs, int64(nsecs))
		encoded, err := EncodeBuf(tm, nil)
		if err != nil {
			t.Fatal(err)
		}
		decoder := NewDecoderFromBytes(append(encoded, Junk...), nil)
		decoded, err := decoder.Decode()
		if err != nil {
			t.Fatal(err)
		}
		casted, ok := decoded.(time.Time)
		if !ok {
			t.Fatal("failed to cast")
		}
		if !casted.Equal(tm) {
			return false
		}
		if !bytes.Equal(decoder.B, Junk) {
			return false
		}
		decoder = NewDecoderFromBytes(append(encoded, Junk...), &DecodeOpts{
			LeaveTAI: true,
		})
		decoded, err = decoder.Decode()
		if err != nil {
			t.Fatal(err)
		}
		casted, ok = decoded.(time.Time)
		if !ok {
			t.Fatal("failed to cast")
		}
		{
			tai, isLeap := tai64n.Leapsecs.Sub(casted)
			if isLeap {
				return false
			}
			if !tai.Equal(tm) {
				return false
			}
			if !bytes.Equal(decoder.B, Junk) {
				return false
			}
			{
				var encoded2 []byte
				encoded2, err = EncodeBuf(casted, nil)
				if err != nil {
					t.Fatal(err)
				}
				if bytes.Equal(encoded, encoded2) {
					return false
				}
				encoded2, err = EncodeBuf(casted, &EncodeOpts{LeaveTAI: true})
				if err != nil {
					t.Fatal(err)
				}
				if !bytes.Equal(encoded, encoded2) {
					return false
				}
			}
		}
		decoder = NewDecoderFromBytes(append(encoded, Junk...), &DecodeOpts{
			LeaveTAI64: true,
		})
		decoded, err = decoder.Decode()
		if err != nil {
			t.Fatal(err)
		}
		{
			tai, ok := decoded.(*tai64n.TAI64N)
			if !ok {
				t.Fatal("failed to cast")
			}
			if !bytes.Equal(encoded[1:], tai[:]) {
				return false
			}
			var raw []byte
			raw, err = EncodeBuf(tai, nil)
			if err != nil {
				t.Fatal(err)
			}
			if !bytes.Equal(raw, encoded) {
				return false
			}
		}
		return bytes.Equal(decoder.B, Junk)
	}
	if err := quick.Check(f, nil); err != nil {
		t.Fatal(err)
	}
}

func TestTAINASymmetric(t *testing.T) {
	f := func(secs int64, nsecs, asecs uint32) bool {
		if (secs > (1<<62)-1) || (secs < -(1 << 62)) {
			return true
		}
		nsecs %= 1000
		asecs %= 1000
		if asecs == 0 {
			return true
		}
		secs += tai64n.Base
		encoded := make([]byte, 1+tai64n.TAI64NASize)
		encoded[0] = byte(AtomTAI64NA)
		be.Put(encoded[1:1+8], uint64(secs))
		be.Put(encoded[1+8:1+8+4], uint64(nsecs))
		be.Put(encoded[1+8+4:1+8+4+4], uint64(asecs))
		decoder := NewDecoderFromBytes(append(encoded, Junk...), nil)
		decoded, err := decoder.Decode()
		if err != nil {
			t.Fatal(err)
		}
		casted, ok := decoded.(*tai64n.TAI64NA)
		if !ok {
			t.Fatal("failed to cast")
		}
		if !bytes.Equal(casted[:], encoded[1:]) {
			return false
		}
		if !bytes.Equal(decoder.B, Junk) {
			return false
		}
		{
			var raw []byte
			raw, err = EncodeBuf(casted, nil)
			if err != nil {
				t.Fatal(err)
			}
			if !bytes.Equal(raw, encoded) {
				return false
			}
		}
		return true
	}
	if err := quick.Check(f, nil); err != nil {
		t.Fatal(err)
	}
}
