"""Nanoleaf device provider — Light Panels / Canvas / Shapes / Lines / Elements."""

from __future__ import annotations

import asyncio
from typing import TYPE_CHECKING, List

from ledgrab.core.devices.led_client import (
    DeviceHealth,
    DiscoveredDevice,
    LEDClient,
    LEDDeviceProvider,
    ProviderDeps,
)
from ledgrab.core.devices.nanoleaf_client import (
    NanoleafClient,
    pair_nanoleaf,
    parse_nanoleaf_url,
)
from ledgrab.utils import get_logger

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

logger = get_logger(__name__)


class NanoleafDeviceProvider(LEDDeviceProvider):
    """Provider for Nanoleaf controllers.

    Treats the controller as a single-pixel target (averaged strip → one
    HSB color via ``PUT /state``). Per-panel addressing via the
    ``extControl`` UDP streaming mode is a follow-up — the MVP keeps it
    simple and matches the shape of every other consumer-bulb driver.

    Requires pairing: the user holds the controller's power button for
    five seconds to open a 30-second window, then the frontend pair
    modal POSTs to ``/api/v1/new`` via ``pair_device``.
    """

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

    @property
    def capabilities(self) -> set:
        return {
            "manual_led_count",
            "requires_pairing",
            "power_control",
            "brightness_control",
            "static_color",
            "health_check",
            "single_pixel",
        }

    def create_client(self, config: "NanoleafConfig", *, deps: ProviderDeps) -> LEDClient:
        return NanoleafClient(
            config.device_url,
            led_count=config.led_count,
            auth_token=config.nanoleaf_token,
            min_interval_s=max(0.0, config.nanoleaf_min_interval_ms / 1000.0),
        )

    async def pair_device(self, url: str) -> dict:
        """Claim an auth token after the user has held the power button."""
        try:
            host = parse_nanoleaf_url(url)
        except ValueError as exc:
            raise ValueError(f"Invalid Nanoleaf URL: {exc}") from exc
        token = await pair_nanoleaf(host)
        return {"nanoleaf_token": token}

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

    async def validate_device(self, url: str) -> dict:
        """Resolve the host. LED-count is user-supplied (matches the
        existing single-pixel pattern) — Nanoleaf reports panel count
        through ``panelLayout``, but for the single-color streaming
        shape it's not needed."""
        try:
            host = parse_nanoleaf_url(url)
        except ValueError as exc:
            raise ValueError(f"Invalid Nanoleaf URL: {exc}") from exc
        logger.info("Nanoleaf URL validated: host=%s", host)
        return {}

    async def discover(self, timeout: float = 3.0) -> List[DiscoveredDevice]:
        """Scan via mDNS ``_nanoleafapi._tcp``.

        Both the newer ``_nanoleafapi._tcp`` and older ``_nanoleaf._tcp``
        service types appear in the field. We query both in parallel and
        deduplicate by IP.
        """
        try:
            from zeroconf import ServiceStateChange
            from zeroconf.asyncio import AsyncServiceBrowser, AsyncServiceInfo, AsyncZeroconf
        except ImportError:
            logger.warning("zeroconf unavailable; skipping Nanoleaf mDNS discovery")
            return []

        service_types = ("_nanoleafapi._tcp.local.", "_nanoleafapi_v1._tcp.local.")
        discovered: dict[str, AsyncServiceInfo] = {}

        def _on_state_change(**kwargs):
            service_type = kwargs.get("service_type", "")
            name = kwargs.get("name", "")
            state_change = kwargs.get("state_change")
            if state_change in (ServiceStateChange.Added, ServiceStateChange.Updated):
                discovered[name] = AsyncServiceInfo(service_type, name)

        try:
            aiozc = AsyncZeroconf()
        except OSError as exc:
            logger.warning("Nanoleaf discovery: zeroconf init failed (%s)", exc)
            return []

        try:
            browsers = [
                AsyncServiceBrowser(aiozc.zeroconf, st, handlers=[_on_state_change])
                for st in service_types
            ]
            await asyncio.sleep(timeout)
            for info in discovered.values():
                await info.async_request(aiozc.zeroconf, timeout=2000)
            for browser in browsers:
                await browser.async_cancel()
        finally:
            try:
                await aiozc.async_close()
            except (OSError, RuntimeError):
                pass

        results: List[DiscoveredDevice] = []
        seen_ips: set[str] = set()
        for name, info in discovered.items():
            addrs = info.parsed_addresses() if info else []
            if not addrs:
                continue
            ip = addrs[0]
            if ip in seen_ips:
                continue
            seen_ips.add(ip)
            service_name = name.rsplit(".", 4)[0] if "." in name else name
            results.append(
                DiscoveredDevice(
                    name=service_name or "Nanoleaf",
                    url=f"nanoleaf://{ip}",
                    device_type="nanoleaf",
                    ip=ip,
                    mac="",
                    led_count=None,
                    version=None,
                )
            )
        logger.info("Nanoleaf mDNS scan found %d controller(s)", len(results))
        return results
