# app/modules/events.py
"""
Module event bus for lifecycle events.

Provides a way for different parts of the application to subscribe to
module lifecycle events (enable, disable, startup, shutdown, config changes).

Usage:
    from app.modules.events import module_event_bus, ModuleEvent

    # Subscribe to events
    @module_event_bus.subscribe(ModuleEvent.ENABLED)
    def on_module_enabled(event_data: ModuleEventData):
        print(f"Module {event_data.module_code} enabled for platform {event_data.platform_id}")

    # Or subscribe manually
    module_event_bus.subscribe(ModuleEvent.DISABLED, my_handler)

    # Emit events (typically done by ModuleService)
    module_event_bus.emit(ModuleEvent.ENABLED, ModuleEventData(
        module_code="billing",
        platform_id=1,
        user_id=42,
    ))
"""

import logging
from collections.abc import Callable
from dataclasses import dataclass, field
from datetime import UTC, datetime
from enum import Enum
from typing import Any

logger = logging.getLogger(__name__)


class ModuleEvent(str, Enum):
    """Module lifecycle events."""

    # Module enablement
    ENABLED = "enabled"  # Module enabled for a platform
    DISABLED = "disabled"  # Module disabled for a platform

    # Application lifecycle
    STARTUP = "startup"  # Application starting, module initializing
    SHUTDOWN = "shutdown"  # Application shutting down

    # Configuration
    CONFIG_CHANGED = "config_changed"  # Module configuration updated

    # Health
    HEALTH_CHECK = "health_check"  # Health check performed


@dataclass
class ModuleEventData:
    """
    Data passed with module events.

    Attributes:
        module_code: The module code (e.g., "billing", "cms")
        platform_id: Platform ID if event is platform-specific
        user_id: User ID if action was user-initiated
        config: Configuration data (for CONFIG_CHANGED)
        metadata: Additional event-specific data
        timestamp: When the event occurred
    """

    module_code: str
    platform_id: int | None = None
    user_id: int | None = None
    config: dict[str, Any] | None = None
    metadata: dict[str, Any] = field(default_factory=dict)
    timestamp: datetime = field(default_factory=lambda: datetime.now(UTC))


# Type alias for event handlers
EventHandler = Callable[[ModuleEventData], None]


class ModuleEventBus:
    """
    Event bus for module lifecycle events.

    Allows components to subscribe to and emit module events.
    This enables loose coupling between the module system and other
    parts of the application that need to react to module changes.

    Example:
        # In a service that needs to react to module changes
        @module_event_bus.subscribe(ModuleEvent.ENABLED)
        def on_billing_enabled(data: ModuleEventData):
            if data.module_code == "billing":
                setup_stripe_webhook(data.platform_id)

        # Or subscribe to multiple events
        @module_event_bus.subscribe(ModuleEvent.ENABLED, ModuleEvent.DISABLED)
        def on_module_change(data: ModuleEventData):
            clear_menu_cache(data.platform_id)
    """

    def __init__(self) -> None:
        self._handlers: dict[ModuleEvent, list[EventHandler]] = {
            event: [] for event in ModuleEvent
        }
        self._global_handlers: list[EventHandler] = []

    def subscribe(
        self,
        *events: ModuleEvent,
    ) -> Callable[[EventHandler], EventHandler]:
        """
        Decorator to subscribe a handler to one or more events.

        Args:
            events: Events to subscribe to. If empty, subscribes to all events.

        Returns:
            Decorator function

        Example:
            @module_event_bus.subscribe(ModuleEvent.ENABLED)
            def on_enabled(data: ModuleEventData):
                print(f"Module {data.module_code} enabled")
        """

        def decorator(handler: EventHandler) -> EventHandler:
            if not events:
                # Subscribe to all events
                self._global_handlers.append(handler)
            else:
                for event in events:
                    self._handlers[event].append(handler)
            return handler

        return decorator

    def subscribe_handler(
        self,
        event: ModuleEvent,
        handler: EventHandler,
    ) -> None:
        """
        Subscribe a handler to a specific event.

        Args:
            event: Event to subscribe to
            handler: Handler function to call
        """
        self._handlers[event].append(handler)

    def unsubscribe(
        self,
        event: ModuleEvent,
        handler: EventHandler,
    ) -> bool:
        """
        Unsubscribe a handler from an event.

        Args:
            event: Event to unsubscribe from
            handler: Handler function to remove

        Returns:
            True if handler was found and removed
        """
        try:
            self._handlers[event].remove(handler)
            return True
        except ValueError:
            return False

    def emit(
        self,
        event: ModuleEvent,
        data: ModuleEventData,
    ) -> None:
        """
        Emit an event to all subscribed handlers.

        Args:
            event: Event type to emit
            data: Event data to pass to handlers

        Note:
            Handlers are called synchronously in registration order.
            Exceptions in handlers are logged but don't prevent other
            handlers from being called.
        """
        logger.debug(
            f"Emitting {event.value} event for module {data.module_code}"
            + (f" on platform {data.platform_id}" if data.platform_id else "")
        )

        # Call event-specific handlers
        for handler in self._handlers[event]:
            try:
                handler(data)
            except Exception as e:
                logger.exception(
                    f"Error in {event.value} handler {handler.__name__}: {e}"
                )

        # Call global handlers
        for handler in self._global_handlers:
            try:
                handler(data)
            except Exception as e:
                logger.exception(
                    f"Error in global handler {handler.__name__}: {e}"
                )

    def emit_enabled(
        self,
        module_code: str,
        platform_id: int,
        user_id: int | None = None,
    ) -> None:
        """
        Convenience method to emit an ENABLED event.

        Args:
            module_code: Module that was enabled
            platform_id: Platform it was enabled for
            user_id: User who enabled it
        """
        self.emit(
            ModuleEvent.ENABLED,
            ModuleEventData(
                module_code=module_code,
                platform_id=platform_id,
                user_id=user_id,
            ),
        )

    def emit_disabled(
        self,
        module_code: str,
        platform_id: int,
        user_id: int | None = None,
    ) -> None:
        """
        Convenience method to emit a DISABLED event.

        Args:
            module_code: Module that was disabled
            platform_id: Platform it was disabled for
            user_id: User who disabled it
        """
        self.emit(
            ModuleEvent.DISABLED,
            ModuleEventData(
                module_code=module_code,
                platform_id=platform_id,
                user_id=user_id,
            ),
        )

    def emit_startup(self, module_code: str) -> None:
        """
        Convenience method to emit a STARTUP event.

        Args:
            module_code: Module that is starting up
        """
        self.emit(
            ModuleEvent.STARTUP,
            ModuleEventData(module_code=module_code),
        )

    def emit_shutdown(self, module_code: str) -> None:
        """
        Convenience method to emit a SHUTDOWN event.

        Args:
            module_code: Module that is shutting down
        """
        self.emit(
            ModuleEvent.SHUTDOWN,
            ModuleEventData(module_code=module_code),
        )

    def emit_config_changed(
        self,
        module_code: str,
        platform_id: int,
        config: dict[str, Any],
        user_id: int | None = None,
    ) -> None:
        """
        Convenience method to emit a CONFIG_CHANGED event.

        Args:
            module_code: Module whose config changed
            platform_id: Platform the config belongs to
            config: New configuration values
            user_id: User who changed the config
        """
        self.emit(
            ModuleEvent.CONFIG_CHANGED,
            ModuleEventData(
                module_code=module_code,
                platform_id=platform_id,
                config=config,
                user_id=user_id,
            ),
        )

    def clear_handlers(self) -> None:
        """Clear all handlers. Useful for testing."""
        for event in ModuleEvent:
            self._handlers[event] = []
        self._global_handlers = []


# Global event bus instance
module_event_bus = ModuleEventBus()


__all__ = [
    "ModuleEvent",
    "ModuleEventData",
    "ModuleEventBus",
    "EventHandler",
    "module_event_bus",
]
