"""LoggingEventBus — structured-logging event bus implementation.

Emits every :class:`~cleveragents.infrastructure.events.models.DomainEvent`
to structured logs via :mod:`structlog`.  Unlike
:class:`~cleveragents.infrastructure.events.reactive.ReactiveEventBus` this
implementation requires no RxPY dependency and is useful for audit trails,
testing, and environments where reactive streaming is not needed.

Based on:
    - docs/specification.md §Event-Driven Architecture
    - Forgejo issue #473
"""

from __future__ import annotations

from collections.abc import Callable

import structlog

from cleveragents.infrastructure.events.models import DomainEvent
from cleveragents.infrastructure.events.types import EventType

_logger = structlog.get_logger(__name__)


class LoggingEventBus:
    """Event bus that writes every event to structured logs.

    Events are logged at ``INFO`` level with all :class:`DomainEvent`
    fields included as structured key-value pairs.  Type-specific
    handlers registered via :meth:`subscribe` are also invoked
    synchronously after logging.

    Thread safety: This implementation is designed for single-threaded use.
    If concurrent access from multiple threads is required, callers must
    provide external synchronization.

    This implementation satisfies the
    :class:`~cleveragents.infrastructure.events.protocol.EventBus`
    protocol and can be used as a drop-in replacement for
    :class:`~cleveragents.infrastructure.events.reactive.ReactiveEventBus`
    in environments where RxPY is undesirable.
    """

    def __init__(self) -> None:
        self._subscriptions: dict[EventType, list[Callable[[DomainEvent], None]]] = {}

    # ------------------------------------------------------------------
    # Public API (satisfies EventBus protocol)
    # ------------------------------------------------------------------

    def emit(self, event: DomainEvent) -> None:
        """Log *event* and dispatch to type-specific handlers.

        Args:
            event: The :class:`DomainEvent` to emit.

        Raises:
            TypeError: If *event* is not a :class:`DomainEvent`.
        """
        if not isinstance(event, DomainEvent):
            raise TypeError(
                f"event must be a DomainEvent, got {type(event).__name__!r}"
            )
        _logger.info(
            "domain_event",
            event_type=str(event.event_type),
            correlation_id=event.correlation_id,
            plan_id=event.plan_id,
            actor_name=event.actor_name,
            details=event.details,
        )
        for handler in list(self._subscriptions.get(event.event_type, ())):
            try:
                handler(event)
            except Exception as exc:
                _logger.warning(
                    "logging_event_handler_failed",
                    event_type=event.event_type.value,
                    handler=getattr(handler, "__qualname__", repr(handler)),
                    error_type=type(exc).__name__,
                    error=str(exc),
                )

    def subscribe(
        self,
        event_type: EventType,
        handler: Callable[[DomainEvent], None],
    ) -> None:
        """Register *handler* for events of *event_type*.

        Args:
            event_type: The :class:`EventType` to listen for.
            handler: Callable invoked for each matching :class:`DomainEvent`.

        Raises:
            TypeError: If *event_type* is not an :class:`EventType`.
            TypeError: If *handler* is not callable.
        """
        if not isinstance(event_type, EventType):
            raise TypeError(
                f"event_type must be an EventType, got {type(event_type).__name__!r}"
            )
        if not callable(handler):
            raise TypeError("handler must be callable")
        self._subscriptions.setdefault(event_type, []).append(handler)


__all__ = ["LoggingEventBus"]
