//go:build linux

package usb

import (
	"errors"
	"fmt"
	"os"
	"time"
	"unsafe"

	"golang.org/x/sys/unix"
)

// ioctl direction constants
const (
	iocNone  = 0
	iocWrite = 1
	iocRead  = 2
)

// ioctl encoding helpers
func ioc(dir, typ, nr, size uintptr) uintptr {
	return (dir << 30) | (size << 16) | (typ << 8) | nr
}

func ior(typ, nr, size uintptr) uintptr  { return ioc(iocRead, typ, nr, size) }
func iow(typ, nr, size uintptr) uintptr  { return ioc(iocWrite, typ, nr, size) }
func iowr(typ, nr, size uintptr) uintptr { return ioc(iocRead|iocWrite, typ, nr, size) }
func io_(typ, nr uintptr) uintptr        { return ioc(iocNone, typ, nr, 0) }

// USBDEVFS_DISCONNECT_CLAIM flags
const disconnectClaimIfDriver = 0x01

// USB device file system ioctl numbers
var (
	usbdevfsControl          = iowr('U', 0, unsafe.Sizeof(usbdevfsCtrlTransfer{}))
	usbdevfsBulk             = iowr('U', 2, unsafe.Sizeof(usbdevfsBulkTransfer{}))
	usbdevfsSetInterface     = ior('U', 4, unsafe.Sizeof(usbdevfsSetIntf{}))
	usbdevfsSetConfig        = ior('U', 5, 4)
	usbdevfsSubmitURB        = ior('U', 10, unsafe.Sizeof(usbdevfsURB{}))
	usbdevfsResetEP          = ior('U', 3, 4)
	usbdevfsDiscardURB       = io_('U', 11)
	usbdevfsReapURB          = iow('U', 12, unsafe.Sizeof(uintptr(0)))
	usbdevfsReapURBNDelay    = iow('U', 13, unsafe.Sizeof(uintptr(0)))
	usbdevfsClaimInterface   = ior('U', 15, 4)
	usbdevfsReleaseInterface = ior('U', 16, 4)
	usbdevfsIoctl            = iowr('U', 18, unsafe.Sizeof(usbdevfsIoctlArg{}))
	usbdevfsReset            = io_('U', 20)
	usbdevfsClearHalt        = ior('U', 21, 4)
	usbdevfsDisconnect       = io_('U', 22)
	usbdevfsConnect          = io_('U', 23)
	usbdevfsGetCapabilities  = ior('U', 26, 4)
	usbdevfsDisconnectClaim  = ior('U', 27, unsafe.Sizeof(usbdevfsDisconnectClaimArg{}))
	usbdevfsGetSpeed         = io_('U', 31)
)

// URB type constants
const (
	urbTypeISO       = 0
	urbTypeInterrupt = 1
	urbTypeControl   = 2
	urbTypeBulk      = 3
)

// usbdevfs structures for ioctls

type usbdevfsCtrlTransfer struct {
	RequestType uint8
	Request     uint8
	Value       uint16
	Index       uint16
	Length      uint16
	Timeout     uint32
	Data        uintptr
}

type usbdevfsBulkTransfer struct {
	Endpoint uint32
	Length   uint32
	Timeout  uint32
	Data     uintptr
}

type usbdevfsSetIntf struct {
	Interface  uint32
	AltSetting uint32
}

type usbdevfsISOPacketDesc struct {
	Length       uint32
	ActualLength uint32
	Status       uint32
}

// usbdevfsIoctlArg is the argument for USBDEVFS_IOCTL (per-interface sub-ioctl)
type usbdevfsIoctlArg struct {
	Ifno      int32
	IoctlCode int32
	Data      uintptr
}

// usbdevfsDisconnectClaimArg is the argument for USBDEVFS_DISCONNECT_CLAIM
type usbdevfsDisconnectClaimArg struct {
	Interface uint32
	Flags     uint32
	Driver    [256]byte
}

type usbdevfsURB struct {
	Type            uint8
	Endpoint        uint8
	Status          int32
	Flags           uint32
	Buffer          uintptr
	BufferLength    int32
	ActualLength    int32
	StartFrame      int32
	NumberOfPackets int32 // or StreamID
	ErrorCount      int32
	Signr           uint32
	UserContext     uintptr
	// ISO packet descriptors follow in memory if Type == urbTypeISO
}

// DeviceHandle provides low-level USB device access via usbdevfs
type DeviceHandle struct {
	fd      int
	busID   string
	devPath string

	// adopted marks a descriptor handed to us from outside rather than
	// opened here. It is closed on Close like any other, but the distinction
	// matters for diagnostics: an adopted descriptor means the host process
	// could not have opened the device itself.
	adopted bool
}

// OpenDevice opens a USB device file for direct access.
//
// If an external file descriptor has been registered for this device (see
// AdoptDeviceFD) it is used instead of opening the path. That is how Android
// works: apps cannot open /dev/bus/usb themselves, so a small Java shim asks
// the system for permission and hands the resulting descriptor down.
func OpenDevice(devPath string, busID string) (*DeviceHandle, error) {
	if fd, ok := takeAdoptedFD(busID); ok {
		return &DeviceHandle{fd: fd, busID: busID, devPath: devPath, adopted: true}, nil
	}

	fd, err := unix.Open(devPath, unix.O_RDWR, 0)
	if err != nil {
		return nil, fmt.Errorf("opening %s: %w", devPath, err)
	}

	return &DeviceHandle{
		fd:      fd,
		busID:   busID,
		devPath: devPath,
	}, nil
}

// Close closes the device handle
func (h *DeviceHandle) Close() error {
	return unix.Close(h.fd)
}

// Fd returns the file descriptor
func (h *DeviceHandle) Fd() int {
	return h.fd
}

// DisconnectDriver disconnects the kernel driver from the device
func (h *DeviceHandle) DisconnectDriver() error {
	_, _, errno := unix.Syscall(unix.SYS_IOCTL, uintptr(h.fd), usbdevfsDisconnect, 0)
	if errno != 0 {
		return fmt.Errorf("USBDEVFS_DISCONNECT: %w", errno)
	}
	return nil
}

// ConnectDriver reconnects the kernel driver
func (h *DeviceHandle) ConnectDriver() error {
	_, _, errno := unix.Syscall(unix.SYS_IOCTL, uintptr(h.fd), usbdevfsConnect, 0)
	if errno != 0 {
		return fmt.Errorf("USBDEVFS_CONNECT: %w", errno)
	}
	return nil
}

// DisconnectDriverForInterface disconnects the kernel driver from a specific interface
// Uses USBDEVFS_IOCTL with USBDEVFS_DISCONNECT sub-ioctl
func (h *DeviceHandle) DisconnectDriverForInterface(ifnum uint32) error {
	arg := usbdevfsIoctlArg{
		Ifno:      int32(ifnum),
		IoctlCode: int32(usbdevfsDisconnect),
		Data:      0,
	}
	_, _, errno := unix.Syscall(unix.SYS_IOCTL, uintptr(h.fd), usbdevfsIoctl, uintptr(unsafe.Pointer(&arg)))
	if errno != 0 && errno != unix.ENODATA { // ENODATA = no driver bound, that's OK
		return fmt.Errorf("USBDEVFS_IOCTL(DISCONNECT, iface %d): %w", ifnum, errno)
	}
	return nil
}

// DisconnectClaimInterface atomically disconnects kernel driver and claims an interface
// Uses USBDEVFS_DISCONNECT_CLAIM (available since Linux 3.7)
func (h *DeviceHandle) DisconnectClaimInterface(ifnum uint32) error {
	arg := usbdevfsDisconnectClaimArg{
		Interface: ifnum,
		Flags:     disconnectClaimIfDriver,
	}
	_, _, errno := unix.Syscall(unix.SYS_IOCTL, uintptr(h.fd), usbdevfsDisconnectClaim, uintptr(unsafe.Pointer(&arg)))
	if errno != 0 {
		return fmt.Errorf("USBDEVFS_DISCONNECT_CLAIM(%d): %w", ifnum, errno)
	}
	return nil
}

// ClaimInterface claims exclusive access to a USB interface
func (h *DeviceHandle) ClaimInterface(ifnum uint32) error {
	_, _, errno := unix.Syscall(unix.SYS_IOCTL, uintptr(h.fd), usbdevfsClaimInterface, uintptr(unsafe.Pointer(&ifnum)))
	if errno != 0 {
		return fmt.Errorf("USBDEVFS_CLAIMINTERFACE(%d): %w", ifnum, errno)
	}
	return nil
}

// ReleaseInterface releases a claimed interface
func (h *DeviceHandle) ReleaseInterface(ifnum uint32) error {
	_, _, errno := unix.Syscall(unix.SYS_IOCTL, uintptr(h.fd), usbdevfsReleaseInterface, uintptr(unsafe.Pointer(&ifnum)))
	if errno != 0 {
		return fmt.Errorf("USBDEVFS_RELEASEINTERFACE(%d): %w", ifnum, errno)
	}
	return nil
}

// SetConfiguration sets the device configuration
func (h *DeviceHandle) SetConfiguration(config uint32) error {
	_, _, errno := unix.Syscall(unix.SYS_IOCTL, uintptr(h.fd), usbdevfsSetConfig, uintptr(unsafe.Pointer(&config)))
	if errno != 0 {
		return fmt.Errorf("USBDEVFS_SETCONFIGURATION(%d): %w", config, errno)
	}
	return nil
}

// SetInterface sets alternate setting for an interface
func (h *DeviceHandle) SetInterface(iface, altSetting uint32) error {
	si := usbdevfsSetIntf{Interface: iface, AltSetting: altSetting}
	_, _, errno := unix.Syscall(unix.SYS_IOCTL, uintptr(h.fd), usbdevfsSetInterface, uintptr(unsafe.Pointer(&si)))
	if errno != 0 {
		return fmt.Errorf("USBDEVFS_SETINTERFACE(%d, %d): %w", iface, altSetting, errno)
	}
	return nil
}

// ClearHalt clears endpoint halt/stall condition
func (h *DeviceHandle) ClearHalt(endpoint uint32) error {
	_, _, errno := unix.Syscall(unix.SYS_IOCTL, uintptr(h.fd), usbdevfsClearHalt, uintptr(unsafe.Pointer(&endpoint)))
	if errno != 0 {
		return fmt.Errorf("USBDEVFS_CLEAR_HALT(%d): %w", endpoint, errno)
	}
	return nil
}

// ResetDevice resets the USB device
func (h *DeviceHandle) ResetDevice() error {
	_, _, errno := unix.Syscall(unix.SYS_IOCTL, uintptr(h.fd), usbdevfsReset, 0)
	if errno != 0 {
		return fmt.Errorf("USBDEVFS_RESET: %w", errno)
	}
	return nil
}

// GetSpeed returns the device speed
func (h *DeviceHandle) GetSpeed() (uint32, error) {
	r, _, errno := unix.Syscall(unix.SYS_IOCTL, uintptr(h.fd), usbdevfsGetSpeed, 0)
	if errno != 0 {
		return 0, fmt.Errorf("USBDEVFS_GET_SPEED: %w", errno)
	}
	return uint32(r), nil
}

// ControlTransfer performs a synchronous control transfer
func (h *DeviceHandle) ControlTransfer(requestType, request uint8, value, index, length uint16, timeout uint32, data []byte) (int, error) {
	var dataPtr uintptr
	if len(data) > 0 {
		dataPtr = uintptr(unsafe.Pointer(&data[0]))
	}

	ct := usbdevfsCtrlTransfer{
		RequestType: requestType,
		Request:     request,
		Value:       value,
		Index:       index,
		Length:      length,
		Timeout:     timeout,
		Data:        dataPtr,
	}

	r, _, errno := unix.Syscall(unix.SYS_IOCTL, uintptr(h.fd), usbdevfsControl, uintptr(unsafe.Pointer(&ct)))
	if errno != 0 {
		return 0, fmt.Errorf("USBDEVFS_CONTROL: %w", errno)
	}
	return int(r), nil
}

// SubmitURBParams holds parameters for async URB submission
type SubmitURBParams struct {
	Type        uint8
	Endpoint    uint8
	Flags       uint32
	Buffer      []byte
	UserContext uintptr
}

// SubmitURB submits an asynchronous URB
func (h *DeviceHandle) SubmitURB(params *SubmitURBParams) (*usbdevfsURB, error) {
	var bufPtr uintptr
	if len(params.Buffer) > 0 {
		bufPtr = uintptr(unsafe.Pointer(&params.Buffer[0]))
	}

	urb := &usbdevfsURB{
		Type:            params.Type,
		Endpoint:        params.Endpoint,
		Flags:           params.Flags,
		Buffer:          bufPtr,
		BufferLength:    int32(len(params.Buffer)),
		NumberOfPackets: -1, // 0xFFFFFFFF for non-ISO
		UserContext:     params.UserContext,
	}

	_, _, errno := unix.Syscall(unix.SYS_IOCTL, uintptr(h.fd), usbdevfsSubmitURB, uintptr(unsafe.Pointer(urb)))
	if errno != 0 {
		return nil, fmt.Errorf("USBDEVFS_SUBMITURB: %w", errno)
	}
	return urb, nil
}

// urbFromKernelPtr converts the uintptr USBDEVFS_REAPURB writes back into a
// *usbdevfsURB.
//
// go vet flags this as "possible misuse of unsafe.Pointer", correctly in
// general: the garbage collector cannot see a pointer stored in a uintptr, so
// the object could be collected before the conversion. It is safe here because
// the kernel only ever returns a pointer we submitted ourselves, and the
// caller keeps that URB reachable — in pendingURBs or unlinkedURBs on the
// server — from submission until after it has been reaped.
func urbFromKernelPtr(p uintptr) *usbdevfsURB {
	//nolint:govet // see the comment above
	return (*usbdevfsURB)(unsafe.Pointer(p))
}

// ReapURB blocks until a URB completes, then returns it
func (h *DeviceHandle) ReapURB() (*usbdevfsURB, error) {
	var urbPtr uintptr
	_, _, errno := unix.Syscall(unix.SYS_IOCTL, uintptr(h.fd), usbdevfsReapURB, uintptr(unsafe.Pointer(&urbPtr)))
	if errno != 0 {
		return nil, fmt.Errorf("USBDEVFS_REAPURB: %w", errno)
	}
	return urbFromKernelPtr(urbPtr), nil
}

// ReapURBNonBlock tries to reap a URB without blocking
func (h *DeviceHandle) ReapURBNonBlock() (*usbdevfsURB, error) {
	var urbPtr uintptr
	_, _, errno := unix.Syscall(unix.SYS_IOCTL, uintptr(h.fd), usbdevfsReapURBNDelay, uintptr(unsafe.Pointer(&urbPtr)))
	if errno != 0 {
		return nil, fmt.Errorf("USBDEVFS_REAPURBNDELAY: %w", errno)
	}
	return urbFromKernelPtr(urbPtr), nil
}

// DiscardURB cancels a submitted URB
func (h *DeviceHandle) DiscardURB(urb *usbdevfsURB) error {
	_, _, errno := unix.Syscall(unix.SYS_IOCTL, uintptr(h.fd), usbdevfsDiscardURB, uintptr(unsafe.Pointer(urb)))
	if errno != 0 {
		return fmt.Errorf("USBDEVFS_DISCARDURB: %w", errno)
	}
	return nil
}

// DiscardURBByPtr cancels a submitted URB given its raw pointer.
// Use this when the URB type is not accessible (e.g. from another package).
func (h *DeviceHandle) DiscardURBByPtr(ptr unsafe.Pointer) error {
	_, _, errno := unix.Syscall(unix.SYS_IOCTL, uintptr(h.fd), usbdevfsDiscardURB, uintptr(ptr))
	if errno != 0 {
		return fmt.Errorf("USBDEVFS_DISCARDURB: %w", errno)
	}
	return nil
}

// ResetEndpoint resets the host-side data toggle for an endpoint without
// sending any USB traffic to the device. Use after SET_CONFIGURATION to
// sync host controller toggle state with the device.
func (h *DeviceHandle) ResetEndpoint(endpoint uint32) error {
	_, _, errno := unix.Syscall(unix.SYS_IOCTL, uintptr(h.fd), usbdevfsResetEP, uintptr(unsafe.Pointer(&endpoint)))
	if errno != 0 {
		return fmt.Errorf("USBDEVFS_RESETEP(%d): %w", endpoint, errno)
	}
	return nil
}

// SubmitISOURBParams holds parameters for async ISO URB submission
type SubmitISOURBParams struct {
	Endpoint        uint8
	Flags           uint32
	Buffer          []byte
	NumberOfPackets int32
	PacketLengths   []uint32 // length of each ISO packet
	UserContext     uintptr
}

// SubmitISOURB submits an asynchronous isochronous URB.
// Returns the URB pointer and the backing memory slice (must be kept alive until reap).
func (h *DeviceHandle) SubmitISOURB(params *SubmitISOURBParams) (urb *usbdevfsURB, mem []byte, err error) {
	urbSize := unsafe.Sizeof(usbdevfsURB{})
	isoDescSize := unsafe.Sizeof(usbdevfsISOPacketDesc{})
	totalSize := urbSize + uintptr(params.NumberOfPackets)*isoDescSize

	mem = make([]byte, totalSize)
	urb = (*usbdevfsURB)(unsafe.Pointer(&mem[0]))

	var bufPtr uintptr
	if len(params.Buffer) > 0 {
		bufPtr = uintptr(unsafe.Pointer(&params.Buffer[0]))
	}

	urb.Type = urbTypeISO
	urb.Endpoint = params.Endpoint
	urb.Flags = params.Flags
	urb.Buffer = bufPtr
	urb.BufferLength = int32(len(params.Buffer))
	urb.NumberOfPackets = params.NumberOfPackets
	urb.UserContext = params.UserContext

	// Fill ISO packet descriptors (immediately following the URB in memory)
	for i := int32(0); i < params.NumberOfPackets; i++ {
		descOffset := urbSize + uintptr(i)*isoDescSize
		desc := (*usbdevfsISOPacketDesc)(unsafe.Pointer(&mem[descOffset]))
		if i < int32(len(params.PacketLengths)) {
			desc.Length = params.PacketLengths[i]
		}
	}

	_, _, errno := unix.Syscall(unix.SYS_IOCTL, uintptr(h.fd), usbdevfsSubmitURB, uintptr(unsafe.Pointer(urb)))
	if errno != 0 {
		return nil, nil, fmt.Errorf("USBDEVFS_SUBMITURB (ISO): %w", errno)
	}
	return urb, mem, nil
}

// ISOPacketResult holds the result of one ISO packet after reaping
type ISOPacketResult struct {
	Length       uint32
	ActualLength uint32
	Status       uint32
}

// ReadISOResults reads the ISO packet results from a reaped ISO URB's backing memory.
func ReadISOResults(mem []byte, numPackets int32) []ISOPacketResult {
	urbSize := unsafe.Sizeof(usbdevfsURB{})
	isoDescSize := unsafe.Sizeof(usbdevfsISOPacketDesc{})

	results := make([]ISOPacketResult, numPackets)
	for i := int32(0); i < numPackets; i++ {
		offset := urbSize + uintptr(i)*isoDescSize
		if int(offset+isoDescSize) > len(mem) {
			break
		}
		desc := (*usbdevfsISOPacketDesc)(unsafe.Pointer(&mem[offset]))
		results[i] = ISOPacketResult{
			Length:       desc.Length,
			ActualLength: desc.ActualLength,
			Status:       desc.Status,
		}
	}
	return results
}

// ReapedURBInfo holds exported fields from a reaped URB needed for response building
type ReapedURBInfo struct {
	UserContext  uintptr
	Status       int32
	ActualLength int32
	StartFrame   int32
	ErrorCount   int32
}

// ErrNoURBReady is returned by ReapURBInfoNonBlock when no URB has completed.
var ErrNoURBReady = errors.New("no completed URB available")

// ErrDeviceGone is returned when the device has been unplugged or the file
// descriptor is no longer usable.
var ErrDeviceGone = errors.New("device gone")

// WaitForURB waits up to timeout for at least one URB to complete.
// It returns true if a URB is ready to be reaped, false on timeout.
//
// usbdevfs signals completed URBs via POLLOUT, so polling lets the reap loop
// stay responsive to shutdown without either spinning on a non-blocking ioctl
// or blocking indefinitely in USBDEVFS_REAPURB. The latter matters: a blocking
// reap can only be broken by closing the fd, which races with the fd being
// reused by another goroutine.
func (h *DeviceHandle) WaitForURB(timeout time.Duration) (bool, error) {
	fds := []unix.PollFd{{Fd: int32(h.fd), Events: unix.POLLOUT}}

	ms := int(timeout.Milliseconds())
	if ms < 0 {
		ms = 0
	}

	for {
		n, err := unix.Poll(fds, ms)
		if err == unix.EINTR {
			continue // interrupted by a signal, not an error
		}
		if err != nil {
			return false, fmt.Errorf("poll: %w", err)
		}
		if n == 0 {
			return false, nil // timeout
		}
		// POLLERR/POLLHUP/POLLNVAL mean the device is gone or the fd was closed.
		if fds[0].Revents&(unix.POLLERR|unix.POLLHUP|unix.POLLNVAL) != 0 {
			return false, ErrDeviceGone
		}
		return fds[0].Revents&unix.POLLOUT != 0, nil
	}
}

// ReapURBInfoNonBlock reaps one completed URB without blocking.
// Returns ErrNoURBReady if none has completed, ErrDeviceGone if the device
// has been disconnected.
func (h *DeviceHandle) ReapURBInfoNonBlock() (*ReapedURBInfo, error) {
	var urbPtr uintptr
	_, _, errno := unix.Syscall(unix.SYS_IOCTL, uintptr(h.fd), usbdevfsReapURBNDelay, uintptr(unsafe.Pointer(&urbPtr)))
	if errno != 0 {
		switch errno {
		case unix.EAGAIN:
			return nil, ErrNoURBReady
		case unix.ENODEV, unix.ESHUTDOWN, unix.EBADF, unix.ENOENT:
			return nil, ErrDeviceGone
		default:
			return nil, fmt.Errorf("USBDEVFS_REAPURBNDELAY: %w", errno)
		}
	}
	if urbPtr == 0 {
		return nil, ErrNoURBReady
	}
	urb := urbFromKernelPtr(urbPtr)
	return &ReapedURBInfo{
		UserContext:  urb.UserContext,
		Status:       urb.Status,
		ActualLength: urb.ActualLength,
		StartFrame:   urb.StartFrame,
		ErrorCount:   urb.ErrorCount,
	}, nil
}

// ReapURBInfo blocks until a URB completes and returns exported info
func (h *DeviceHandle) ReapURBInfo() (*ReapedURBInfo, error) {
	var urbPtr uintptr
	_, _, errno := unix.Syscall(unix.SYS_IOCTL, uintptr(h.fd), usbdevfsReapURB, uintptr(unsafe.Pointer(&urbPtr)))
	if errno != 0 {
		return nil, fmt.Errorf("USBDEVFS_REAPURB: %w", errno)
	}
	urb := urbFromKernelPtr(urbPtr)
	return &ReapedURBInfo{
		UserContext:  urb.UserContext,
		Status:       urb.Status,
		ActualLength: urb.ActualLength,
		StartFrame:   urb.StartFrame,
		ErrorCount:   urb.ErrorCount,
	}, nil
}

// GetFile returns an os.File wrapping the device fd (useful for epoll/select)
func (h *DeviceHandle) GetFile() *os.File {
	return os.NewFile(uintptr(h.fd), h.devPath)
}
