package pdu

import (
	"bytes"
	"encoding/binary"
	"math"
	"sync"
	"testing"
)

// region encode
func TestEncodeReturnsByteSliceOfLength16(t *testing.T) {
	p := &PDU_HEADER{
		Command_length:  1,
		Command_id:      1,
		Command_status:  1,
		Sequence_number: 1,
	}
	buf := bytes.NewBuffer(make([]byte, 16))
	buf.Reset()
	err := p.Encode(buf)

	if err != nil {
		t.Errorf("Expected no error, got %v", err)
	}
	if buf.Cap() != 16 {
		t.Errorf("Expected byte slice of length 16, got %d", buf.Cap())
	}
}

func TestEncodeHandlesZeroValues(t *testing.T) {
	p := &PDU_HEADER{
		Command_length:  0,
		Command_id:      0,
		Command_status:  0,
		Sequence_number: 0,
	}
	buf := bytes.NewBuffer(make([]byte, 16))
	buf.Reset()
	err := p.Encode(buf)

	if err != nil {
		t.Errorf("Expected no error, got %v", err)
	}
	expected := make([]byte, 16)
	for i, v := range buf.Bytes() {
		if v != expected[i] {
			t.Errorf("Expected byte slice with zero values, got %v", buf.Bytes())
			break
		}
	}
}

func TestEncodeEncodesProperly(t *testing.T) {
	p := &PDU_HEADER{
		Command_length:  1,
		Command_id:      2,
		Command_status:  3,
		Sequence_number: 4,
	}
	buf := bytes.NewBuffer(make([]byte, 16))
	buf.Reset()
	err := p.Encode(buf)

	if err != nil {
		t.Errorf("Expected no error, got %v", err)
	}

	expected := []byte{0, 0, 0, 1, 0, 0, 0, 2, 0, 0, 0, 3, 0, 0, 0, 4}
	for i, v := range buf.Bytes() {
		if v != expected[i] {
			t.Errorf("Expected byte slice with values %v, got %v", expected, buf.Bytes())
			break
		}
	}
}

func TestEncodeEncodesProperlyComplex(t *testing.T) {
	p := &PDU_HEADER{
		Command_length:  13426724,
		Command_id:      254352,
		Command_status:  35634264,
		Sequence_number: 476543523,
	}
	buf := bytes.NewBuffer(make([]byte, 16))
	buf.Reset()
	err := p.Encode(buf)

	if err != nil {
		t.Errorf("Expected no error, got %v", err)
	}

	expected := []byte{0, 204, 224, 36, 0, 3, 225, 144, 2, 31, 188, 88, 28, 103, 122, 35}
	for i, v := range buf.Bytes() {
		if v != expected[i] {
			t.Errorf("Expected byte slice with values %v, got %v", expected, buf.Bytes())
			break
		}
	}
}

func TestEncodeIntoCorrectlyEncodesFields(t *testing.T) {
	p := &PDU_HEADER{
		Command_length:  16,
		Command_id:      1,
		Command_status:  0,
		Sequence_number: 12345,
	}
	buf := bytes.NewBuffer(make([]byte, 16))
	buf.Reset()
	err := p.Encode(buf)

	if err != nil {
		t.Errorf("Expected no error, got %v", err)
	}

	innerbuf := buf.Bytes()
	if binary.BigEndian.Uint32(innerbuf[0:4]) != p.Command_length {
		t.Errorf("Expected command_length %d, got %d", p.Command_length, binary.BigEndian.Uint32(innerbuf[0:4]))
	}
	if binary.BigEndian.Uint32(innerbuf[4:8]) != p.Command_id {
		t.Errorf("Expected command_id %d, got %d", p.Command_id, binary.BigEndian.Uint32(innerbuf[4:8]))
	}
	if binary.BigEndian.Uint32(innerbuf[8:12]) != p.Command_status {
		t.Errorf("Expected command_status %d, got %d", p.Command_status, binary.BigEndian.Uint32(innerbuf[8:12]))
	}
	if binary.BigEndian.Uint32(innerbuf[12:16]) != p.Sequence_number {
		t.Errorf("Expected sequence_number %d, got %d", p.Sequence_number, binary.BigEndian.Uint32(innerbuf[12:16]))
	}
}

func TestEncodeIntoHandlesLargerBuffer(t *testing.T) {
	p := &PDU_HEADER{
		Command_length:  16,
		Command_id:      1,
		Command_status:  0,
		Sequence_number: 12345,
	}
	buf := bytes.NewBuffer(make([]byte, 20)) // larger buffer size
	buf.Reset()
	err := p.Encode(buf)

	if err != nil {
		t.Errorf("Expected no error, got %v", err)
	}

	innerbuf := buf.Bytes()
	if binary.BigEndian.Uint32(innerbuf[0:4]) != p.Command_length {
		t.Errorf("Expected command_length %d, got %d", p.Command_length, binary.BigEndian.Uint32(innerbuf[0:4]))
	}
	if binary.BigEndian.Uint32(innerbuf[4:8]) != p.Command_id {
		t.Errorf("Expected command_id %d, got %d", p.Command_id, binary.BigEndian.Uint32(innerbuf[4:8]))
	}
	if binary.BigEndian.Uint32(innerbuf[8:12]) != p.Command_status {
		t.Errorf("Expected command_status %d, got %d", p.Command_status, binary.BigEndian.Uint32(innerbuf[8:12]))
	}
	if binary.BigEndian.Uint32(innerbuf[12:16]) != p.Sequence_number {
		t.Errorf("Expected sequence_number %d, got %d", p.Sequence_number, binary.BigEndian.Uint32(innerbuf[12:16]))
	}
}

func TestEncodeIntoUsesBigEndianEncoding(t *testing.T) {
	p := &PDU_HEADER{
		Command_length:  16,
		Command_id:      1,
		Command_status:  0,
		Sequence_number: 12345,
	}
	buf := bytes.NewBuffer(make([]byte, 16))
	buf.Reset()
	err := p.Encode(buf)

	if err != nil {
		t.Errorf("Expected no error, got %v", err)
	}

	innerbuf := buf.Bytes()
	if binary.BigEndian.Uint32(innerbuf[0:4]) != p.Command_length {
		t.Errorf("Expected command_length %d, got %d", p.Command_length, binary.BigEndian.Uint32(innerbuf[0:4]))
	}
	if binary.BigEndian.Uint32(innerbuf[4:8]) != p.Command_id {
		t.Errorf("Expected command_id %d, got %d", p.Command_id, binary.BigEndian.Uint32(innerbuf[4:8]))
	}
	if binary.BigEndian.Uint32(innerbuf[8:12]) != p.Command_status {
		t.Errorf("Expected command_status %d, got %d", p.Command_status, binary.BigEndian.Uint32(innerbuf[8:12]))
	}
	if binary.BigEndian.Uint32(innerbuf[12:16]) != p.Sequence_number {
		t.Errorf("Expected sequence_number %d, got %d", p.Sequence_number, binary.BigEndian.Uint32(innerbuf[12:16]))
	}
}

func TestEncodeIntoConcurrencySafety(t *testing.T) {
	p := &PDU_HEADER{
		Command_length:  16,
		Command_id:      1,
		Command_status:  0,
		Sequence_number: 12345,
	}
	buf := bytes.NewBuffer(make([]byte, 16))
	buf.Reset()
	var wg sync.WaitGroup
	for i := 0; i < 1000; i++ {
		wg.Add(1)
		go func() {
			defer wg.Done()
			p.Encode(buf)
		}()
	}
	wg.Wait()

	innerbuf := buf.Bytes()
	if binary.BigEndian.Uint32(innerbuf[0:4]) != p.Command_length {
		t.Errorf("Expected command_length %d, got %d", p.Command_length, binary.BigEndian.Uint32(innerbuf[0:4]))
	}
	if binary.BigEndian.Uint32(innerbuf[4:8]) != p.Command_id {
		t.Errorf("Expected command_id %d, got %d", p.Command_id, binary.BigEndian.Uint32(innerbuf[4:8]))
	}
	if binary.BigEndian.Uint32(innerbuf[8:12]) != p.Command_status {
		t.Errorf("Expected command_status %d, got %d", p.Command_status, binary.BigEndian.Uint32(innerbuf[8:12]))
	}
	if binary.BigEndian.Uint32(innerbuf[12:16]) != p.Sequence_number {
		t.Errorf("Expected sequence_number %d, got %d", p.Sequence_number, binary.BigEndian.Uint32(innerbuf[12:16]))
	}
}

func TestEncodeIntoWithMaximumValues(t *testing.T) {
	p := &PDU_HEADER{
		Command_length:  math.MaxUint32,
		Command_id:      math.MaxUint32,
		Command_status:  math.MaxUint32,
		Sequence_number: math.MaxUint32,
	}
	buf := bytes.NewBuffer(make([]byte, 16))
	buf.Reset()
	err := p.Encode(buf)

	if err != nil {
		t.Errorf("Expected no error, got %v", err)
	}

	innerbuf := buf.Bytes()
	if binary.BigEndian.Uint32(innerbuf[0:4]) != p.Command_length {
		t.Errorf("Expected command_length %d, got %d", p.Command_length, binary.BigEndian.Uint32(innerbuf[0:4]))
	}
	if binary.BigEndian.Uint32(innerbuf[4:8]) != p.Command_id {
		t.Errorf("Expected command_id %d, got %d", p.Command_id, binary.BigEndian.Uint32(innerbuf[4:8]))
	}
	if binary.BigEndian.Uint32(innerbuf[8:12]) != p.Command_status {
		t.Errorf("Expected command_status %d, got %d", p.Command_status, binary.BigEndian.Uint32(innerbuf[8:12]))
	}
	if binary.BigEndian.Uint32(innerbuf[12:16]) != p.Sequence_number {
		t.Errorf("Expected sequence_number %d, got %d", p.Sequence_number, binary.BigEndian.Uint32(innerbuf[12:16]))
	}
}

func TestEncodeIntoWithBoundaryValues(t *testing.T) {
	p := &PDU_HEADER{
		Command_length:  0,
		Command_id:      0,
		Command_status:  0,
		Sequence_number: 0,
	}
	buf := bytes.NewBuffer(make([]byte, 16))
	buf.Reset()
	err := p.Encode(buf)

	if err != nil {
		t.Errorf("Expected no error, got %v", err)
	}

	innerbuf := buf.Bytes()
	if binary.BigEndian.Uint32(innerbuf[0:4]) != p.Command_length {
		t.Errorf("Expected command_length %d, got %d", p.Command_length, binary.BigEndian.Uint32(innerbuf[0:4]))
	}
	if binary.BigEndian.Uint32(innerbuf[4:8]) != p.Command_id {
		t.Errorf("Expected command_id %d, got %d", p.Command_id, binary.BigEndian.Uint32(innerbuf[4:8]))
	}
	if binary.BigEndian.Uint32(innerbuf[8:12]) != p.Command_status {
		t.Errorf("Expected command_status %d, got %d", p.Command_status, binary.BigEndian.Uint32(innerbuf[8:12]))
	}
	if binary.BigEndian.Uint32(innerbuf[12:16]) != p.Sequence_number {
		t.Errorf("Expected sequence_number %d, got %d", p.Sequence_number, binary.BigEndian.Uint32(innerbuf[12:16]))
	}
}

// region decode
func TestDecodeHandlesShortByteSlice(t *testing.T) {
	var p PDU_HEADER
	data := []byte{0, 0, 0, 10}
	defer func() {
		if r := recover(); r != nil {
			t.Errorf("Decode panicked with short byte slice")
		}
	}()
	err := p.Decode(bytes.NewBuffer(data))
	if err != nil {
		t.Errorf("Expected no error, got %v", err)
	}
}

func TestDecodeParsesValidByteSlice(t *testing.T) {
	var p PDU_HEADER
	data := []byte{0, 0, 0, 16, 0, 0, 0, 1, 0, 0, 0, 2, 0, 0, 0, 3}
	err := p.Decode(bytes.NewBuffer(data))

	if err != nil {
		t.Errorf("Expected no error, got %v", err)
	}

	if p.Command_length != 16 {
		t.Errorf("Expected command_length to be 16, got %d", p.Command_length)
	}
	if p.Command_id != 1 {
		t.Errorf("Expected command_id to be 1, got %d", p.Command_id)
	}
	if p.Command_status != 2 {
		t.Errorf("Expected command_status to be 2, got %d", p.Command_status)
	}
	if p.Sequence_number != 3 {
		t.Errorf("Expected sequence_number to be 3, got %d", p.Sequence_number)
	}
}

func TestDecodeHandlesLongerByteSliceWithoutCrashing(t *testing.T) {
	var p PDU_HEADER
	data := make([]byte, 20)
	defer func() {
		if r := recover(); r != nil {
			t.Errorf("Decode panicked with long byte slice")
		}
	}()
	err := p.Decode(bytes.NewBuffer(data))
	if err != nil {
		t.Errorf("Expected no error, got %v", err)
	}
}

func TestDecodeHandlesNilDataInput(t *testing.T) {
	var p PDU_HEADER
	err := p.Decode(nil)

	if err == nil {
		t.Errorf("Expected error, got none")
	}

	if p.Command_length != 0 {
		t.Errorf("Expected command_length to be 0, got %d", p.Command_length)
	}
	if p.Command_id != 0 {
		t.Errorf("Expected command_id to be 0, got %d", p.Command_id)
	}
	if p.Command_status != 0 {
		t.Errorf("Expected command_status to be 0, got %d", p.Command_status)
	}
	if p.Sequence_number != 0 {
		t.Errorf("Expected sequence_number to be 0, got %d", p.Sequence_number)
	}
}

func TestDecodeHandlesEmptyByteSliceGracefully(t *testing.T) {
	var p PDU_HEADER
	data := []byte{}
	err := p.Decode(bytes.NewBuffer(data))

	if err != nil {
		t.Errorf("Expected no error, got %v", err)
	}

	if p.Command_length != 0 {
		t.Errorf("Expected command_length to be 0, got %d", p.Command_length)
	}
	if p.Command_id != 0 {
		t.Errorf("Expected command_id to be 0, got %d", p.Command_id)
	}
	if p.Command_status != 0 {
		t.Errorf("Expected command_status to be 0, got %d", p.Command_status)
	}
	if p.Sequence_number != 0 {
		t.Errorf("Expected sequence_number to be 0, got %d", p.Sequence_number)
	}
}

func TestDecodeDoesNotModifyInputByteSlice(t *testing.T) {
	var p PDU_HEADER
	data := []byte{0, 0, 0, 16, 0, 0, 0, 1, 0, 0, 0, 2, 0, 0, 0, 3}
	originalData := make([]byte, len(data))
	copy(originalData, data)

	err := p.Decode(bytes.NewBuffer(data))

	if err != nil {
		t.Errorf("Expected no error, got %v", err)
	}

	for i := range data {
		if data[i] != originalData[i] {
			t.Errorf("Expected data at index %d to be %d, got %d", i, originalData[i], data[i])
		}
	}
}

func TestDecodeHandlesByteSlicesWithMaxUint32Values(t *testing.T) {
	var p PDU_HEADER
	data := []byte{255, 255, 255, 255, 255, 255, 255, 255}
	err := p.Decode(bytes.NewBuffer(data))

	if err != nil {
		t.Errorf("Expected no error, got %v", err)
	}

	if p.Command_length != math.MaxUint32 {
		t.Errorf("Expected command_length to be %d, got %d", math.MaxUint32, p.Command_length)
	}
	if p.Command_id != math.MaxUint32 {
		t.Errorf("Expected command_id to be %d, got %d", math.MaxUint32, p.Command_id)
	}
}

func TestDecodeHandlesByteSlicesWithMinimumUint32Values(t *testing.T) {
	var p PDU_HEADER
	data := []byte{0, 0, 0, 0, 0, 0, 0, 0}
	err := p.Decode(bytes.NewBuffer(data))

	if err != nil {
		t.Errorf("Expected no error, got %v", err)
	}

	if p.Command_length != 0 {
		t.Errorf("Expected command_length to be 0, got %d", p.Command_length)
	}
	if p.Command_id != 0 {
		t.Errorf("Expected command_id to be 0, got %d", p.Command_id)
	}
}

// region size
func TestSizeReturns16(t *testing.T) {
	var p PDU_HEADER
	if p.Size() != 16 {
		t.Errorf("Expected size to be 16, got %d", p.Size())
	}
}

// region benchmarks
// With buffer pool
func BenchmarkEncodeWithBufferPool(b *testing.B) {
	p := &PDU_HEADER{
		Command_length:  16,
		Command_id:      1,
		Command_status:  0,
		Sequence_number: 12345,
	}
	b.ResetTimer()
	for i := 0; i < b.N; i++ {
		buf := ByteBufferPool.Get(p.Size())
		p.Encode(buf)
		ByteBufferPool.Put(buf)
	}
}

// Without buffer pool
func BenchmarkEncodeWithoutBufferPool(b *testing.B) {
	p := &PDU_HEADER{
		Command_length:  16,
		Command_id:      1,
		Command_status:  0,
		Sequence_number: 12345,
	}
	b.ResetTimer()
	for i := 0; i < b.N; i++ {
		buf := bytes.Buffer{}
		p.Encode(&buf)
	}
}

func BenchmarkDecodeBufferPool(b *testing.B) {
	p := &PDU_HEADER{}
	b.ResetTimer()
	for i := 0; i < b.N; i++ {
		data := []byte{0, 0, 0, 16, 0, 0, 0, 1, 0, 0, 0, 2, 0, 0, 0, 3}
		buf := ByteBufferPool.Get(len(data))
		buf.Write(data)
		p.Decode(buf)
		ByteBufferPool.Put(buf)
	}
}
