package helpers_test

import (
	"bytes"
	"testing"

	"github.com/jfraeys/fetch_ml/internal/api/helpers"
)

func TestNewPayloadParser(t *testing.T) {
	payload := []byte{0x01, 0x02, 0x03, 0x04, 0x05}
	p := helpers.NewPayloadParser(payload, 2)

	if p.Offset() != 2 {
		t.Errorf("expected offset 2, got %d", p.Offset())
	}
	if !bytes.Equal(p.Payload(), payload) {
		t.Error("payload mismatch")
	}
}

func TestPayloadParser_ParseByte(t *testing.T) {
	tests := []struct {
		name    string
		payload []byte
		start   int
		want    byte
		wantErr bool
	}{
		{
			name:    "valid byte",
			payload: []byte{0x00, 0x01, 0x02},
			start:   1,
			want:    0x01,
			wantErr: false,
		},
		{
			name:    "end of payload",
			payload: []byte{0x00, 0x01},
			start:   2,
			want:    0,
			wantErr: true,
		},
		{
			name:    "empty payload",
			payload: []byte{},
			start:   0,
			want:    0,
			wantErr: true,
		},
	}

	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			p := helpers.NewPayloadParser(tt.payload, tt.start)
			got, err := p.ParseByte()
			if (err != nil) != tt.wantErr {
				t.Errorf("ParseByte() error = %v, wantErr %v", err, tt.wantErr)
				return
			}
			if got != tt.want {
				t.Errorf("ParseByte() = %v, want %v", got, tt.want)
			}
		})
	}
}

func TestPayloadParser_ParseUint16(t *testing.T) {
	tests := []struct {
		name    string
		payload []byte
		start   int
		want    uint16
		wantErr bool
	}{
		{
			name:    "valid uint16",
			payload: []byte{0x01, 0x02, 0x03, 0x04},
			start:   0,
			want:    0x0102,
			wantErr: false,
		},
		{
			name:    "another valid uint16",
			payload: []byte{0x00, 0xAB, 0xCD},
			start:   1,
			want:    0xABCD,
			wantErr: false,
		},
		{
			name:    "too short",
			payload: []byte{0x00, 0x01},
			start:   1,
			want:    0,
			wantErr: true,
		},
		{
			name:    "empty at end",
			payload: []byte{0x00, 0x01, 0x02},
			start:   3,
			want:    0,
			wantErr: true,
		},
	}

	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			p := helpers.NewPayloadParser(tt.payload, tt.start)
			got, err := p.ParseUint16()
			if (err != nil) != tt.wantErr {
				t.Errorf("ParseUint16() error = %v, wantErr %v", err, tt.wantErr)
				return
			}
			if got != tt.want {
				t.Errorf("ParseUint16() = %v, want %v", got, tt.want)
			}
		})
	}
}

func TestPayloadParser_ParseLengthPrefixedString(t *testing.T) {
	tests := []struct {
		name    string
		payload []byte
		start   int
		want    string
		wantErr bool
	}{
		{
			name:    "valid string",
			payload: []byte{0x05, 'h', 'e', 'l', 'l', 'o'},
			start:   0,
			want:    "hello",
			wantErr: false,
		},
		{
			name:    "empty string",
			payload: []byte{0x00, 0x01, 0x02},
			start:   0,
			want:    "",
			wantErr: false,
		},
		{
			name:    "from offset",
			payload: []byte{0x00, 0x03, 'f', 'o', 'o'},
			start:   1,
			want:    "foo",
			wantErr: false,
		},
		{
			name:    "too short for length",
			payload: []byte{0x05, 'h', 'i'},
			start:   0,
			want:    "",
			wantErr: true,
		},
		{
			name:    "no length byte",
			payload: []byte{},
			start:   0,
			want:    "",
			wantErr: true,
		},
	}

	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			p := helpers.NewPayloadParser(tt.payload, tt.start)
			got, err := p.ParseLengthPrefixedString()
			if (err != nil) != tt.wantErr {
				t.Errorf("ParseLengthPrefixedString() error = %v, wantErr %v", err, tt.wantErr)
				return
			}
			if got != tt.want {
				t.Errorf("ParseLengthPrefixedString() = %v, want %v", got, tt.want)
			}
		})
	}
}

func TestPayloadParser_ParseUint16PrefixedString(t *testing.T) {
	tests := []struct {
		name    string
		payload []byte
		start   int
		want    string
		wantErr bool
	}{
		{
			name:    "valid string",
			payload: []byte{0x00, 0x05, 'h', 'e', 'l', 'l', 'o'},
			start:   0,
			want:    "hello",
			wantErr: false,
		},
		{
			name:    "empty string",
			payload: []byte{0x00, 0x00, 0x01, 0x02},
			start:   0,
			want:    "",
			wantErr: false,
		},
		{
			name:    "from offset",
			payload: []byte{0x00, 0x00, 0x00, 0x03, 'b', 'a', 'r'},
			start:   2,
			want:    "bar",
			wantErr: false,
		},
		{
			name:    "too short for length",
			payload: []byte{0x00},
			start:   0,
			want:    "",
			wantErr: true,
		},
		{
			name:    "too short for string",
			payload: []byte{0x00, 0x05, 'h', 'i'},
			start:   0,
			want:    "",
			wantErr: true,
		},
	}

	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			p := helpers.NewPayloadParser(tt.payload, tt.start)
			got, err := p.ParseUint16PrefixedString()
			if (err != nil) != tt.wantErr {
				t.Errorf("ParseUint16PrefixedString() error = %v, wantErr %v", err, tt.wantErr)
				return
			}
			if got != tt.want {
				t.Errorf("ParseUint16PrefixedString() = %v, want %v", got, tt.want)
			}
		})
	}
}

func TestPayloadParser_ParseBool(t *testing.T) {
	tests := []struct {
		name    string
		payload []byte
		start   int
		want    bool
		wantErr bool
	}{
		{
			name:    "true",
			payload: []byte{0x01, 0x00},
			start:   0,
			want:    true,
			wantErr: false,
		},
		{
			name:    "false",
			payload: []byte{0x00, 0x01},
			start:   0,
			want:    false,
			wantErr: false,
		},
		{
			name:    "non-zero is true",
			payload: []byte{0xFF},
			start:   0,
			want:    true,
			wantErr: false,
		},
		{
			name:    "empty",
			payload: []byte{},
			start:   0,
			want:    false,
			wantErr: true,
		},
	}

	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			p := helpers.NewPayloadParser(tt.payload, tt.start)
			got, err := p.ParseBool()
			if (err != nil) != tt.wantErr {
				t.Errorf("ParseBool() error = %v, wantErr %v", err, tt.wantErr)
				return
			}
			if got != tt.want {
				t.Errorf("ParseBool() = %v, want %v", got, tt.want)
			}
		})
	}
}

func TestPayloadParser_ParseFixedBytes(t *testing.T) {
	tests := []struct {
		name    string
		payload []byte
		start   int
		length  int
		want    []byte
		wantErr bool
	}{
		{
			name:    "valid",
			payload: []byte{0x00, 0x01, 0x02, 0x03, 0x04},
			start:   1,
			length:  3,
			want:    []byte{0x01, 0x02, 0x03},
			wantErr: false,
		},
		{
			name:    "exact length",
			payload: []byte{0x00, 0x01, 0x02},
			start:   0,
			length:  3,
			want:    []byte{0x00, 0x01, 0x02},
			wantErr: false,
		},
		{
			name:    "too long",
			payload: []byte{0x00, 0x01},
			start:   0,
			length:  3,
			want:    nil,
			wantErr: true,
		},
		{
			name:    "from end",
			payload: []byte{0x00, 0x01, 0x02},
			start:   2,
			length:  2,
			want:    nil,
			wantErr: true,
		},
	}

	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			p := helpers.NewPayloadParser(tt.payload, tt.start)
			got, err := p.ParseFixedBytes(tt.length)
			if (err != nil) != tt.wantErr {
				t.Errorf("ParseFixedBytes() error = %v, wantErr %v", err, tt.wantErr)
				return
			}
			if !bytes.Equal(got, tt.want) {
				t.Errorf("ParseFixedBytes() = %v, want %v", got, tt.want)
			}
		})
	}
}

func TestPayloadParser_Offset(t *testing.T) {
	payload := []byte{0x00, 0x01, 0x02, 0x03}
	p := helpers.NewPayloadParser(payload, 1)

	if p.Offset() != 1 {
		t.Errorf("initial Offset() = %d, want 1", p.Offset())
	}

	p.ParseByte()
	if p.Offset() != 2 {
		t.Errorf("after ParseByte() Offset() = %d, want 2", p.Offset())
	}
}

func TestPayloadParser_Remaining(t *testing.T) {
	payload := []byte{0x00, 0x01, 0x02, 0x03}
	p := helpers.NewPayloadParser(payload, 2)

	remaining := p.Remaining()
	if !bytes.Equal(remaining, []byte{0x02, 0x03}) {
		t.Errorf("Remaining() = %v, want [0x02, 0x03]", remaining)
	}

	// At end
	p = helpers.NewPayloadParser(payload, 4)
	if p.Remaining() != nil {
		t.Errorf("at end Remaining() = %v, want nil", p.Remaining())
	}

	// Beyond end
	p = helpers.NewPayloadParser(payload, 5)
	if p.Remaining() != nil {
		t.Errorf("beyond end Remaining() = %v, want nil", p.Remaining())
	}
}

func TestPayloadParser_HasRemaining(t *testing.T) {
	payload := []byte{0x00, 0x01}

	tests := []struct {
		start int
		want  bool
	}{
		{0, true},
		{1, true},
		{2, false},
		{3, false},
	}

	for _, tt := range tests {
		p := helpers.NewPayloadParser(payload, tt.start)
		if got := p.HasRemaining(); got != tt.want {
			t.Errorf("HasRemaining() with start=%d = %v, want %v", tt.start, got, tt.want)
		}
	}
}

func TestPayloadParser_ChainedParsing(t *testing.T) {
	// Simulate a real protocol: [api_key:2][name_len:1][name:var][value:2]
	payload := []byte{
		0xAB, 0xCD, // api_key
		0x05,                    // name_len
		'h', 'e', 'l', 'l', 'o', // name
		0x00, 0x42, // value
	}

	p := helpers.NewPayloadParser(payload, 0)

	// Parse api key (2 bytes)
	apiKey, err := p.ParseFixedBytes(2)
	if err != nil {
		t.Fatalf("failed to parse api key: %v", err)
	}
	if !bytes.Equal(apiKey, []byte{0xAB, 0xCD}) {
		t.Errorf("api key = %v, want [0xAB, 0xCD]", apiKey)
	}

	// Parse name
	name, err := p.ParseLengthPrefixedString()
	if err != nil {
		t.Fatalf("failed to parse name: %v", err)
	}
	if name != "hello" {
		t.Errorf("name = %q, want 'hello'", name)
	}

	// Parse value
	value, err := p.ParseUint16()
	if err != nil {
		t.Fatalf("failed to parse value: %v", err)
	}
	if value != 0x0042 {
		t.Errorf("value = 0x%X, want 0x0042", value)
	}

	// Should be at end
	if p.HasRemaining() {
		t.Error("expected no remaining bytes")
	}
}
