"""GitHub webhook event dispatcher with registry pattern."""

from __future__ import annotations

import asyncio
import logging
from typing import Any, Callable

logger = logging.getLogger(__name__)

HandlerCallable = Callable[..., Any]


class WebhookDispatcher:
    """Routes incoming GitHub webhook events to registered handler callables.

    Uses a registry pattern: handlers are registered per event type and called
    when a matching event arrives.  Unhandled event types are logged at INFO
    level — no exception is raised.

    Example::

        @dispatcher.register_handler("push")
        async def handle_push(delivery_id: str, payload: dict) -> None:
            print(f"Push to {payload.get('repository', {}).get('full_name')}")

    """

    def __init__(self) -> None:
        self._handlers: dict[str, list[HandlerCallable]] = {}

    def register_handler(self, event_type: str) -> Callable[[HandlerCallable], HandlerCallable]:
        """Return a decorator that registers *fn* as a handler for *event_type*.

        Multiple handlers may be registered for the same event type; all will
        be called in registration order.
        """

        def decorator(fn: HandlerCallable) -> HandlerCallable:
            self._handlers.setdefault(event_type, []).append(fn)
            return fn

        return decorator

    async def dispatch(
        self,
        event_type: str,
        delivery_id: str,
        payload: dict[str, Any],
    ) -> str:
        """Dispatch an event to all registered handlers.

        Args:
            event_type: Value of the ``X-GitHub-Event`` header.
            delivery_id: Value of the ``X-GitHub-Delivery`` header.
            payload: Decoded JSON body of the webhook request.

        Returns:
            ``"dispatched"`` if at least one handler was called,
            ``"no_handler"`` if no handler is registered for *event_type*.
        """
        handlers = self._handlers.get(event_type, [])
        if not handlers:
            logger.info(
                "github webhook: no handler registered for event_type=%s delivery=%s",
                event_type,
                delivery_id,
            )
            return "no_handler"

        for handler in handlers:
            try:
                result = handler(delivery_id, payload)
                if asyncio.iscoroutine(result):
                    await result
            except Exception:
                logger.exception(
                    "github webhook handler error: event=%s handler=%s",
                    event_type,
                    getattr(handler, "__name__", repr(handler)),
                )

        return "dispatched"

    def handler_count(self, event_type: str) -> int:
        """Return the number of registered handlers for *event_type*."""
        return len(self._handlers.get(event_type, []))

    @property
    def registered_event_types(self) -> list[str]:
        """Return a sorted list of all event types that have registered handlers."""
        return sorted(self._handlers.keys())


# Module-level singleton — import and use directly.
dispatcher = WebhookDispatcher()

# Convenient shorthand matching the task API spec.
register_handler = dispatcher.register_handler
