"""
EventBus for Information Spaces.

This module provides an in-process publish/subscribe system
for space events. It supports both synchronous and asynchronous
event handlers.
"""

import asyncio
import logging
from collections import defaultdict
from typing import Callable, Dict, List, Optional, Set, Union, Any
from dataclasses import dataclass, field
import weakref

from .models import SpaceEvent, SpaceEventType


# Type aliases for handlers
SyncHandler = Callable[[SpaceEvent], None]
AsyncHandler = Callable[[SpaceEvent], Any]  # Coroutine
Handler = Union[SyncHandler, AsyncHandler]


logger = logging.getLogger(__name__)


@dataclass
class HandlerRegistration:
    """
    Registration info for an event handler.

    Attributes:
        handler: The handler function
        is_async: Whether the handler is async
        priority: Handler priority (lower = earlier execution)
        handler_id: Unique ID for this registration
    """

    handler: Handler
    is_async: bool = False
    priority: int = 100
    handler_id: str = field(default_factory=lambda: str(id(None)))

    def __post_init__(self):
        self.handler_id = str(id(self.handler))


class EventBus:
    """
    In-process event bus for space events.

    Provides publish/subscribe functionality for space operations.
    Handlers can be registered for specific event types or for all events.

    Usage:
        bus = EventBus()

        # Register a handler
        def on_space_created(event: SpaceEvent):
            print(f"Space created: {event.payload['name']}")

        bus.subscribe(SpaceEventType.SPACE_CREATED, on_space_created)

        # Emit an event
        bus.emit(SpaceEvent(
            event_type=SpaceEventType.SPACE_CREATED,
            space_id="space-123",
            payload={"name": "my-docs"}
        ))

        # Unsubscribe
        bus.unsubscribe(SpaceEventType.SPACE_CREATED, on_space_created)
    """

    def __init__(self):
        """Initialize the event bus."""
        # Handlers indexed by event type
        self._handlers: Dict[SpaceEventType, List[HandlerRegistration]] = defaultdict(
            list
        )
        # Handlers that receive all events
        self._global_handlers: List[HandlerRegistration] = []
        # Track all handler IDs for quick lookup
        self._handler_ids: Set[str] = set()
        # Event history (optional, for debugging)
        self._history: List[SpaceEvent] = []
        self._history_enabled: bool = False
        self._max_history: int = 1000

    def subscribe(
        self,
        event_type: Optional[SpaceEventType],
        handler: Handler,
        priority: int = 100,
    ) -> str:
        """
        Subscribe a handler to events.

        Args:
            event_type: The event type to subscribe to, or None for all events
            handler: The handler function (sync or async)
            priority: Handler priority (lower = earlier execution)

        Returns:
            Handler ID that can be used to unsubscribe
        """
        is_async = asyncio.iscoroutinefunction(handler)
        registration = HandlerRegistration(
            handler=handler,
            is_async=is_async,
            priority=priority,
        )

        if event_type is None:
            self._global_handlers.append(registration)
            self._global_handlers.sort(key=lambda r: r.priority)
        else:
            self._handlers[event_type].append(registration)
            self._handlers[event_type].sort(key=lambda r: r.priority)

        self._handler_ids.add(registration.handler_id)
        return registration.handler_id

    def subscribe_all(self, handler: Handler, priority: int = 100) -> str:
        """
        Subscribe a handler to all events.

        Args:
            handler: The handler function
            priority: Handler priority

        Returns:
            Handler ID
        """
        return self.subscribe(None, handler, priority)

    def unsubscribe(
        self,
        event_type: Optional[SpaceEventType],
        handler: Handler,
    ) -> bool:
        """
        Unsubscribe a handler from events.

        Args:
            event_type: The event type, or None for global handlers
            handler: The handler to remove

        Returns:
            True if handler was found and removed
        """
        handler_id = str(id(handler))

        if event_type is None:
            for i, reg in enumerate(self._global_handlers):
                if reg.handler_id == handler_id:
                    self._global_handlers.pop(i)
                    self._handler_ids.discard(handler_id)
                    return True
        else:
            handlers = self._handlers.get(event_type, [])
            for i, reg in enumerate(handlers):
                if reg.handler_id == handler_id:
                    handlers.pop(i)
                    self._handler_ids.discard(handler_id)
                    return True

        return False

    def unsubscribe_by_id(self, handler_id: str) -> bool:
        """
        Unsubscribe a handler by its ID.

        Args:
            handler_id: The handler ID returned by subscribe

        Returns:
            True if handler was found and removed
        """
        # Check global handlers
        for i, reg in enumerate(self._global_handlers):
            if reg.handler_id == handler_id:
                self._global_handlers.pop(i)
                self._handler_ids.discard(handler_id)
                return True

        # Check type-specific handlers
        for handlers in self._handlers.values():
            for i, reg in enumerate(handlers):
                if reg.handler_id == handler_id:
                    handlers.pop(i)
                    self._handler_ids.discard(handler_id)
                    return True

        return False

    def emit(self, event: SpaceEvent) -> List[Exception]:
        """
        Emit an event synchronously.

        Calls all registered handlers for the event type.
        Async handlers are run in a new event loop if needed.

        Args:
            event: The event to emit

        Returns:
            List of exceptions raised by handlers (empty if all succeeded)
        """
        if self._history_enabled:
            self._record_history(event)

        exceptions: List[Exception] = []

        # Collect all handlers to call
        handlers = list(self._global_handlers)
        handlers.extend(self._handlers.get(event.event_type, []))
        handlers.sort(key=lambda r: r.priority)

        for registration in handlers:
            try:
                if registration.is_async:
                    # Run async handler
                    try:
                        loop = asyncio.get_running_loop()
                    except RuntimeError:
                        loop = None

                    if loop and loop.is_running():
                        # Create a task but don't await it here
                        asyncio.create_task(registration.handler(event))
                    else:
                        asyncio.run(registration.handler(event))
                else:
                    registration.handler(event)
            except Exception as e:
                logger.exception(
                    f"Handler {registration.handler_id} raised exception for event {event.event_type}"
                )
                exceptions.append(e)

        return exceptions

    async def emit_async(self, event: SpaceEvent) -> List[Exception]:
        """
        Emit an event asynchronously.

        Args:
            event: The event to emit

        Returns:
            List of exceptions raised by handlers
        """
        if self._history_enabled:
            self._record_history(event)

        exceptions: List[Exception] = []

        # Collect all handlers to call
        handlers = list(self._global_handlers)
        handlers.extend(self._handlers.get(event.event_type, []))
        handlers.sort(key=lambda r: r.priority)

        for registration in handlers:
            try:
                if registration.is_async:
                    await registration.handler(event)
                else:
                    registration.handler(event)
            except Exception as e:
                logger.exception(
                    f"Handler {registration.handler_id} raised exception for event {event.event_type}"
                )
                exceptions.append(e)

        return exceptions

    def has_handlers(self, event_type: SpaceEventType) -> bool:
        """
        Check if there are any handlers for an event type.

        Args:
            event_type: The event type to check

        Returns:
            True if there are handlers (including global handlers)
        """
        return bool(self._global_handlers) or bool(
            self._handlers.get(event_type, [])
        )

    def handler_count(self, event_type: Optional[SpaceEventType] = None) -> int:
        """
        Get the number of handlers registered.

        Args:
            event_type: Specific event type, or None for total count

        Returns:
            Number of handlers
        """
        if event_type is None:
            count = len(self._global_handlers)
            for handlers in self._handlers.values():
                count += len(handlers)
            return count
        else:
            return len(self._handlers.get(event_type, [])) + len(
                self._global_handlers
            )

    def clear(self, event_type: Optional[SpaceEventType] = None) -> int:
        """
        Clear handlers.

        Args:
            event_type: Specific event type, or None to clear all

        Returns:
            Number of handlers removed
        """
        if event_type is None:
            count = self.handler_count()
            self._handlers.clear()
            self._global_handlers.clear()
            self._handler_ids.clear()
            return count
        else:
            handlers = self._handlers.pop(event_type, [])
            for reg in handlers:
                self._handler_ids.discard(reg.handler_id)
            return len(handlers)

    # History management

    def enable_history(self, max_events: int = 1000) -> None:
        """Enable event history recording."""
        self._history_enabled = True
        self._max_history = max_events

    def disable_history(self) -> None:
        """Disable event history recording."""
        self._history_enabled = False

    def clear_history(self) -> None:
        """Clear the event history."""
        self._history.clear()

    def get_history(
        self,
        event_type: Optional[SpaceEventType] = None,
        space_id: Optional[str] = None,
        limit: Optional[int] = None,
    ) -> List[SpaceEvent]:
        """
        Get event history.

        Args:
            event_type: Filter by event type
            space_id: Filter by space ID
            limit: Maximum number of events to return

        Returns:
            List of events (newest first)
        """
        events = list(reversed(self._history))

        if event_type is not None:
            events = [e for e in events if e.event_type == event_type]

        if space_id is not None:
            events = [e for e in events if e.space_id == space_id]

        if limit is not None:
            events = events[:limit]

        return events

    def _record_history(self, event: SpaceEvent) -> None:
        """Record an event in history."""
        self._history.append(event)
        if len(self._history) > self._max_history:
            self._history = self._history[-self._max_history :]


# Global event bus instance (optional singleton pattern)
_default_bus: Optional[EventBus] = None


def get_event_bus() -> EventBus:
    """Get the default global event bus instance."""
    global _default_bus
    if _default_bus is None:
        _default_bus = EventBus()
    return _default_bus


def reset_event_bus() -> None:
    """Reset the default global event bus (useful for testing)."""
    global _default_bus
    _default_bus = None
