"""Event bus implementation for loose coupling between services."""

from __future__ import annotations

import asyncio
import logging
from dataclasses import dataclass
from typing import TYPE_CHECKING, Any, cast

if TYPE_CHECKING:
    from collections.abc import Awaitable, Callable, Coroutine

logger = logging.getLogger(__name__)


@dataclass
class Event:
    """Base event class."""

    name: str
    data: dict[str, Any]
    source: str  # Which service emitted this
    timestamp: float | None = None  # Will be set automatically

    def __post_init__(self) -> None:
        """Set timestamp if not provided."""
        if self.timestamp is None:
            import time

            self.timestamp = time.time()


class EventBus:
    """Central event bus for loose coupling between services."""

    def __init__(self) -> None:
        """Initialize the event bus."""
        self._handlers: dict[str, list[Callable[[Event], None]]] = {}
        self._async_handlers: dict[str, list[Callable[[Event], Awaitable[None]]]] = {}
        self._running_tasks: set[asyncio.Task[None]] = set()

    def subscribe(
        self, event_name: str, handler: Callable[[Event], None | Awaitable[None]]
    ) -> None:
        """Subscribe to an event.

        Args:
            event_name: Name of the event to subscribe to
            handler: Function to call when event is emitted (sync or async)
        """
        if asyncio.iscoroutinefunction(handler):
            if event_name not in self._async_handlers:
                self._async_handlers[event_name] = []
            self._async_handlers[event_name].append(
                cast("Callable[[Event], Awaitable[None]]", handler)
            )
        else:
            if event_name not in self._handlers:
                self._handlers[event_name] = []
            self._handlers[event_name].append(cast("Callable[[Event], None]", handler))

        logger.debug(f"Subscribed handler to event: {event_name}")

    def unsubscribe(
        self, event_name: str, handler: Callable[[Event], None | Awaitable[None]]
    ) -> bool:
        """Unsubscribe from an event.

        Args:
            event_name: Name of the event
            handler: Handler function to remove

        Returns:
            True if handler was found and removed, False otherwise
        """
        removed = False

        if asyncio.iscoroutinefunction(handler):
            if event_name in self._async_handlers:
                try:
                    self._async_handlers[event_name].remove(
                        cast("Callable[[Event], Awaitable[None]]", handler)
                    )
                    removed = True
                    if not self._async_handlers[event_name]:
                        del self._async_handlers[event_name]
                except ValueError:
                    pass
        else:
            if event_name in self._handlers:
                try:
                    self._handlers[event_name].remove(cast("Callable[[Event], None]", handler))
                    removed = True
                    if not self._handlers[event_name]:
                        del self._handlers[event_name]
                except ValueError:
                    pass

        if removed:
            logger.debug(f"Unsubscribed handler from event: {event_name}")

        return removed

    def emit(self, event_name: str, data: dict[str, Any], source: str = "unknown") -> None:
        """Emit an event to all subscribers.

        Args:
            event_name: Name of the event to emit
            data: Event data
            source: Source service that emitted the event
        """
        event = Event(event_name, data, source)

        logger.debug(f"Emitting event: {event_name} from {source}")

        # Handle sync handlers
        if event_name in self._handlers:
            for handler in self._handlers[event_name]:
                try:
                    handler(event)
                except Exception as e:
                    logger.error(f"Error in sync event handler for {event_name}: {e}")

        # Handle async handlers
        if event_name in self._async_handlers:
            task = asyncio.create_task(self._emit_async(event))
            self._running_tasks.add(task)
            # Clean up completed tasks
            task.add_done_callback(self._running_tasks.discard)

    async def _emit_async(self, event: Event) -> None:
        """Emit event to async handlers.

        Args:
            event: Event to emit
        """
        if event.name in self._async_handlers:
            handlers = self._async_handlers[event.name]

            tasks: list[asyncio.Task[None]] = []
            for handler in handlers:
                try:
                    coro = cast("Coroutine[Any, Any, None]", handler(event))
                    task: asyncio.Task[None] = asyncio.create_task(coro)
                    tasks.append(task)
                except Exception as e:
                    logger.error(f"Error creating task for async handler: {e}")

            if tasks:
                results = await asyncio.gather(*tasks, return_exceptions=True)

                for result in results:
                    if isinstance(result, Exception):
                        logger.error(f"Error in async event handler for {event.name}: {result}")

    def has_subscribers(self, event_name: str) -> bool:
        """Check if an event has any subscribers.

        Args:
            event_name: Name of the event

        Returns:
            True if there are subscribers for the event
        """
        return (event_name in self._handlers and len(self._handlers[event_name]) > 0) or (
            event_name in self._async_handlers and len(self._async_handlers[event_name]) > 0
        )

    def get_subscriber_count(self, event_name: str) -> int:
        """Get the number of subscribers for an event.

        Args:
            event_name: Name of the event

        Returns:
            Total number of subscribers (sync + async)
        """
        sync_count = len(self._handlers.get(event_name, []))
        async_count = len(self._async_handlers.get(event_name, []))
        return sync_count + async_count

    async def wait_for_pending_events(self, timeout: float = 10.0) -> None:
        """Wait for all pending async event handlers to complete.

        Args:
            timeout: Maximum time to wait in seconds
        """
        if self._running_tasks:
            try:
                await asyncio.wait_for(
                    asyncio.gather(*self._running_tasks, return_exceptions=True), timeout=timeout
                )
            except TimeoutError:
                logger.warning(f"Timeout waiting for event handlers after {timeout}s")
                for task in self._running_tasks:
                    if not task.done():
                        task.cancel()

    def clear_all_subscribers(self) -> None:
        """Clear all event subscribers."""
        self._handlers.clear()
        self._async_handlers.clear()
        logger.debug("Cleared all event subscribers")
