"""Daylight cycle LED stream — simulates natural daylight color temperature.

Implements DaylightColorStripStream which produces a uniform LED color array
that transitions through dawn, daylight, sunset, and night over a continuous
24-hour cycle.  Can use real wall-clock time or a configurable simulation speed.
"""

import threading
import time
from typing import Optional

import numpy as np

from wled_controller.core.processing.color_strip_stream import ColorStripStream
from wled_controller.utils import get_logger
from wled_controller.utils.timer import high_resolution_timer

logger = get_logger(__name__)

# ── Daylight color table ────────────────────────────────────────────────
#
# Maps hour-of-day (0–24) to RGB color.  Interpolated linearly between
# control points.  Colors approximate natural daylight color temperature
# from warm sunrise tones through cool midday to warm sunset and dim night.
#
# Format: (hour, R, G, B)
_DAYLIGHT_CURVE = [
    (0.0,   10,  10,  30),     # midnight — deep blue
    (4.0,   10,  10,  40),     # pre-dawn — dark blue
    (5.5,   40,  20,  60),     # first light — purple hint
    (6.0,  255, 100,  30),     # sunrise — warm orange
    (7.0,  255, 170,  80),     # early morning — golden
    (8.0,  255, 220, 160),     # morning — warm white
    (10.0, 255, 245, 230),     # mid-morning — neutral warm
    (12.0, 240, 248, 255),     # noon — cool white / slight blue
    (14.0, 255, 250, 240),     # afternoon — neutral
    (16.0, 255, 230, 180),     # late afternoon — warm
    (17.5, 255, 180, 100),     # pre-sunset — golden
    (18.5, 255, 100,  40),     # sunset — deep orange
    (19.0, 200,  60,  40),     # late sunset — red
    (19.5, 100,  30,  60),     # dusk — purple
    (20.0,  40,  20,  60),     # twilight — dark purple
    (21.0,  15,  15,  45),     # night — dark blue
    (24.0,  10,  10,  30),     # midnight (wrap)
]

# Pre-build a (1440, 3) uint8 LUT — one entry per minute of the day
_daylight_lut: Optional[np.ndarray] = None


def _get_daylight_lut() -> np.ndarray:
    global _daylight_lut
    if _daylight_lut is not None:
        return _daylight_lut

    lut = np.zeros((1440, 3), dtype=np.uint8)
    for minute in range(1440):
        hour = minute / 60.0
        # Find surrounding control points
        prev = _DAYLIGHT_CURVE[0]
        nxt = _DAYLIGHT_CURVE[-1]
        for i in range(len(_DAYLIGHT_CURVE) - 1):
            if _DAYLIGHT_CURVE[i][0] <= hour <= _DAYLIGHT_CURVE[i + 1][0]:
                prev = _DAYLIGHT_CURVE[i]
                nxt = _DAYLIGHT_CURVE[i + 1]
                break
        span = nxt[0] - prev[0]
        t = (hour - prev[0]) / span if span > 0 else 0.0
        # Smooth interpolation (smoothstep)
        t = t * t * (3 - 2 * t)
        for ch in range(3):
            lut[minute, ch] = int(prev[ch + 1] + (nxt[ch + 1] - prev[ch + 1]) * t + 0.5)

    _daylight_lut = lut
    return lut


class DaylightColorStripStream(ColorStripStream):
    """Color strip stream simulating a 24-hour daylight cycle.

    All LEDs display the same color at any moment.  The color smoothly
    transitions through a pre-defined daylight curve.
    """

    def __init__(self, source):
        self._colors_lock = threading.Lock()
        self._running = False
        self._thread: Optional[threading.Thread] = None
        self._fps = 10   # low FPS — transitions are slow
        self._frame_time = 1.0 / 10
        self._clock = None
        self._led_count = 1
        self._auto_size = True
        self._lut = _get_daylight_lut()
        self._update_from_source(source)

    def _update_from_source(self, source) -> None:
        self._speed = float(getattr(source, "speed", 1.0))
        self._use_real_time = bool(getattr(source, "use_real_time", False))
        self._latitude = float(getattr(source, "latitude", 50.0))
        _lc = getattr(source, "led_count", 0)
        self._auto_size = not _lc
        self._led_count = _lc if _lc and _lc > 0 else 1
        with self._colors_lock:
            self._colors: Optional[np.ndarray] = None

    def configure(self, device_led_count: int) -> None:
        if self._auto_size and device_led_count > 0:
            new_count = max(self._led_count, device_led_count)
            if new_count != self._led_count:
                self._led_count = new_count

    @property
    def target_fps(self) -> int:
        return self._fps

    @property
    def led_count(self) -> int:
        return self._led_count

    def set_capture_fps(self, fps: int) -> None:
        self._fps = max(1, min(30, fps))
        self._frame_time = 1.0 / self._fps

    def start(self) -> None:
        if self._running:
            return
        self._running = True
        self._thread = threading.Thread(
            target=self._animate_loop,
            name="css-daylight",
            daemon=True,
        )
        self._thread.start()
        logger.info(f"DaylightColorStripStream started (leds={self._led_count})")

    def stop(self) -> None:
        self._running = False
        if self._thread:
            self._thread.join(timeout=5.0)
            if self._thread.is_alive():
                logger.warning("DaylightColorStripStream thread did not terminate within 5s")
            self._thread = None
        logger.info("DaylightColorStripStream stopped")

    def get_latest_colors(self) -> Optional[np.ndarray]:
        with self._colors_lock:
            return self._colors

    def update_source(self, source) -> None:
        from wled_controller.storage.color_strip_source import DaylightColorStripSource
        if isinstance(source, DaylightColorStripSource):
            prev_led_count = self._led_count if self._auto_size else None
            self._update_from_source(source)
            if prev_led_count and self._auto_size:
                self._led_count = prev_led_count
            logger.info("DaylightColorStripStream params updated in-place")

    def set_clock(self, clock) -> None:
        self._clock = clock

    # ── Animation loop ──────────────────────────────────────────────

    def _animate_loop(self) -> None:
        _pool_n = 0
        _buf_a = _buf_b = None
        _use_a = True

        try:
            with high_resolution_timer():
                while self._running:
                    wall_start = time.perf_counter()
                    frame_time = self._frame_time
                    try:
                        clock = self._clock
                        if clock:
                            if not clock.is_running:
                                time.sleep(0.1)
                                continue
                            t = clock.get_time()
                            speed = clock.speed
                        else:
                            t = wall_start
                            speed = self._speed

                        n = self._led_count
                        if n != _pool_n:
                            _pool_n = n
                            _buf_a = np.empty((n, 3), dtype=np.uint8)
                            _buf_b = np.empty((n, 3), dtype=np.uint8)

                        buf = _buf_a if _use_a else _buf_b
                        _use_a = not _use_a

                        if self._use_real_time:
                            # Use actual wall-clock time
                            import datetime
                            now = datetime.datetime.now()
                            minute_of_day = now.hour * 60 + now.minute + now.second / 60.0
                        else:
                            # Simulated cycle: speed=1.0 → full 24h in ~240s (4 min)
                            cycle_seconds = 240.0 / max(speed, 0.01)
                            phase = (t % cycle_seconds) / cycle_seconds  # 0..1
                            minute_of_day = phase * 1440.0

                        idx = int(minute_of_day) % 1440
                        color = self._lut[idx]
                        buf[:] = color

                        with self._colors_lock:
                            self._colors = buf
                    except Exception as e:
                        logger.error(f"DaylightColorStripStream animation error: {e}")

                    elapsed = time.perf_counter() - wall_start
                    time.sleep(max(frame_time - elapsed, 0.001))
        except Exception as e:
            logger.error(f"Fatal DaylightColorStripStream loop error: {e}", exc_info=True)
        finally:
            self._running = False
