"""Razer Chroma SDK LED client — controls Razer RGB peripherals via REST API."""

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

# Chroma SDK REST API base
CHROMA_SDK_URL = "http://localhost:54235/razer/rest"

# Device type → (endpoint suffix, max LEDs)
CHROMA_DEVICES = {
    "keyboard": ("keyboard", 132),      # 22 columns × 6 rows
    "mouse": ("mouse", 30),
    "mousepad": ("mousepad", 15),
    "headset": ("headset", 5),           # left + right + 3 zones
    "chromalink": ("chromalink", 5),
    "keypad": ("keypad", 20),            # 5×4 grid
}


class ChromaClient(LEDClient):
    """LED client that controls Razer peripherals via the Chroma SDK REST API.

    The Chroma SDK exposes a local REST API. Workflow:
      1. POST /razer/rest to init a session → get session URL
      2. PUT effects to {session_url}/{device_type}
      3. DELETE session on close

    Uses CUSTOM effect type for per-LED RGB control.
    """

    HEARTBEAT_INTERVAL = 10  # seconds — SDK kills session after 15s idle

    def __init__(
        self,
        url: str = "",
        led_count: int = 0,
        chroma_device_type: str = "chromalink",
        **kwargs,
    ):
        self._base_url = url or CHROMA_SDK_URL
        self._led_count = led_count
        self._chroma_device_type = chroma_device_type
        self._session_url: Optional[str] = None
        self._connected = False
        self._heartbeat_task: Optional[asyncio.Task] = None
        self._http_client = None

    async def connect(self) -> bool:
        import httpx

        self._http_client = httpx.AsyncClient(timeout=5.0)

        # Initialize Chroma SDK session
        init_payload = {
            "title": "WLED Screen Controller",
            "description": "LED pixel streaming from WLED Screen Controller",
            "author": {"name": "WLED-SC", "contact": "https://github.com"},
            "device_supported": [self._chroma_device_type],
            "category": "application",
        }

        try:
            resp = await self._http_client.post(self._base_url, json=init_payload)
            resp.raise_for_status()
            data = resp.json()
            self._session_url = data.get("uri") or data.get("sessionid")
            if not self._session_url:
                raise RuntimeError(f"Chroma SDK init returned no session URL: {data}")
        except Exception as e:
            logger.error("Chroma SDK init failed: %s", e)
            await self._http_client.aclose()
            self._http_client = None
            raise

        self._connected = True
        self._heartbeat_task = asyncio.create_task(self._heartbeat_loop())

        logger.info(
            "Chroma client connected: device=%s session=%s leds=%d",
            self._chroma_device_type, self._session_url, self._led_count,
        )
        return True

    async def _heartbeat_loop(self):
        """Keep the Chroma SDK session alive."""
        while self._connected and self._session_url:
            try:
                await asyncio.sleep(self.HEARTBEAT_INTERVAL)
                if self._http_client and self._session_url:
                    await self._http_client.put(
                        f"{self._session_url}/heartbeat",
                        json={},
                    )
            except asyncio.CancelledError:
                break
            except Exception as e:
                logger.debug("Chroma heartbeat error: %s", e)

    async def close(self) -> None:
        self._connected = False
        if self._heartbeat_task:
            self._heartbeat_task.cancel()
            try:
                await self._heartbeat_task
            except asyncio.CancelledError:
                pass
            self._heartbeat_task = None

        if self._http_client and self._session_url:
            try:
                await self._http_client.delete(self._session_url)
            except Exception:
                pass

        if self._http_client:
            await self._http_client.aclose()
            self._http_client = None

        self._session_url = None
        logger.info("Chroma client closed")

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

    async def send_pixels(
        self,
        pixels: Union[List[Tuple[int, int, int]], np.ndarray],
        brightness: int = 255,
    ) -> bool:
        if not self.is_connected or not self._http_client:
            return False

        if isinstance(pixels, np.ndarray):
            pixel_arr = pixels
        else:
            pixel_arr = np.array(pixels, dtype=np.uint8)

        # Note: brightness already applied by processor loop (_cached_brightness)
        device_info = CHROMA_DEVICES.get(self._chroma_device_type)
        if not device_info:
            return False

        endpoint, max_leds = device_info
        n = min(len(pixel_arr), max_leds, self._led_count or max_leds)

        # Chroma uses BGR packed as 0x00BBGGRR integers
        colors = []
        for i in range(n):
            r, g, b = int(pixel_arr[i][0]), int(pixel_arr[i][1]), int(pixel_arr[i][2])
            colors.append(r | (g << 8) | (b << 16))

        # Pad to max_leds if needed
        while len(colors) < max_leds:
            colors.append(0)

        # Build effect payload based on device type
        if self._chroma_device_type == "keyboard":
            # Keyboard uses 2D array: 6 rows × 22 columns
            grid = []
            idx = 0
            for row in range(6):
                row_colors = []
                for col in range(22):
                    row_colors.append(colors[idx] if idx < len(colors) else 0)
                    idx += 1
                grid.append(row_colors)
            effect = {"effect": "CHROMA_CUSTOM", "param": grid}
        elif self._chroma_device_type == "keypad":
            # Keypad uses 2D array: 4 rows × 5 columns
            grid = []
            idx = 0
            for row in range(4):
                row_colors = []
                for col in range(5):
                    row_colors.append(colors[idx] if idx < len(colors) else 0)
                    idx += 1
                grid.append(row_colors)
            effect = {"effect": "CHROMA_CUSTOM", "param": grid}
        else:
            # 1D devices: mouse, mousepad, headset, chromalink
            effect = {"effect": "CHROMA_CUSTOM", "param": colors[:max_leds]}

        try:
            url = f"{self._session_url}/{endpoint}"
            resp = await self._http_client.put(url, json=effect)
            return resp.status_code == 200
        except Exception as e:
            logger.error("Chroma send failed: %s", e)
            return False

    @classmethod
    async def check_health(
        cls,
        url: str,
        http_client,
        prev_health: Optional[DeviceHealth] = None,
    ) -> DeviceHealth:
        """Check if Chroma SDK is running."""
        base = url or CHROMA_SDK_URL
        try:
            resp = await http_client.get(base, timeout=3.0)
            if resp.status_code < 500:
                return DeviceHealth(
                    online=True,
                    latency_ms=0.0,
                    device_name="Razer Chroma SDK",
                    last_checked=datetime.now(timezone.utc),
                )
        except Exception:
            pass

        return DeviceHealth(
            online=False,
            error="Chroma SDK not responding (is Razer Synapse running?)",
            last_checked=datetime.now(timezone.utc),
        )
