package usbip

import (
	"bytes"
	"encoding/binary"
	"fmt"
	"io"
)

// Protocol version
const ProtocolVersion = 0x0111

// Management phase opcodes
const (
	OpReqDevlist = 0x8005
	OpRepDevlist = 0x0005
	OpReqImport  = 0x8003
	OpRepImport  = 0x0003
)

// Data transfer phase commands
const (
	CmdSubmit = 0x00000001
	CmdUnlink = 0x00000002
	RetSubmit = 0x00000003
	RetUnlink = 0x00000004
)

// Transfer directions
const (
	DirOut = 0
	DirIn  = 1
)

// USB device speeds
const (
	SpeedUnknown   = 0
	SpeedLow       = 1
	SpeedFull      = 2
	SpeedHigh      = 3
	SpeedWireless  = 4
	SpeedSuper     = 5
	SpeedSuperPlus = 6
)

// OpHeader is the 8-byte header for management messages
type OpHeader struct {
	Version uint16
	Command uint16
	Status  uint32
}

// DeviceDescriptor describes a USB device in USB/IP protocol
type DeviceDescriptor struct {
	Path                [256]byte
	BusID               [32]byte
	BusNum              uint32
	DevNum              uint32
	Speed               uint32
	IDVendor            uint16
	IDProduct           uint16
	BcdDevice           uint16
	BDeviceClass        uint8
	BDeviceSubClass     uint8
	BDeviceProtocol     uint8
	BConfigurationValue uint8
	BNumConfigurations  uint8
	BNumInterfaces      uint8
}

// InterfaceDescriptor describes a USB interface
type InterfaceDescriptor struct {
	BInterfaceClass    uint8
	BInterfaceSubClass uint8
	BInterfaceProtocol uint8
	Padding            uint8
}

// URBHeader is the 48-byte common header for USB/IP transfer messages
type URBHeader struct {
	Command   uint32
	SeqNum    uint32
	DevID     uint32
	Direction uint32
	Endpoint  uint32
}

// CmdSubmitBody follows URBHeader for USBIP_CMD_SUBMIT
type CmdSubmitBody struct {
	TransferFlags     uint32
	TransferBufferLen uint32
	StartFrame        uint32
	NumberOfPackets   uint32
	Interval          uint32
	Setup             [8]byte
}

// RetSubmitBody follows URBHeader for USBIP_RET_SUBMIT
type RetSubmitBody struct {
	Status          int32
	ActualLength    uint32
	StartFrame      uint32
	NumberOfPackets uint32
	ErrorCount      uint32
	Padding         [8]byte
}

// CmdUnlinkBody follows URBHeader for USBIP_CMD_UNLINK
type CmdUnlinkBody struct {
	UnlinkSeqNum uint32
	Padding      [24]byte
}

// RetUnlinkBody follows URBHeader for USBIP_RET_UNLINK
type RetUnlinkBody struct {
	Status  int32
	Padding [24]byte
}

// ISOPacketDescriptor for isochronous transfers
type ISOPacketDescriptor struct {
	Offset       uint32
	Length       uint32
	ActualLength uint32
	Status       uint32
}

// --- Encoding/Decoding helpers ---

// WriteOpHeader writes an operation header
func WriteOpHeader(w io.Writer, cmd uint16, status uint32) error {
	h := OpHeader{Version: ProtocolVersion, Command: cmd, Status: status}
	return binary.Write(w, binary.BigEndian, &h)
}

// ReadOpHeader reads an operation header
func ReadOpHeader(r io.Reader) (*OpHeader, error) {
	h := &OpHeader{}
	if err := binary.Read(r, binary.BigEndian, h); err != nil {
		return nil, err
	}
	return h, nil
}

// WriteDeviceDescriptor writes a device descriptor
func WriteDeviceDescriptor(w io.Writer, d *DeviceDescriptor) error {
	return binary.Write(w, binary.BigEndian, d)
}

// ReadDeviceDescriptor reads a device descriptor
func ReadDeviceDescriptor(r io.Reader) (*DeviceDescriptor, error) {
	d := &DeviceDescriptor{}
	if err := binary.Read(r, binary.BigEndian, d); err != nil {
		return nil, err
	}
	return d, nil
}

// WriteInterfaceDescriptor writes an interface descriptor
func WriteInterfaceDescriptor(w io.Writer, d *InterfaceDescriptor) error {
	return binary.Write(w, binary.BigEndian, d)
}

// ReadURBHeader reads a URB header
func ReadURBHeader(r io.Reader) (*URBHeader, error) {
	h := &URBHeader{}
	if err := binary.Read(r, binary.BigEndian, h); err != nil {
		return nil, err
	}
	return h, nil
}

// WriteURBHeader writes a URB header
func WriteURBHeader(w io.Writer, h *URBHeader) error {
	return binary.Write(w, binary.BigEndian, h)
}

// ReadCmdSubmit reads a CMD_SUBMIT body (after URB header)
func ReadCmdSubmit(r io.Reader) (*CmdSubmitBody, error) {
	b := &CmdSubmitBody{}
	if err := binary.Read(r, binary.BigEndian, b); err != nil {
		return nil, err
	}
	return b, nil
}

// WriteCmdSubmit writes a CMD_SUBMIT body
func WriteCmdSubmit(w io.Writer, b *CmdSubmitBody) error {
	return binary.Write(w, binary.BigEndian, b)
}

// ReadRetSubmit reads a RET_SUBMIT body
func ReadRetSubmit(r io.Reader) (*RetSubmitBody, error) {
	b := &RetSubmitBody{}
	if err := binary.Read(r, binary.BigEndian, b); err != nil {
		return nil, err
	}
	return b, nil
}

// WriteRetSubmit writes a RET_SUBMIT body
func WriteRetSubmit(w io.Writer, b *RetSubmitBody) error {
	return binary.Write(w, binary.BigEndian, b)
}

// ReadCmdUnlink reads a CMD_UNLINK body
func ReadCmdUnlink(r io.Reader) (*CmdUnlinkBody, error) {
	b := &CmdUnlinkBody{}
	if err := binary.Read(r, binary.BigEndian, b); err != nil {
		return nil, err
	}
	return b, nil
}

// WriteRetUnlink writes a RET_UNLINK body
func WriteRetUnlink(w io.Writer, b *RetUnlinkBody) error {
	return binary.Write(w, binary.BigEndian, b)
}

// --- High-level message builders ---

// BuildDevlistReply builds a complete OP_REP_DEVLIST response
func BuildDevlistReply(devices []DeviceDescriptor, interfaces [][]InterfaceDescriptor) ([]byte, error) {
	buf := &bytes.Buffer{}

	// Header
	if err := WriteOpHeader(buf, OpRepDevlist, 0); err != nil {
		return nil, err
	}

	// Number of devices
	if err := binary.Write(buf, binary.BigEndian, uint32(len(devices))); err != nil {
		return nil, err
	}

	// Each device + its interfaces
	for i, dev := range devices {
		if err := WriteDeviceDescriptor(buf, &dev); err != nil {
			return nil, err
		}
		if i < len(interfaces) {
			for _, iface := range interfaces[i] {
				if err := WriteInterfaceDescriptor(buf, &iface); err != nil {
					return nil, err
				}
			}
		}
	}

	return buf.Bytes(), nil
}

// BuildImportReply builds an OP_REP_IMPORT response
func BuildImportReply(status uint32, dev *DeviceDescriptor) ([]byte, error) {
	buf := &bytes.Buffer{}

	if err := WriteOpHeader(buf, OpRepImport, status); err != nil {
		return nil, err
	}

	if status == 0 && dev != nil {
		if err := WriteDeviceDescriptor(buf, dev); err != nil {
			return nil, err
		}
	}

	return buf.Bytes(), nil
}

// BuildRetSubmit builds a RET_SUBMIT message.
// actualLength must be set for BOTH directions: it reports how many bytes
// were actually transferred. For OUT transfers the kernel driver checks this
// (e.g. UVC probe control expects actualLength == 26).
func BuildRetSubmit(seqNum, devID, direction, endpoint uint32, status int32, actualLength uint32, data []byte) ([]byte, error) {
	buf := &bytes.Buffer{}

	hdr := &URBHeader{
		Command:   RetSubmit,
		SeqNum:    seqNum,
		DevID:     devID,
		Direction: direction,
		Endpoint:  endpoint,
	}
	if err := WriteURBHeader(buf, hdr); err != nil {
		return nil, err
	}

	body := &RetSubmitBody{
		Status:          status,
		ActualLength:    actualLength,
		NumberOfPackets: 0xFFFFFFFF,
	}
	if err := WriteRetSubmit(buf, body); err != nil {
		return nil, err
	}

	// Transfer buffer for IN direction
	if direction == DirIn && len(data) > 0 {
		buf.Write(data)
	}

	return buf.Bytes(), nil
}

// BuildRetSubmitISO builds a RET_SUBMIT message for isochronous transfers.
// The transfer data must already be packed (compact, no gaps).
func BuildRetSubmitISO(seqNum, devID, direction, endpoint uint32, status int32,
	actualLength uint32, packedData []byte, startFrame uint32, numPackets int32, errorCount int32,
	isoDescs []ISOPacketDescriptor) ([]byte, error) {

	buf := &bytes.Buffer{}

	hdr := &URBHeader{
		Command:   RetSubmit,
		SeqNum:    seqNum,
		DevID:     devID,
		Direction: direction,
		Endpoint:  endpoint,
	}
	if err := WriteURBHeader(buf, hdr); err != nil {
		return nil, err
	}

	body := &RetSubmitBody{
		Status:          status,
		ActualLength:    actualLength,
		StartFrame:      startFrame,
		NumberOfPackets: uint32(numPackets),
		ErrorCount:      uint32(errorCount),
	}
	if err := WriteRetSubmit(buf, body); err != nil {
		return nil, err
	}

	// Transfer buffer (packed) for IN direction
	if direction == DirIn && len(packedData) > 0 {
		buf.Write(packedData)
	}

	// ISO packet descriptors
	for _, desc := range isoDescs {
		if err := binary.Write(buf, binary.BigEndian, &desc); err != nil {
			return nil, err
		}
	}

	return buf.Bytes(), nil
}

// BuildRetUnlink builds a RET_UNLINK message
func BuildRetUnlink(seqNum, devID uint32, status int32) ([]byte, error) {
	buf := &bytes.Buffer{}

	hdr := &URBHeader{
		Command:   RetUnlink,
		SeqNum:    seqNum,
		DevID:     devID,
		Direction: 0,
		Endpoint:  0,
	}
	if err := WriteURBHeader(buf, hdr); err != nil {
		return nil, err
	}

	body := &RetUnlinkBody{Status: status}
	if err := WriteRetUnlink(buf, body); err != nil {
		return nil, err
	}

	return buf.Bytes(), nil
}

// SetBusID sets a bus ID string in a fixed-size byte array
func SetBusID(arr *[32]byte, busID string) {
	copy(arr[:], busID)
}

// SetPath sets a path string in a fixed-size byte array
func SetPath(arr *[256]byte, path string) {
	copy(arr[:], path)
}

// GetBusID extracts a bus ID string from a fixed-size byte array
func GetBusID(arr [32]byte) string {
	n := bytes.IndexByte(arr[:], 0)
	if n < 0 {
		n = 32
	}
	return string(arr[:n])
}

// GetPath extracts a path string from a fixed-size byte array
func GetPath(arr [256]byte) string {
	n := bytes.IndexByte(arr[:], 0)
	if n < 0 {
		n = 256
	}
	return string(arr[:n])
}

// SpeedString returns a human-readable speed name
func SpeedString(speed uint32) string {
	switch speed {
	case SpeedLow:
		return "1.5 Mbps (Low)"
	case SpeedFull:
		return "12 Mbps (Full)"
	case SpeedHigh:
		return "480 Mbps (High)"
	case SpeedSuper:
		return "5 Gbps (Super)"
	case SpeedSuperPlus:
		return "10 Gbps (Super+)"
	default:
		return fmt.Sprintf("Unknown (%d)", speed)
	}
}
