"""Handler registration and request dispatch.

The Dispatcher receives pre-instantiated handler singletons at startup
and routes incoming requests to the correct handler based on identifier keys.
"""

from __future__ import annotations

import logging
from typing import Dict, List

from entr_adapter_core.handlers import BaseHandler
from entr_adapter_core.logging import execute_with_flight_recorder
from entr_adapter_core.schemas import (
    AdapterResponse,
    DocumentDownloadResponse,
    DocumentPayload,
    RetrievalPayload,
    RetrievalResponse,
    StatusEnum,
)

logger = logging.getLogger(__name__)


class Dispatcher:
    """Routes incoming requests to registered handler instances.

    Handlers are registered as a dict mapping ``adapter_identifier`` strings
    to pre-instantiated ``BaseHandler`` singletons. The dispatcher validates
    the registry at construction time and provides dispatch methods for
    processing and retrieval endpoints.
    """

    def __init__(self, handlers: Dict[str, BaseHandler]) -> None:
        """Initialize the dispatcher with a handler registry.

        Args:
            handlers: Mapping of adapter identifiers to handler instances.

        Raises:
            ValueError: If the handlers dict is empty.
            TypeError: If any value in the dict is not a BaseHandler instance.
        """
        if not handlers:
            raise ValueError(
                "At least one handler must be registered. "
                "Pass a non-empty dict to handlers, e.g. "
                "{'invoice': InvoiceHandler(settings=...)}"
            )

        for key, handler in handlers.items():
            if not isinstance(handler, BaseHandler):
                raise TypeError(
                    f"Handler for '{key}' must be an instance of BaseHandler, "
                    f"got {type(handler).__name__}"
                )

        self._handlers = handlers
        logger.info("Registered handlers: %s", list(handlers.keys()))

    def get_handler_identifiers(self) -> List[str]:
        """Return the list of registered handler identifier keys."""
        return list(self._handlers.keys())

    async def dispatch_process(self, payload: DocumentPayload) -> AdapterResponse:
        """Dispatch a document payload to the matching handler.

        Looks up the handler by ``payload.adapter_identifier``. If found,
        calls ``handler.process(payload)``. If not found or if the handler
        raises an unhandled exception, returns a failed ``AdapterResponse``
        with a safe error message.

        Args:
            payload: The document payload from ENTR Core.

        Returns:
            The ``AdapterResponse`` from the handler, or a failed response
            on dispatch error.
        """
        identifier = payload.adapter_identifier
        handler = self._handlers.get(identifier)

        if handler is None:
            logger.warning("Unknown adapter_identifier: %s", identifier)
            return AdapterResponse(
                document_id=payload.document_id,
                status=StatusEnum.FAILED,
                failure_reason=f"Unknown adapter_identifier: {identifier}",
            )

        return await execute_with_flight_recorder(handler, payload)

    async def dispatch_list_documents(
        self, source_identifier: str, payload: RetrievalPayload
    ) -> RetrievalResponse:
        """Dispatch a list-documents request to the matching retrieval handler.

        Args:
            source_identifier: The adapter identifier to look up.
            payload: The retrieval payload with filters.

        Returns:
            The ``RetrievalResponse`` from the retrieval handler, or an
            error response if the handler is not found or has no retrieval support.
        """
        handler = self._handlers.get(source_identifier)

        if handler is None:
            logger.warning(
                "Unknown source_identifier for list_documents: %s",
                source_identifier,
            )
            return RetrievalResponse(
                status=StatusEnum.FAILED,
                message=f"Unknown source_identifier: {source_identifier}",
                documents=[],
            )

        if handler.retrieval_handler is None:
            logger.warning(
                "No retrieval_handler for source_identifier: %s",
                source_identifier,
            )
            return RetrievalResponse(
                status=StatusEnum.FAILED,
                message=f"No retrieval handler configured for: {source_identifier}",
                documents=[],
            )

        try:
            return await handler.retrieval_handler.list_documents(payload)
        except Exception as exc:
            logger.exception(
                "Unhandled exception in list_documents for source_identifier: %s",
                source_identifier,
            )
            return RetrievalResponse(
                status=StatusEnum.FAILED,
                message=f"Internal error: {type(exc).__name__}",
                documents=[],
            )

    async def dispatch_download_document(
        self, source_identifier: str, document_identifier: str
    ) -> DocumentDownloadResponse:
        """Dispatch a download-document request to the matching retrieval handler.

        Args:
            source_identifier: The adapter identifier to look up.
            document_identifier: The document ID to download.

        Returns:
            The ``DocumentDownloadResponse`` from the retrieval handler.

        Raises:
            ValueError: If the handler or retrieval handler is not found.
                The router (Story 1.6) should catch this and return an
                appropriate HTTP response.
        """
        handler = self._handlers.get(source_identifier)

        if handler is None:
            logger.warning(
                "Unknown source_identifier for download_document: %s",
                source_identifier,
            )
            raise ValueError(f"Unknown source_identifier: {source_identifier}")

        if handler.retrieval_handler is None:
            logger.warning(
                "No retrieval_handler for source_identifier: %s",
                source_identifier,
            )
            raise ValueError(
                f"No retrieval handler configured for: {source_identifier}"
            )

        try:
            return await handler.retrieval_handler.get_document(document_identifier)
        except ValueError:
            raise
        except Exception as exc:
            logger.exception(
                "Unhandled exception in download_document for source_identifier: %s",
                source_identifier,
            )
            raise ValueError(
                f"Internal error during download: {type(exc).__name__}"
            ) from exc
