"""Philips Hue LED client — streams color data via the Hue Entertainment API."""

import asyncio
import socket
import struct
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__)

# Hue Entertainment API constants
HUE_ENT_PORT = 2100
PROTOCOL_NAME = b"HueStream"
VERSION_MAJOR = 2
VERSION_MINOR = 0
COLOR_SPACE_RGB = 0x00
# API version 2 header: "HueStream" + version(2B) + sequence(1B) + reserved(2B)
#                       + color_space(1B) + reserved(1B) = 16 bytes
HEADER_SIZE = 16


def _build_entertainment_frame(
    lights: List[Tuple[int, int, int]],
    brightness: int = 255,
    sequence: int = 0,
) -> bytes:
    """Build a Hue Entertainment API v2 UDP frame.

    Each light gets 7 bytes: [light_id(2B)][R(2B)][G(2B)][B(2B)]
    Colors are 16-bit (0-65535). We scale 8-bit RGB + brightness.
    """
    # Header
    header = bytearray(HEADER_SIZE)
    header[0:9] = PROTOCOL_NAME
    header[9] = VERSION_MAJOR
    header[10] = VERSION_MINOR
    header[11] = sequence & 0xFF
    header[12] = 0x00  # reserved
    header[13] = 0x00  # reserved
    header[14] = COLOR_SPACE_RGB
    header[15] = 0x00  # reserved

    # Light data
    # Note: brightness already applied by processor loop (_cached_brightness)
    data = bytearray()
    for idx, (r, g, b) in enumerate(lights):
        light_id = idx  # 0-based light index in entertainment group
        r16 = int(r * 257)  # scale 0-255 to 0-65535
        g16 = int(g * 257)
        b16 = int(b * 257)
        data += struct.pack(">BHHH", light_id, r16, g16, b16)

    return bytes(header) + bytes(data)


class HueClient(LEDClient):
    """LED client for Philips Hue Entertainment API streaming.

    Uses UDP (optionally DTLS) to stream color data at ~25 fps to a Hue
    entertainment group. Each light in the group is treated as one "LED".
    """

    def __init__(
        self,
        url: str = "",
        led_count: int = 0,
        hue_username: str = "",
        hue_client_key: str = "",
        hue_entertainment_group_id: str = "",
        **kwargs,
    ):
        self._bridge_ip = url.replace("hue://", "").rstrip("/")
        self._led_count = led_count
        self._username = hue_username
        self._client_key = hue_client_key
        self._group_id = hue_entertainment_group_id
        self._sock: Optional[socket.socket] = None
        self._connected = False
        self._sequence = 0
        self._dtls_sock = None

    async def connect(self) -> bool:
        loop = asyncio.get_event_loop()

        # Activate entertainment streaming via REST API
        await self._activate_streaming(True)

        # Open UDP socket for entertainment streaming
        self._sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
        self._sock.setblocking(False)

        # Try DTLS if dtls library is available
        try:
            from dtls import do_patch
            do_patch()
            import ssl

            ctx = ssl.SSLContext(ssl.PROTOCOL_TLS_CLIENT)
            ctx.check_hostname = False
            ctx.verify_mode = ssl.CERT_NONE
            ctx.set_ciphers("PSK-AES128-GCM-SHA256")
            # PSK identity = username, key = client_key (hex decoded)
            psk = bytes.fromhex(self._client_key)
            ctx.set_psk_client_callback(lambda hint: (self._username.encode(), psk))
            self._dtls_sock = ctx.wrap_socket(self._sock, server_hostname=self._bridge_ip)
            await loop.run_in_executor(
                None,
                lambda: self._dtls_sock.connect((self._bridge_ip, HUE_ENT_PORT)),
            )
            logger.info("Hue DTLS connection established to %s", self._bridge_ip)
        except (ImportError, Exception) as e:
            # Fall back to plain UDP (works for local testing / older bridges)
            logger.warning(
                "Hue DTLS not available (%s), falling back to plain UDP. "
                "Install 'dtls' package for encrypted streaming.",
                e,
            )
            self._dtls_sock = None

        self._connected = True
        logger.info(
            "Hue client connected: bridge=%s group=%s lights=%d",
            self._bridge_ip, self._group_id, self._led_count,
        )
        return True

    async def _activate_streaming(self, active: bool) -> None:
        """Activate/deactivate entertainment streaming via Hue REST API."""
        import httpx

        url = f"https://{self._bridge_ip}/clip/v2/resource/entertainment_configuration/{self._group_id}"
        payload = {"action": "start" if active else "stop"}
        headers = {"hue-application-key": self._username}

        async with httpx.AsyncClient(verify=False, timeout=5.0) as client:
            resp = await client.put(url, json=payload, headers=headers)
            if resp.status_code not in (200, 207):
                logger.warning("Hue streaming %s failed: %s", "start" if active else "stop", resp.text)

    async def close(self) -> None:
        if self._connected:
            try:
                await self._activate_streaming(False)
            except Exception:
                pass
        if self._dtls_sock:
            try:
                self._dtls_sock.close()
            except Exception:
                pass
        if self._sock:
            try:
                self._sock.close()
            except Exception:
                pass
        self._sock = None
        self._dtls_sock = None
        self._connected = False
        logger.info("Hue client closed: bridge=%s", self._bridge_ip)

    @property
    def is_connected(self) -> bool:
        return self._connected

    @property
    def supports_fast_send(self) -> bool:
        return True

    def send_pixels_fast(
        self,
        pixels: Union[List[Tuple[int, int, int]], np.ndarray],
        brightness: int = 255,
    ) -> None:
        if not self._connected:
            return

        if isinstance(pixels, np.ndarray):
            light_colors = [tuple(pixels[i]) for i in range(min(len(pixels), self._led_count))]
        else:
            light_colors = pixels[: self._led_count]

        frame = _build_entertainment_frame(light_colors, brightness, self._sequence)
        self._sequence = (self._sequence + 1) & 0xFF

        try:
            if self._dtls_sock:
                self._dtls_sock.send(frame)
            elif self._sock:
                self._sock.sendto(frame, (self._bridge_ip, HUE_ENT_PORT))
        except Exception as e:
            logger.warning("Hue send failed: %s", e)

    async def send_pixels(
        self,
        pixels: Union[List[Tuple[int, int, int]], np.ndarray],
        brightness: int = 255,
    ) -> bool:
        if not self._connected:
            return False
        loop = asyncio.get_event_loop()
        await loop.run_in_executor(
            None,
            lambda: self.send_pixels_fast(pixels, brightness),
        )
        return True

    @classmethod
    async def check_health(
        cls,
        url: str,
        http_client,
        prev_health: Optional[DeviceHealth] = None,
    ) -> DeviceHealth:
        """Check if the Hue bridge is reachable."""
        bridge_ip = url.replace("hue://", "").rstrip("/")
        try:
            import httpx
            import time

            start = time.time()
            async with httpx.AsyncClient(verify=False, timeout=3.0) as client:
                resp = await client.get(f"https://{bridge_ip}/api/0/config")
                latency = (time.time() - start) * 1000
                if resp.status_code == 200:
                    data = resp.json()
                    return DeviceHealth(
                        online=True,
                        latency_ms=round(latency, 1),
                        device_name=data.get("name", "Hue Bridge"),
                        device_version=data.get("swversion"),
                        last_checked=datetime.now(timezone.utc),
                    )
            return DeviceHealth(online=False, error="Unexpected response", last_checked=datetime.now(timezone.utc))
        except Exception as e:
            return DeviceHealth(online=False, error=str(e), last_checked=datetime.now(timezone.utc))
