package usb

import "testing"

// buildDescriptorBlob assembles a device descriptor followed by raw
// configuration bytes, the way a usbdevfs file read returns them.
func buildDescriptorBlob(numConfigs uint8, configs ...[]byte) []byte {
	dev := []byte{
		18,         // bLength
		0x01,       // bDescriptorType = DEVICE
		0x00, 0x02, // bcdUSB 2.00
		0x00,       // bDeviceClass (per-interface)
		0x00,       // bDeviceSubClass
		0x00,       // bDeviceProtocol
		64,         // bMaxPacketSize0
		0x6d, 0x04, // idVendor  046d
		0x1c, 0xc0, // idProduct c01c
		0x10, 0x02, // bcdDevice 0210
		1, 2, 3, // string indices
		numConfigs,
	}
	blob := dev
	for _, c := range configs {
		blob = append(blob, c...)
	}
	return blob
}

func ifaceDesc(number, alt, numEndpoints, class, subclass, protocol uint8) []byte {
	return []byte{9, 0x04, number, alt, numEndpoints, class, subclass, protocol, 0}
}

func endpointDesc(addr, attrs uint8, maxPacket uint16, interval uint8) []byte {
	return []byte{7, 0x05, addr, attrs, byte(maxPacket), byte(maxPacket >> 8), interval}
}

func configDesc(value uint8, body []byte) []byte {
	total := 9 + len(body)
	cfg := []byte{9, 0x02, byte(total), byte(total >> 8), 1, value, 0, 0x80, 250}
	return append(cfg, body...)
}

func TestParseDescriptorsDeviceFields(t *testing.T) {
	blob := buildDescriptorBlob(1, configDesc(1, ifaceDesc(0, 0, 0, 3, 1, 1)))

	pd, err := ParseDescriptors(blob)
	if err != nil {
		t.Fatalf("ParseDescriptors: %v", err)
	}

	if pd.VendorID != 0x046d {
		t.Errorf("VendorID = %04x, want 046d", pd.VendorID)
	}
	if pd.ProductID != 0xc01c {
		t.Errorf("ProductID = %04x, want c01c", pd.ProductID)
	}
	if pd.BcdDevice != 0x0210 {
		t.Errorf("BcdDevice = %04x, want 0210", pd.BcdDevice)
	}
	if pd.NumConfigs != 1 {
		t.Errorf("NumConfigs = %d, want 1", pd.NumConfigs)
	}
	if len(pd.Configs) != 1 {
		t.Fatalf("got %d configs, want 1", len(pd.Configs))
	}
}

// A composite device where endpoint number 1 appears twice with different
// directions and different transfer types. Keying the endpoint map by number
// alone collapses these two into one, which is what made the server submit
// interrupt URBs with the bulk type and broke HID devices.
func TestAllEndpointsKeepsDirectionsSeparate(t *testing.T) {
	body := ifaceDesc(0, 0, 2, 0x08, 0x06, 0x50)                 // mass storage
	body = append(body, endpointDesc(0x01, 0x02, 512, 0)...)     // bulk OUT, EP1
	body = append(body, endpointDesc(0x82, 0x02, 512, 0)...)     // bulk IN,  EP2
	body = append(body, ifaceDesc(1, 0, 1, 0x03, 0x01, 0x01)...) // HID keyboard
	body = append(body, endpointDesc(0x81, 0x03, 8, 10)...)      // interrupt IN, EP1

	blob := buildDescriptorBlob(1, configDesc(1, body))
	pd, err := ParseDescriptors(blob)
	if err != nil {
		t.Fatalf("ParseDescriptors: %v", err)
	}

	eps := pd.Configs[0].AllEndpoints()
	if len(eps) != 3 {
		t.Fatalf("got %d endpoints, want 3: %+v", len(eps), eps)
	}

	if got := eps[0x01].TransferType; got != TransferTypeBulk {
		t.Errorf("EP 0x01 type = %d, want bulk (%d)", got, TransferTypeBulk)
	}
	if got := eps[0x81].TransferType; got != TransferTypeInterrupt {
		t.Errorf("EP 0x81 type = %d, want interrupt (%d) — direction bit must not collapse", got, TransferTypeInterrupt)
	}
	if got := eps[0x81].Interval; got != 10 {
		t.Errorf("EP 0x81 interval = %d, want 10", got)
	}
	if got := eps[0x82].MaxPacketSize; got != 512 {
		t.Errorf("EP 0x82 maxpacket = %d, want 512", got)
	}
}

// A webcam's isochronous endpoints only exist in a non-zero alternate
// setting. sysfs shows only the active setting, so an endpoint map built from
// it would classify these as bulk after a SET_INTERFACE.
func TestAllEndpointsIncludesAlternateSettings(t *testing.T) {
	body := ifaceDesc(1, 0, 0, 0x0e, 0x02, 0x00) // video streaming, alt 0: no endpoints
	body = append(body, ifaceDesc(1, 1, 1, 0x0e, 0x02, 0x00)...)
	body = append(body, endpointDesc(0x81, 0x05, 1024, 1)...) // isochronous IN
	body = append(body, ifaceDesc(1, 2, 1, 0x0e, 0x02, 0x00)...)
	body = append(body, endpointDesc(0x81, 0x05, 2048, 1)...)

	blob := buildDescriptorBlob(1, configDesc(1, body))
	pd, err := ParseDescriptors(blob)
	if err != nil {
		t.Fatalf("ParseDescriptors: %v", err)
	}

	cfg := pd.Configs[0]
	if len(cfg.Interfaces) != 3 {
		t.Fatalf("got %d interface descriptors, want 3 (alt 0,1,2)", len(cfg.Interfaces))
	}

	eps := cfg.AllEndpoints()
	ep, ok := eps[0x81]
	if !ok {
		t.Fatal("EP 0x81 missing — endpoints from non-zero alternate settings were dropped")
	}
	if ep.TransferType != TransferTypeIsochronous {
		t.Errorf("EP 0x81 type = %d, want isochronous (%d)", ep.TransferType, TransferTypeIsochronous)
	}
}

func TestActiveInterfacesOnlyAltZero(t *testing.T) {
	body := ifaceDesc(0, 0, 0, 0x01, 0x01, 0x00)
	body = append(body, ifaceDesc(1, 0, 0, 0x01, 0x02, 0x00)...)
	body = append(body, ifaceDesc(1, 1, 1, 0x01, 0x02, 0x00)...)
	body = append(body, endpointDesc(0x81, 0x05, 192, 1)...)

	blob := buildDescriptorBlob(1, configDesc(1, body))
	pd, _ := ParseDescriptors(blob)

	active := pd.Configs[0].ActiveInterfaces()
	if len(active) != 2 {
		t.Fatalf("got %d active interfaces, want 2 (one per interface number)", len(active))
	}
	for _, iface := range active {
		if iface.AltSetting != 0 {
			t.Errorf("interface %d has alt setting %d, want 0", iface.Number, iface.AltSetting)
		}
	}
}

// Class-specific descriptors (HID, UVC, audio) sit between the standard ones
// and must be skipped by bLength rather than confusing the walk.
func TestParseDescriptorsSkipsClassSpecific(t *testing.T) {
	hidDesc := []byte{9, 0x21, 0x11, 0x01, 0x00, 0x01, 0x22, 0x3f, 0x00}

	body := ifaceDesc(0, 0, 1, 0x03, 0x01, 0x01)
	body = append(body, hidDesc...)
	body = append(body, endpointDesc(0x81, 0x03, 8, 10)...)

	blob := buildDescriptorBlob(1, configDesc(1, body))
	pd, err := ParseDescriptors(blob)
	if err != nil {
		t.Fatalf("ParseDescriptors: %v", err)
	}

	eps := pd.Configs[0].AllEndpoints()
	if _, ok := eps[0x81]; !ok {
		t.Fatal("endpoint after a HID descriptor was not parsed")
	}
	if len(pd.Configs[0].Interfaces[0].Endpoints) != 1 {
		t.Errorf("got %d endpoints on the interface, want 1",
			len(pd.Configs[0].Interfaces[0].Endpoints))
	}
}

func TestFindConfigSelectsByValue(t *testing.T) {
	blob := buildDescriptorBlob(2,
		configDesc(1, ifaceDesc(0, 0, 0, 0x03, 0, 0)),
		configDesc(2, ifaceDesc(0, 0, 0, 0x08, 0, 0)),
	)
	pd, err := ParseDescriptors(blob)
	if err != nil {
		t.Fatalf("ParseDescriptors: %v", err)
	}

	cfg := pd.FindConfig(2)
	if cfg == nil {
		t.Fatal("FindConfig(2) returned nil")
	}
	if cfg.Interfaces[0].Class != 0x08 {
		t.Errorf("got interface class %02x, want 08 — wrong configuration selected", cfg.Interfaces[0].Class)
	}
	if pd.FindConfig(9) != nil {
		t.Error("FindConfig(9) should return nil for a configuration that does not exist")
	}
}

func TestParseDescriptorsRejectsGarbage(t *testing.T) {
	tests := []struct {
		name string
		data []byte
	}{
		{"empty", nil},
		{"too short", []byte{18, 0x01, 0x00}},
		{"not a device descriptor", append([]byte{9, 0x02}, make([]byte, 20)...)},
		{"no configuration", buildDescriptorBlob(1)},
	}

	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			if _, err := ParseDescriptors(tt.data); err == nil {
				t.Error("expected an error, got nil")
			}
		})
	}
}

// A truncated or zero-length descriptor must terminate the walk instead of
// looping forever or reading past the buffer.
func TestParseDescriptorsHandlesTruncation(t *testing.T) {
	blob := buildDescriptorBlob(1, configDesc(1, ifaceDesc(0, 0, 1, 3, 1, 1)))
	blob = append(blob, 0x00, 0x05) // zero bLength would spin forever
	blob = append(blob, 9, 0x04)    // interface descriptor claiming 9 bytes, only 2 present

	done := make(chan struct{})
	go func() {
		defer close(done)
		if _, err := ParseDescriptors(blob); err != nil {
			t.Errorf("unexpected error: %v", err)
		}
	}()

	<-done
}
