package usbip

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

// The USB/IP wire format is fixed: a 20-byte basic header followed by a
// 28-byte body. Any drift here desynchronises the stream permanently, so the
// sizes are pinned.
func TestWireFormatSizes(t *testing.T) {
	var buf bytes.Buffer

	if err := WriteURBHeader(&buf, &URBHeader{}); err != nil {
		t.Fatalf("WriteURBHeader: %v", err)
	}
	if buf.Len() != 20 {
		t.Errorf("URB header is %d bytes, want 20", buf.Len())
	}

	buf.Reset()
	if err := WriteCmdSubmit(&buf, &CmdSubmitBody{}); err != nil {
		t.Fatalf("WriteCmdSubmit: %v", err)
	}
	if buf.Len() != 28 {
		t.Errorf("CMD_SUBMIT body is %d bytes, want 28", buf.Len())
	}

	buf.Reset()
	if err := WriteRetSubmit(&buf, &RetSubmitBody{}); err != nil {
		t.Fatalf("WriteRetSubmit: %v", err)
	}
	if buf.Len() != 28 {
		t.Errorf("RET_SUBMIT body is %d bytes, want 28", buf.Len())
	}

	buf.Reset()
	if err := WriteRetUnlink(&buf, &RetUnlinkBody{}); err != nil {
		t.Fatalf("WriteRetUnlink: %v", err)
	}
	if buf.Len() != 28 {
		t.Errorf("RET_UNLINK body is %d bytes, want 28", buf.Len())
	}
}

func TestBuildRetSubmitInDirection(t *testing.T) {
	payload := []byte{0x01, 0x02, 0x03, 0x04}

	msg, err := BuildRetSubmit(42, 0x00030002, DirIn, 1, 0, uint32(len(payload)), payload)
	if err != nil {
		t.Fatalf("BuildRetSubmit: %v", err)
	}

	if len(msg) != 48+len(payload) {
		t.Fatalf("message is %d bytes, want %d", len(msg), 48+len(payload))
	}

	hdr, err := ReadURBHeader(bytes.NewReader(msg))
	if err != nil {
		t.Fatalf("ReadURBHeader: %v", err)
	}
	if hdr.Command != RetSubmit {
		t.Errorf("command = 0x%08x, want RET_SUBMIT", hdr.Command)
	}
	if hdr.SeqNum != 42 {
		t.Errorf("seqnum = %d, want 42", hdr.SeqNum)
	}
	if hdr.Direction != DirIn {
		t.Errorf("direction = %d, want IN", hdr.Direction)
	}

	body, err := ReadRetSubmit(bytes.NewReader(msg[20:]))
	if err != nil {
		t.Fatalf("ReadRetSubmit: %v", err)
	}
	if body.ActualLength != uint32(len(payload)) {
		t.Errorf("actual_length = %d, want %d", body.ActualLength, len(payload))
	}
	if body.NumberOfPackets != 0xFFFFFFFF {
		t.Errorf("number_of_packets = %d, want 0xFFFFFFFF for non-ISO", body.NumberOfPackets)
	}
	if !bytes.Equal(msg[48:], payload) {
		t.Errorf("payload = %x, want %x", msg[48:], payload)
	}
}

// actual_length must be reported for OUT transfers too. The kernel UVC driver
// checks it: a VS_PROBE SET_CUR that reports 0 instead of 26 fails the probe.
func TestBuildRetSubmitOutReportsActualLength(t *testing.T) {
	msg, err := BuildRetSubmit(7, 1, DirOut, 0, 0, 26, nil)
	if err != nil {
		t.Fatalf("BuildRetSubmit: %v", err)
	}
	if len(msg) != 48 {
		t.Fatalf("OUT reply is %d bytes, want 48 (no payload)", len(msg))
	}

	body, _ := ReadRetSubmit(bytes.NewReader(msg[20:]))
	if body.ActualLength != 26 {
		t.Errorf("actual_length = %d, want 26", body.ActualLength)
	}
}

// An IN reply carries no payload when the transfer failed, and the negative
// status has to survive the unsigned round trip on the wire.
func TestBuildRetSubmitErrorStatus(t *testing.T) {
	msg, err := BuildRetSubmit(9, 1, DirIn, 2, -32, 0, nil)
	if err != nil {
		t.Fatalf("BuildRetSubmit: %v", err)
	}
	if len(msg) != 48 {
		t.Fatalf("error reply is %d bytes, want 48", len(msg))
	}

	status := int32(binary.BigEndian.Uint32(msg[20:24]))
	if status != -32 {
		t.Errorf("status = %d, want -32 (-EPIPE)", status)
	}
}

func TestBuildRetSubmitISO(t *testing.T) {
	descs := []ISOPacketDescriptor{
		{Offset: 0, Length: 192, ActualLength: 192, Status: 0},
		{Offset: 192, Length: 192, ActualLength: 100, Status: 0},
	}
	packed := make([]byte, 292) // 192 + 100 actual bytes, packed without gaps

	msg, err := BuildRetSubmitISO(5, 1, DirIn, 1, 0, 292, packed, 1000, 2, 0, descs)
	if err != nil {
		t.Fatalf("BuildRetSubmitISO: %v", err)
	}

	wantLen := 48 + len(packed) + 2*16
	if len(msg) != wantLen {
		t.Fatalf("ISO reply is %d bytes, want %d", len(msg), wantLen)
	}

	body, _ := ReadRetSubmit(bytes.NewReader(msg[20:]))
	if body.NumberOfPackets != 2 {
		t.Errorf("number_of_packets = %d, want 2", body.NumberOfPackets)
	}
	if body.StartFrame != 1000 {
		t.Errorf("start_frame = %d, want 1000", body.StartFrame)
	}

	// Descriptors follow the packed payload, big-endian.
	descOff := 48 + len(packed)
	var got ISOPacketDescriptor
	if err := binary.Read(bytes.NewReader(msg[descOff:]), binary.BigEndian, &got); err != nil {
		t.Fatalf("reading ISO descriptor: %v", err)
	}
	if got.Length != 192 || got.ActualLength != 192 {
		t.Errorf("first descriptor = %+v, want length 192 actual 192", got)
	}
}

func TestBusIDRoundTrip(t *testing.T) {
	tests := []string{"1-1", "1-4.3.2", "", "12345678901234567890123456789012"}

	for _, want := range tests {
		var arr [32]byte
		SetBusID(&arr, want)
		if got := GetBusID(arr); got != want {
			t.Errorf("GetBusID(SetBusID(%q)) = %q", want, got)
		}
	}
}

func TestSetBusIDTruncatesOverlongInput(t *testing.T) {
	var arr [32]byte
	SetBusID(&arr, "this-bus-id-is-far-longer-than-thirty-two-bytes")
	if got := len(GetBusID(arr)); got != 32 {
		t.Errorf("overlong bus ID produced %d bytes, want 32", got)
	}
}

func TestReadCmdSubmitParsesSetupPacket(t *testing.T) {
	var buf bytes.Buffer
	body := &CmdSubmitBody{
		TransferBufferLen: 18,
		NumberOfPackets:   0,
		Interval:          0,
		// GET_DESCRIPTOR(device): bmRequestType=0x80 bRequest=0x06 wValue=0x0100
		Setup: [8]byte{0x80, 0x06, 0x00, 0x01, 0x00, 0x00, 0x12, 0x00},
	}
	if err := WriteCmdSubmit(&buf, body); err != nil {
		t.Fatalf("WriteCmdSubmit: %v", err)
	}

	got, err := ReadCmdSubmit(&buf)
	if err != nil {
		t.Fatalf("ReadCmdSubmit: %v", err)
	}

	// Setup fields are little-endian even though the surrounding header is not.
	if wValue := binary.LittleEndian.Uint16(got.Setup[2:4]); wValue != 0x0100 {
		t.Errorf("wValue = 0x%04x, want 0x0100", wValue)
	}
	if wLength := binary.LittleEndian.Uint16(got.Setup[6:8]); wLength != 18 {
		t.Errorf("wLength = %d, want 18", wLength)
	}
	if got.TransferBufferLen != 18 {
		t.Errorf("transfer_buffer_length = %d, want 18", got.TransferBufferLen)
	}
}

func TestBuildImportReply(t *testing.T) {
	desc := &DeviceDescriptor{BusNum: 1, DevNum: 4, Speed: SpeedHigh}
	SetBusID(&desc.BusID, "1-4")

	ok, err := BuildImportReply(0, desc)
	if err != nil {
		t.Fatalf("BuildImportReply: %v", err)
	}
	if len(ok) != 8+312 {
		t.Errorf("successful reply is %d bytes, want %d", len(ok), 8+312)
	}

	// A failure carries only the header — no descriptor follows.
	fail, err := BuildImportReply(1, nil)
	if err != nil {
		t.Fatalf("BuildImportReply(1): %v", err)
	}
	if len(fail) != 8 {
		t.Errorf("failure reply is %d bytes, want 8", len(fail))
	}

	hdr, _ := ReadOpHeader(bytes.NewReader(fail))
	if hdr.Status != 1 {
		t.Errorf("status = %d, want 1", hdr.Status)
	}
	if hdr.Version != ProtocolVersion {
		t.Errorf("version = 0x%04x, want 0x%04x", hdr.Version, ProtocolVersion)
	}
}
