"""MQTT LED client — publishes pixel data to an MQTT topic on a configured broker.

The client acquires a per-source runtime from :class:`MQTTManager` at
``connect()`` time and releases it at ``close()``. Every device references
an ``mqtt_source_id`` so different devices can talk to different brokers.
"""

import json
from typing import List, Tuple

import numpy as np

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

logger = get_logger(__name__)


def parse_mqtt_url(url: str) -> str:
    """Extract topic from an mqtt:// URL.

    Format: mqtt://topic/path  (broker connection is per-source, not in URL)
    """
    if url.startswith("mqtt://"):
        return url[7:]
    return url


class MQTTLEDClient(LEDClient):
    """Publishes JSON pixel data to an MQTT topic via an MQTTManager runtime."""

    def __init__(
        self,
        url: str,
        led_count: int = 0,
        *,
        mqtt_manager=None,
        mqtt_source_id: str = "",
        **kwargs,
    ):
        self._topic = parse_mqtt_url(url)
        self._led_count = led_count
        self._mqtt_manager = mqtt_manager
        self._mqtt_source_id = mqtt_source_id
        self._runtime = None
        self._connected = False

    async def connect(self) -> bool:
        if self._mqtt_manager is None:
            raise ConnectionError("MQTT manager not available")
        if not self._mqtt_source_id:
            raise ConnectionError("Device has no mqtt_source_id configured")
        try:
            self._runtime = await self._mqtt_manager.acquire(self._mqtt_source_id)
        except Exception as e:
            raise ConnectionError(f"Failed to acquire MQTT runtime: {e}") from e
        if not self._runtime.is_connected:
            # Runtime exists but the broker hasn't established the TCP
            # connection yet — leave the LED client in a "queued" state so
            # publishes get buffered (MQTTRuntime.publish queues on
            # disconnect). The runtime will drain when connection is made.
            logger.info(
                "MQTT LED client %s: runtime acquired but broker not yet connected",
                self._mqtt_source_id,
            )
        self._connected = True
        return True

    async def close(self) -> None:
        if self._runtime is not None and self._mqtt_manager is not None:
            try:
                await self._mqtt_manager.release(self._mqtt_source_id)
            except Exception as e:
                logger.warning("Failed to release MQTT runtime %s: %s", self._mqtt_source_id, e)
        self._runtime = None
        self._connected = False

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

    async def send_pixels(
        self,
        pixels: List[Tuple[int, int, int]] | np.ndarray,
        brightness: int = 255,
    ) -> bool:
        if self._runtime is None:
            return False

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

        payload = json.dumps(
            {
                "pixels": pixel_list,
                "brightness": brightness,
                "led_count": len(pixel_list),
            }
        )

        await self._runtime.publish(self._topic, payload, retain=False, qos=0)
        return True

    @classmethod
    async def check_health(
        cls,
        url: str,
        http_client,
        prev_health=None,
        *,
        mqtt_manager=None,
        mqtt_source_id: str | None = None,
    ) -> DeviceHealth:
        from datetime import datetime, timezone

        if mqtt_manager is None or not mqtt_source_id:
            return DeviceHealth(
                online=False,
                error="MQTT source not configured",
                last_checked=datetime.now(timezone.utc),
            )
        runtime = mqtt_manager.get_runtime(mqtt_source_id)
        connected = bool(runtime and runtime.is_connected)
        return DeviceHealth(
            online=connected,
            last_checked=datetime.now(timezone.utc),
            error=None if connected else "MQTT broker disconnected",
        )
