"""Home Assistant light target processor — casts LED colors to HA lights.

Reads from a ColorStripStream, averages LED segments to single RGB values,
and calls light.turn_on / light.turn_off via the HA WebSocket connection.
Rate-limited to update_rate Hz (typically 1-5 Hz).
"""

import asyncio
import time
from typing import Dict, List, Optional, Tuple

import numpy as np

from wled_controller.core.processing.target_processor import TargetContext, TargetProcessor
from wled_controller.storage.ha_light_output_target import HALightMapping
from wled_controller.utils import get_logger

logger = get_logger(__name__)


class HALightTargetProcessor(TargetProcessor):
    """Streams averaged LED colors to Home Assistant light entities."""

    def __init__(
        self,
        target_id: str,
        ha_source_id: str,
        color_strip_source_id: str = "",
        light_mappings: Optional[List[HALightMapping]] = None,
        update_rate: float = 2.0,
        transition: float = 0.5,
        min_brightness_threshold: int = 0,
        color_tolerance: int = 5,
        ctx: Optional[TargetContext] = None,
    ):
        super().__init__(target_id, ctx)
        self._ha_source_id = ha_source_id
        self._css_id = color_strip_source_id
        self._light_mappings = light_mappings or []
        self._update_rate = max(0.5, min(5.0, update_rate))
        self._transition = transition
        self._min_brightness_threshold = min_brightness_threshold
        self._color_tolerance = color_tolerance

        # Runtime state
        self._css_stream = None
        self._ha_runtime = None
        self._previous_colors: Dict[str, Tuple[int, int, int]] = {}
        self._previous_on: Dict[str, bool] = {}  # track on/off state per entity
        self._start_time: Optional[float] = None

    @property
    def device_id(self) -> Optional[str]:
        return None  # HA light targets don't use device providers

    async def start(self) -> None:
        if self._is_running:
            return

        # Acquire CSS stream
        if self._css_id and self._ctx.color_strip_stream_manager:
            try:
                self._css_stream = self._ctx.color_strip_stream_manager.acquire(
                    self._css_id, self._target_id
                )
            except Exception as e:
                logger.warning(f"HA light {self._target_id}: failed to acquire CSS stream: {e}")

        # Acquire HA runtime
        try:
            from wled_controller.core.home_assistant.ha_manager import HomeAssistantManager

            ha_manager: Optional[HomeAssistantManager] = getattr(self._ctx, "ha_manager", None)
            if ha_manager:
                self._ha_runtime = await ha_manager.acquire(self._ha_source_id)
        except Exception as e:
            logger.warning(f"HA light {self._target_id}: failed to acquire HA runtime: {e}")

        self._is_running = True
        self._start_time = time.monotonic()
        self._task = asyncio.create_task(self._processing_loop())
        logger.info(f"HA light target started: {self._target_id}")

    async def stop(self) -> None:
        self._is_running = False
        if self._task:
            self._task.cancel()
            try:
                await self._task
            except asyncio.CancelledError:
                pass
            self._task = None

        # Release CSS stream
        if self._css_stream and self._ctx.color_strip_stream_manager:
            try:
                self._ctx.color_strip_stream_manager.release(self._css_id, self._target_id)
            except Exception:
                pass
            self._css_stream = None

        # Release HA runtime
        if self._ha_runtime:
            try:
                ha_manager = getattr(self._ctx, "ha_manager", None)
                if ha_manager:
                    await ha_manager.release(self._ha_source_id)
            except Exception:
                pass
            self._ha_runtime = None

        self._previous_colors.clear()
        self._previous_on.clear()
        logger.info(f"HA light target stopped: {self._target_id}")

    def update_settings(self, settings) -> None:
        if isinstance(settings, dict):
            if "update_rate" in settings:
                self._update_rate = max(0.5, min(5.0, float(settings["update_rate"])))
            if "transition" in settings:
                self._transition = float(settings["transition"])
            if "min_brightness_threshold" in settings:
                self._min_brightness_threshold = int(settings["min_brightness_threshold"])
            if "color_tolerance" in settings:
                self._color_tolerance = int(settings["color_tolerance"])
            if "light_mappings" in settings:
                self._light_mappings = settings["light_mappings"]

    def update_css_source(self, color_strip_source_id: str) -> None:
        """Hot-swap the CSS stream."""
        old_id = self._css_id
        self._css_id = color_strip_source_id

        if self._is_running and self._ctx.color_strip_stream_manager:
            try:
                new_stream = self._ctx.color_strip_stream_manager.acquire(
                    color_strip_source_id, self._target_id
                )
                old_stream = self._css_stream
                self._css_stream = new_stream
                if old_stream:
                    self._ctx.color_strip_stream_manager.release(old_id, self._target_id)
            except Exception as e:
                logger.warning(f"HA light {self._target_id}: CSS swap failed: {e}")

    def get_state(self) -> dict:
        return {
            "target_id": self._target_id,
            "ha_source_id": self._ha_source_id,
            "css_id": self._css_id,
            "is_running": self._is_running,
            "ha_connected": self._ha_runtime.is_connected if self._ha_runtime else False,
            "light_count": len(self._light_mappings),
            "update_rate": self._update_rate,
        }

    def get_metrics(self) -> dict:
        return {
            "target_id": self._target_id,
            "uptime": time.monotonic() - self._start_time if self._start_time else 0,
            "update_rate": self._update_rate,
        }

    async def _processing_loop(self) -> None:
        """Main loop: read CSS colors, average per mapping, send to HA lights."""
        interval = 1.0 / self._update_rate

        while self._is_running:
            try:
                loop_start = time.monotonic()

                if self._css_stream and self._ha_runtime and self._ha_runtime.is_connected:
                    colors = self._css_stream.get_latest_colors()
                    if colors is not None and len(colors) > 0:
                        await self._update_lights(colors)

                # Sleep for remaining frame time
                elapsed = time.monotonic() - loop_start
                sleep_time = max(0.05, interval - elapsed)
                await asyncio.sleep(sleep_time)

            except asyncio.CancelledError:
                break
            except Exception as e:
                logger.error(f"HA light {self._target_id} loop error: {e}")
                await asyncio.sleep(1.0)

    async def _update_lights(self, colors: np.ndarray) -> None:
        """Average LED segments and call HA services for changed lights."""
        led_count = len(colors)

        for mapping in self._light_mappings:
            if not mapping.entity_id:
                continue

            # Resolve LED range
            start = max(0, mapping.led_start)
            end = mapping.led_end if mapping.led_end >= 0 else led_count
            end = min(end, led_count)
            if start >= end:
                continue

            # Average the LED segment
            segment = colors[start:end]
            avg = segment.mean(axis=0).astype(int)
            r, g, b = int(avg[0]), int(avg[1]), int(avg[2])

            # Calculate brightness (0-255) from max channel
            brightness = max(r, g, b)

            # Apply brightness scale
            if mapping.brightness_scale < 1.0:
                brightness = int(brightness * mapping.brightness_scale)

            # Check brightness threshold
            should_be_on = (
                brightness >= self._min_brightness_threshold or self._min_brightness_threshold == 0
            )

            entity_id = mapping.entity_id
            prev_color = self._previous_colors.get(entity_id)
            was_on = self._previous_on.get(entity_id, True)

            if should_be_on:
                # Check if color changed beyond tolerance
                new_color = (r, g, b)
                if prev_color is not None and was_on:
                    dr = abs(r - prev_color[0])
                    dg = abs(g - prev_color[1])
                    db = abs(b - prev_color[2])
                    if max(dr, dg, db) < self._color_tolerance:
                        continue  # skip — color hasn't changed enough

                # Call light.turn_on
                service_data = {
                    "rgb_color": [r, g, b],
                    "brightness": min(255, int(brightness * mapping.brightness_scale)),
                }
                if self._transition > 0:
                    service_data["transition"] = self._transition

                await self._ha_runtime.call_service(
                    domain="light",
                    service="turn_on",
                    service_data=service_data,
                    target={"entity_id": entity_id},
                )
                self._previous_colors[entity_id] = new_color
                self._previous_on[entity_id] = True

            elif was_on:
                # Brightness dropped below threshold — turn off
                await self._ha_runtime.call_service(
                    domain="light",
                    service="turn_off",
                    service_data={},
                    target={"entity_id": entity_id},
                )
                self._previous_on[entity_id] = False
                self._previous_colors.pop(entity_id, None)
