"""GameEventValueStream — value stream driven by game events.

Subscribes to the GameEventBus for a configured event type, normalizes
incoming game values to 0.0-1.0 using min/max mapping, applies optional
EMA smoothing, and reverts to a default value on timeout.

Thread-safe: the EventBus callback runs on the publisher's thread while
get_value() is called from the render thread.
"""

from __future__ import annotations

import threading
import time
from typing import TYPE_CHECKING, Optional

from wled_controller.core.processing.value_stream import ValueStream
from wled_controller.utils import get_logger

if TYPE_CHECKING:
    from wled_controller.core.game_integration.event_bus import GameEventBus
    from wled_controller.core.game_integration.events import GameEvent
    from wled_controller.storage.value_source import GameEventValueSource

logger = get_logger(__name__)


class GameEventValueStream(ValueStream):
    """Runtime resolver that exposes game metrics as 0.0-1.0 scalars.

    Subscribes to events of a specific type on the GameEventBus,
    normalizes raw game values, applies EMA smoothing, and handles
    timeout (reverts to default_value when no events arrive).
    """

    def __init__(
        self,
        event_type: str,
        min_game_value: float = 0.0,
        max_game_value: float = 100.0,
        smoothing: float = 0.0,
        default_value: float = 0.5,
        timeout: float = 5.0,
        event_bus: Optional["GameEventBus"] = None,
    ) -> None:
        self._event_type = event_type
        self._min_game = min_game_value
        self._max_game = max_game_value
        self._smoothing = max(0.0, min(1.0, smoothing))
        self._default_value = max(0.0, min(1.0, default_value))
        self._timeout = max(0.0, timeout)
        self._event_bus = event_bus

        self._lock = threading.Lock()
        self._current_value: float = self._default_value
        self._last_event_time: Optional[float] = None
        self._subscription_id: Optional[str] = None
        self._has_received_event: bool = False

    def start(self) -> None:
        """Subscribe to the EventBus for the configured event type."""
        if self._event_bus is not None:
            self._subscription_id = self._event_bus.subscribe(
                self._event_type,
                self._on_event,
            )
            logger.info(
                "GameEventValueStream started (event_type=%s, sub=%s)",
                self._event_type,
                self._subscription_id,
            )

    def stop(self) -> None:
        """Unsubscribe from the EventBus and reset state."""
        if self._event_bus is not None and self._subscription_id is not None:
            self._event_bus.unsubscribe(self._subscription_id)
            logger.info(
                "GameEventValueStream stopped (event_type=%s, sub=%s)",
                self._event_type,
                self._subscription_id,
            )
            self._subscription_id = None
        with self._lock:
            self._current_value = self._default_value
            self._last_event_time = None
            self._has_received_event = False

    def get_value(self) -> float:
        """Return current normalized value (0.0-1.0), or default if timed out."""
        with self._lock:
            if not self._has_received_event:
                return self._default_value

            if self._timeout > 0.0 and self._last_event_time is not None:
                elapsed = time.monotonic() - self._last_event_time
                if elapsed > self._timeout:
                    return self._default_value

            return self._current_value

    def get_color(self) -> tuple:
        """Game event value source only provides scalars, not colors."""
        raise NotImplementedError("GameEventValueStream does not produce colors")

    def update_source(self, source: "GameEventValueSource") -> None:
        """Hot-update parameters from a modified GameEventValueSource config."""
        from wled_controller.storage.value_source import GameEventValueSource as GEVS

        if not isinstance(source, GEVS):
            return

        with self._lock:
            self._min_game = source.min_game_value
            self._max_game = source.max_game_value
            self._smoothing = max(0.0, min(1.0, source.smoothing))
            self._default_value = max(0.0, min(1.0, source.default_value))
            self._timeout = max(0.0, source.timeout)

    def _on_event(self, event: "GameEvent") -> None:
        """EventBus callback — normalize and apply smoothing.

        Called from the publisher's thread; must be thread-safe.
        """
        raw_value = event.value
        normalized = self._normalize(raw_value)

        with self._lock:
            if self._smoothing > 0.0 and self._has_received_event:
                alpha = 1.0 - self._smoothing
                normalized = alpha * normalized + self._smoothing * self._current_value

            self._current_value = normalized
            self._last_event_time = time.monotonic()
            self._has_received_event = True

    def _normalize(self, raw_value: float) -> float:
        """Map a raw game value to the 0.0-1.0 range using min/max."""
        game_range = self._max_game - self._min_game
        if abs(game_range) < 1e-9:
            return 0.5
        normalized = (raw_value - self._min_game) / game_range
        return max(0.0, min(1.0, normalized))
