"""ESP-NOW device provider — ultra-low-latency LED control via ESP32 gateway."""

from __future__ import annotations

from typing import TYPE_CHECKING, List

from ledgrab.core.devices.espnow_client import ESPNowClient
from ledgrab.core.devices.led_client import (
    DeviceHealth,
    DiscoveredDevice,
    LEDClient,
    LEDDeviceProvider,
    ProviderDeps,
)
from ledgrab.core.devices.serial_transport import (
    list_serial_ports,
    parse_serial_url,
    port_exists,
)
from ledgrab.utils import get_logger

if TYPE_CHECKING:
    from ledgrab.core.devices.device_config import ESPNowConfig

logger = get_logger(__name__)

# Description fragments that commonly indicate an ESP32 gateway's USB bridge.
_ESP_DESC_FRAGMENTS = ("cp210", "ch340", "esp", "ftdi", "usb serial")


class ESPNowDeviceProvider(LEDDeviceProvider):
    """Provider for ESP-NOW LED devices via serial ESP32 gateway.

    URL = serial port of the gateway ESP32 (COM3, /dev/ttyUSB0, or
    usb:VID:PID on Android). Each device represents one remote ESP32
    peer identified by MAC address. Multiple devices can share the same
    gateway (serial port).
    """

    @property
    def device_type(self) -> str:
        return "espnow"

    @property
    def capabilities(self) -> set:
        return {"manual_led_count", "health_check"}

    def create_client(self, config: "ESPNowConfig", *, deps: ProviderDeps) -> LEDClient:
        return ESPNowClient(
            config.device_url,
            led_count=config.led_count,
            baud_rate=config.baud_rate,
            espnow_peer_mac=config.espnow_peer_mac or "FF:FF:FF:FF:FF:FF",
            espnow_channel=config.espnow_channel,
        )

    async def check_health(self, url: str, http_client, prev_health=None) -> DeviceHealth:
        return await ESPNowClient.check_health(url, http_client, prev_health)

    async def validate_device(self, url: str) -> dict:
        """Validate serial port is accessible. LED count is manual."""
        port, _baud = parse_serial_url(url)
        try:
            if not port_exists(port):
                available = ", ".join(p.device for p in list_serial_ports()) or "none"
                raise ValueError(f"Serial port {port} not found. Available ports: {available}")
        except ValueError:
            raise
        except Exception as e:
            raise ValueError(f"Failed to enumerate serial ports: {e}")
        return {}

    async def discover(self, timeout: float = 3.0) -> List[DiscoveredDevice]:
        """Discover serial ports that could be ESP32 gateways.

        Filters the host's serial-port list by description fragments
        typical of ESP32 USB bridges (CP210x, CH340, FTDI, generic
        "USB Serial"). On Android, the USB bridge already reports
        descriptive names, so the same filter applies.
        """
        try:
            ports = list_serial_ports()
            results: List[DiscoveredDevice] = []
            for port in ports:
                desc = (port.description or "").lower()
                if not any(frag in desc for frag in _ESP_DESC_FRAGMENTS):
                    continue
                results.append(
                    DiscoveredDevice(
                        name=f"ESP-NOW Gateway ({port.description})",
                        url=port.device,
                        device_type="espnow",
                        ip="",
                        mac="",
                        led_count=None,
                        version=None,
                    )
                )
            logger.info("ESP-NOW gateway scan found %d candidate port(s)", len(results))
            return results
        except Exception as e:
            logger.error("ESP-NOW gateway discovery failed: %s", e)
            return []
