"""Thread-safe game event pub/sub bus.

The GameEventBus dispatches GameEvent instances to subscribers. Supports
type-specific subscriptions (receive only events of a given type) and
wildcard subscriptions (receive all events, useful for diagnostics).
"""

import collections
import threading
import uuid
from typing import Callable

from wled_controller.core.game_integration.events import GameEvent
from wled_controller.utils import get_logger

logger = get_logger(__name__)

Callback = Callable[[GameEvent], None]


class GameEventBus:
    """In-process pub/sub bus for game events.

    Thread-safe: publish() and subscribe/unsubscribe can be called from
    any thread concurrently.
    """

    def __init__(self, recent_maxlen: int = 100) -> None:
        self._lock = threading.Lock()
        # event_type -> {sub_id: callback}
        self._subscribers: dict[str, dict[str, Callback]] = {}
        # Wildcard subscribers receive every event
        self._wildcard_subscribers: dict[str, Callback] = {}
        # Recent events ring buffer
        self._recent_events: collections.deque[GameEvent] = collections.deque(
            maxlen=recent_maxlen,
        )
        # Stats: event_type -> count
        self._event_counts: dict[str, int] = {}
        self._last_event_timestamp: float | None = None

    def publish(self, event: GameEvent) -> None:
        """Dispatch an event to all matching subscribers.

        Callbacks are invoked synchronously under the lock released —
        a snapshot of subscribers is taken while holding the lock, then
        callbacks are called outside the lock to avoid deadlocks.
        """
        with self._lock:
            self._recent_events.append(event)
            self._event_counts[event.event_type] = self._event_counts.get(event.event_type, 0) + 1
            self._last_event_timestamp = event.timestamp

            # Snapshot subscribers for this event type + wildcards
            type_subs = dict(self._subscribers.get(event.event_type, {}))
            wildcard_subs = dict(self._wildcard_subscribers)

        # Invoke outside lock
        for sub_id, callback in type_subs.items():
            try:
                callback(event)
            except Exception:
                logger.exception(f"Error in event subscriber {sub_id}")

        for sub_id, callback in wildcard_subs.items():
            try:
                callback(event)
            except Exception:
                logger.exception(f"Error in wildcard subscriber {sub_id}")

    def subscribe(self, event_type: str, callback: Callback) -> str:
        """Subscribe to events of a specific type.

        Returns:
            A subscription ID that can be passed to unsubscribe().
        """
        sub_id = f"sub_{uuid.uuid4().hex[:8]}"
        with self._lock:
            if event_type not in self._subscribers:
                self._subscribers[event_type] = {}
            self._subscribers[event_type][sub_id] = callback
        logger.debug(f"Subscribed {sub_id} to event type '{event_type}'")
        return sub_id

    def subscribe_all(self, callback: Callback) -> str:
        """Subscribe to all events (wildcard).

        Returns:
            A subscription ID that can be passed to unsubscribe().
        """
        sub_id = f"sub_{uuid.uuid4().hex[:8]}"
        with self._lock:
            self._wildcard_subscribers[sub_id] = callback
        logger.debug(f"Subscribed {sub_id} as wildcard listener")
        return sub_id

    def unsubscribe(self, subscription_id: str) -> bool:
        """Remove a subscription by ID.

        Returns:
            True if the subscription was found and removed.
        """
        with self._lock:
            # Check type-specific subscribers
            for subs in self._subscribers.values():
                if subscription_id in subs:
                    del subs[subscription_id]
                    logger.debug(f"Unsubscribed {subscription_id}")
                    return True
            # Check wildcard subscribers
            if subscription_id in self._wildcard_subscribers:
                del self._wildcard_subscribers[subscription_id]
                logger.debug(f"Unsubscribed wildcard {subscription_id}")
                return True
        return False

    def get_recent_events(self, limit: int = 50) -> list[GameEvent]:
        """Return the most recent events (newest last).

        Args:
            limit: Maximum number of events to return.
        """
        with self._lock:
            events = list(self._recent_events)
        # Return the last `limit` events
        return events[-limit:]

    def get_stats(self) -> dict:
        """Return diagnostic statistics.

        Returns:
            Dict with 'event_counts' (per-type) and 'last_event_timestamp'.
        """
        with self._lock:
            return {
                "event_counts": dict(self._event_counts),
                "last_event_timestamp": self._last_event_timestamp,
            }
