"""Base provider for serial LED controllers (Adalight, AmbiLED, etc.).

Subclasses only need to override ``device_type`` and ``create_client()``.
All common serial-device logic (COM port validation, discovery, health
checks, power control via black frames) lives here.
"""

from typing import List

import numpy as np

from wled_controller.core.devices.led_client import (
    DeviceHealth,
    DiscoveredDevice,
    LEDDeviceProvider,
)
from wled_controller.utils import get_logger

logger = get_logger(__name__)


class SerialDeviceProvider(LEDDeviceProvider):
    """Base provider for serial LED controllers."""

    @property
    def capabilities(self) -> set:
        # manual_led_count: user must specify LED count (can't auto-detect)
        # power_control: can blank LEDs by sending all-black pixels
        # brightness_control: software brightness (multiplies pixel values before sending)
        # health_check: serial port availability probe
        return {"manual_led_count", "power_control", "brightness_control", "health_check"}

    async def check_health(self, url: str, http_client, prev_health=None) -> DeviceHealth:
        # Generic serial port health check — enumerate COM ports
        from wled_controller.core.devices.adalight_client import AdalightClient

        return await AdalightClient.check_health(url, http_client, prev_health)

    async def validate_device(self, url: str) -> dict:
        """Validate that the serial port exists.

        Returns empty dict — serial devices don't report LED count,
        so it must be provided by the user.
        """
        from wled_controller.core.devices.adalight_client import parse_adalight_url

        port, _baud = parse_adalight_url(url)

        try:
            import serial.tools.list_ports

            available_ports = [p.device for p in serial.tools.list_ports.comports()]
            port_upper = port.upper()
            if not any(p.upper() == port_upper for p in available_ports):
                raise ValueError(
                    f"Serial port {port} not found. "
                    f"Available ports: {', '.join(available_ports) or 'none'}"
                )
        except ValueError:
            raise
        except Exception as e:
            raise ValueError(f"Failed to enumerate serial ports: {e}")

        logger.info(f"{self.device_type} device validated: port {port}")
        return {}

    async def discover(self, timeout: float = 3.0) -> List[DiscoveredDevice]:
        """Discover serial ports that could be LED devices."""
        try:
            import serial.tools.list_ports

            ports = serial.tools.list_ports.comports()
            results = []
            for port_info in ports:
                results.append(
                    DiscoveredDevice(
                        name=port_info.description or port_info.device,
                        url=port_info.device,
                        device_type=self.device_type,
                        ip=port_info.device,
                        mac="",
                        led_count=None,
                        version=None,
                    )
                )
            logger.info(f"{self.device_type} serial port scan found {len(results)} port(s)")
            return results
        except Exception as e:
            logger.error(f"{self.device_type} serial port discovery failed: {e}")
            return []

    async def get_power(self, url: str, **kwargs) -> bool:
        # Serial devices have no hardware power query; assume on
        return True

    async def set_power(self, url: str, on: bool, **kwargs) -> None:
        """Turn device on/off by sending an all-black frame (off) or no-op (on).

        Requires kwargs: led_count (int), baud_rate (int | None).
        """
        if on:
            return  # "on" is a no-op — next processing frame lights LEDs up

        led_count = kwargs.get("led_count", 0)
        baud_rate = kwargs.get("baud_rate")
        if led_count <= 0:
            raise ValueError(f"led_count is required to send black frame to {self.device_type} device")

        client = self.create_client(url, led_count=led_count, baud_rate=baud_rate)
        try:
            await client.connect()
            black = np.zeros((led_count, 3), dtype=np.uint8)
            await client.send_pixels(black, brightness=255)
            logger.info(f"{self.device_type} power off: sent black frame to {url}")
        finally:
            await client.close()

