"""TopicRouter dispatches consumed records to topic-specific handlers.

Implements the MessageHandler protocol so it plugs directly into
Consumer.subscribe().

Usage::

    from neonlink import TopicRouter, Consumer

    router = TopicRouter()
    router.handle("posting-input", posting_handler)
    router.handle_func("sync-triggers", lambda rec: process_sync(rec))

    consumer = Consumer(cfg, topics=router.topics())
    consumer.subscribe(router)
"""

from __future__ import annotations

from typing import Callable

from neonlink.errors import PermanentError
from neonlink.record import Record


class TopicRouter:
    """Routes records to topic-specific handlers.

    Implements MessageHandler so it can be passed directly to Consumer.subscribe().
    """

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

    def handle(self, topic: str, handler: object) -> None:
        """Register a MessageHandler for a topic.

        Raises ValueError at startup on empty topic, None handler, or duplicate
        registration — misconfiguration should fail fast.
        """
        if not topic:
            raise ValueError("neonlink: TopicRouter.handle called with empty topic")
        if handler is None:
            raise ValueError("neonlink: TopicRouter.handle called with None handler")
        if not callable(getattr(handler, "handle_message", None)):
            raise TypeError(
                f"neonlink: handler for topic {topic!r} must implement handle_message(record)"
            )
        if topic in self._handlers:
            raise ValueError(
                f"neonlink: TopicRouter.handle called twice for topic {topic!r}"
            )
        self._handlers[topic] = handler

    def handle_func(self, topic: str, fn: Callable[[Record], None]) -> None:
        """Register a plain function as a handler for a topic."""
        if fn is None:
            raise ValueError("neonlink: TopicRouter.handle_func called with None fn")
        self.handle(topic, _FuncHandler(fn))

    def topics(self) -> list[str]:
        """Return registered topic names in sorted order.

        Pass this directly to the Consumer constructor.
        """
        return sorted(self._handlers.keys())

    def handle_message(self, record: Record) -> None:
        """Dispatch the record to the handler registered for record.topic.

        Raises PermanentError if no handler is registered — unroutable records
        should not block the consumer with infinite redelivery.
        """
        handler = self._handlers.get(record.topic)
        if handler is None:
            raise PermanentError(
                f"neonlink: no handler registered for topic {record.topic!r}"
            )
        handler.handle_message(record)


class _FuncHandler:
    """Adapts a plain function to the MessageHandler protocol."""

    __slots__ = ("_fn",)

    def __init__(self, fn: Callable[[Record], None]) -> None:
        self._fn = fn

    def handle_message(self, record: Record) -> None:
        self._fn(record)
