//go:build linux

package usbip

import (
	"testing"

	"github.com/duffy/usb-server/internal/usb"
)

func newTestServer(eps map[uint8]usb.Endpoint) *Server {
	dev := &usb.Device{
		BusID:     "1-1",
		Endpoints: eps,
	}
	s := NewServer(dev)
	s.buildEndpointTypeMap()
	return s
}

// The composite case that broke HID: endpoint number 1 exists as bulk OUT
// (0x01) and interrupt IN (0x81). Both must keep their own transfer type.
func TestGetURBTypeSeparatesDirections(t *testing.T) {
	s := newTestServer(map[uint8]usb.Endpoint{
		0x01: {Address: 0x01, TransferType: usb.TransferTypeBulk},
		0x81: {Address: 0x81, TransferType: usb.TransferTypeInterrupt, Interval: 10},
		0x82: {Address: 0x82, TransferType: usb.TransferTypeIsochronous, Interval: 1},
	})

	tests := []struct {
		name     string
		epAddr   uint8
		interval uint32
		packets  int32
		want     uint8
	}{
		{"bulk OUT endpoint 1", 0x01, 0, 0, usbdevfsTypeBulk},
		{"interrupt IN endpoint 1", 0x81, 10, 0, usbdevfsTypeInterrupt},
		{"isochronous IN endpoint 2", 0x82, 1, 8, usbdevfsTypeISO},
		{"control endpoint 0", 0x00, 0, 0, usbdevfsTypeControl},
		{"control endpoint 0 IN", 0x80, 0, 0, usbdevfsTypeControl},
	}

	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			got := s.getURBType(tt.epAddr, tt.interval, tt.packets)
			if got != tt.want {
				t.Errorf("getURBType(0x%02x, interval=%d, packets=%d) = %s, want %s",
					tt.epAddr, tt.interval, tt.packets, urbTypeName[got], urbTypeName[tt.want])
			}
		})
	}
}

// An endpoint missing from the descriptor map must not default to bulk when
// the request carries an interval: only periodic transfers have one, and
// submitting an interrupt endpoint's URB as bulk is what the kernel rejects.
func TestGetURBTypeFallsBackOnInterval(t *testing.T) {
	s := newTestServer(nil)

	if got := s.getURBType(0x83, 8, 0); got != usbdevfsTypeInterrupt {
		t.Errorf("unknown endpoint with interval=8: got %s, want INT", urbTypeName[got])
	}
	if got := s.getURBType(0x02, 0, 0); got != usbdevfsTypeBulk {
		t.Errorf("unknown endpoint with interval=0: got %s, want BULK", urbTypeName[got])
	}
}

// NumberOfPackets is authoritative for isochronous transfers: a webcam only
// activates its ISO endpoints after SET_INTERFACE, so the descriptor map may
// still describe the alternate-setting-0 view when the request arrives.
func TestGetURBTypeISOWinsOverMap(t *testing.T) {
	s := newTestServer(map[uint8]usb.Endpoint{
		0x81: {Address: 0x81, TransferType: usb.TransferTypeBulk},
	})

	if got := s.getURBType(0x81, 1, 16); got != usbdevfsTypeISO {
		t.Errorf("packets=16 should force ISO, got %s", urbTypeName[got])
	}
}

func TestBuildEndpointTypeMapFallsBackToInterfaces(t *testing.T) {
	dev := &usb.Device{
		BusID: "1-1",
		Interfaces: []usb.Interface{{
			Number: 0,
			Class:  0x03,
			Endpoints: []usb.Endpoint{
				{Address: 0x81, TransferType: usb.TransferTypeInterrupt, Interval: 10},
			},
		}},
	}

	s := NewServer(dev)
	s.buildEndpointTypeMap()

	if got := s.getURBType(0x81, 10, 0); got != usbdevfsTypeInterrupt {
		t.Errorf("sysfs fallback lost the interrupt type: got %s", urbTypeName[got])
	}
}
