"""USB HID LED client — controls RGB peripherals via HID protocol using hidapi."""

import asyncio
from datetime import datetime, timezone
from typing import List, Optional, Tuple, Union

import numpy as np

from wled_controller.core.devices.led_client import DeviceHealth, LEDClient
from wled_controller.utils import get_logger

logger = get_logger(__name__)


def _parse_hid_url(url: str) -> Tuple[int, int]:
    """Parse 'hid://vendor_id:product_id' to (vid, pid) ints."""
    path = url.replace("hid://", "")
    parts = path.split(":")
    if len(parts) != 2:
        raise ValueError(f"Invalid HID URL: {url}. Expected format: hid://VID:PID (hex)")
    return int(parts[0], 16), int(parts[1], 16)


class USBHIDClient(LEDClient):
    """LED client that controls RGB peripherals via USB HID reports.

    Uses the hidapi library to send raw HID reports containing LED color data.
    The report format is device-specific but follows a common pattern:
        [REPORT_ID][MODE][LED_INDEX][R][G][B]...

    For generic HID RGB devices, a bulk-set report is used:
        [REPORT_ID=0x00][CMD=0x0E][ZONE_COUNT][R G B R G B ...]
    """

    # Common HID report IDs and commands
    REPORT_ID_LED = 0x00
    CMD_SET_LEDS = 0x0E
    MAX_REPORT_SIZE = 64  # typical HID report size

    def __init__(
        self,
        url: str = "",
        led_count: int = 0,
        hid_usage_page: int = 0,
        **kwargs,
    ):
        self._vid, self._pid = _parse_hid_url(url)
        self._led_count = led_count
        self._usage_page = hid_usage_page
        self._device = None
        self._connected = False

    async def connect(self) -> bool:
        try:
            import hid
        except ImportError:
            raise RuntimeError("hidapi is required for USB HID devices: pip install hidapi")

        loop = asyncio.get_event_loop()

        def _open():
            device = hid.device()
            device.open(self._vid, self._pid)
            device.set_nonblocking(True)
            return device

        self._device = await loop.run_in_executor(None, _open)
        self._connected = True

        manufacturer = self._device.get_manufacturer_string() or "Unknown"
        product = self._device.get_product_string() or "Unknown"
        logger.info(
            "USB HID client connected: %04X:%04X (%s %s) leds=%d",
            self._vid, self._pid, manufacturer, product, self._led_count,
        )
        return True

    async def close(self) -> None:
        if self._device:
            loop = asyncio.get_event_loop()
            await loop.run_in_executor(None, self._device.close)
        self._device = None
        self._connected = False
        logger.info("USB HID client closed: %04X:%04X", self._vid, self._pid)

    @property
    def is_connected(self) -> bool:
        return self._connected and self._device is not None

    async def send_pixels(
        self,
        pixels: Union[List[Tuple[int, int, int]], np.ndarray],
        brightness: int = 255,
    ) -> bool:
        if not self.is_connected:
            return False

        if isinstance(pixels, np.ndarray):
            pixel_list = pixels.tolist()
        else:
            pixel_list = list(pixels)

        # Note: brightness already applied by processor loop (_cached_brightness)

        # Build HID reports — split across multiple reports if needed
        # Each report: [REPORT_ID][CMD][OFFSET_LO][OFFSET_HI][COUNT][R G B R G B ...]
        max_leds_per_report = (self.MAX_REPORT_SIZE - 5) // 3
        offset = 0
        reports = []

        while offset < len(pixel_list):
            chunk = pixel_list[offset: offset + max_leds_per_report]
            report = bytearray(self.MAX_REPORT_SIZE)
            report[0] = self.REPORT_ID_LED
            report[1] = self.CMD_SET_LEDS
            report[2] = offset & 0xFF
            report[3] = (offset >> 8) & 0xFF
            report[4] = len(chunk)

            for i, (r, g, b) in enumerate(chunk):
                base = 5 + i * 3
                report[base] = int(r)
                report[base + 1] = int(g)
                report[base + 2] = int(b)

            reports.append(bytes(report))
            offset += len(chunk)

        loop = asyncio.get_event_loop()

        def _send_all():
            for report in reports:
                self._device.write(report)

        try:
            await loop.run_in_executor(None, _send_all)
            return True
        except Exception as e:
            logger.error("USB HID send failed: %s", e)
            self._connected = False
            return False

    @classmethod
    async def check_health(
        cls,
        url: str,
        http_client,
        prev_health: Optional[DeviceHealth] = None,
    ) -> DeviceHealth:
        """Check if the HID device is present."""
        try:
            import hid

            vid, pid = _parse_hid_url(url)
            devices = hid.enumerate(vid, pid)
            if devices:
                info = devices[0]
                return DeviceHealth(
                    online=True,
                    latency_ms=0.0,
                    device_name=info.get("product_string", "USB HID Device"),
                    last_checked=datetime.now(timezone.utc),
                )
            return DeviceHealth(
                online=False,
                error=f"HID device {vid:04X}:{pid:04X} not found",
                last_checked=datetime.now(timezone.utc),
            )
        except ImportError:
            return DeviceHealth(
                online=False, error="hidapi not installed",
                last_checked=datetime.now(timezone.utc),
            )
        except Exception as e:
            return DeviceHealth(
                online=False, error=str(e),
                last_checked=datetime.now(timezone.utc),
            )
