"""App factory for creating fully configured ENTR adapter applications.

Provides ``create_adapter_app()`` — the primary entry point for most adapters.
For adapters needing custom middleware or additional routes, use
``create_adapter_router()`` from ``router.py`` instead.
"""

from __future__ import annotations

import asyncio
import logging
from contextlib import asynccontextmanager
from typing import Any, Callable, Dict, List, Optional

from fastapi import FastAPI

from entr_adapter_core.app.router import create_adapter_router
from entr_adapter_core.handlers import BaseHandler

logger = logging.getLogger(__name__)


def create_adapter_app(
    handlers: Dict[str, BaseHandler],
    *,
    on_startup: Optional[List[Callable]] = None,
    on_shutdown: Optional[List[Callable]] = None,
    title: str = "ENTR Adapter",
    settings: Any = None,
    **kwargs,
) -> FastAPI:
    """Create a fully configured FastAPI application for an ENTR adapter.

    This is the primary entry point for most adapters. It creates a FastAPI
    app with all standard endpoints (/process, /list_documents,
    /download_document, /health) and optional lifecycle callbacks.

    For adapters needing custom middleware, additional routes, or custom
    startup logic, use ``create_adapter_router()`` instead and include
    it in your own FastAPI app.

    Args:
        handlers: Mapping of adapter identifiers to handler instances.
            Each handler must be an instance of ``BaseHandler``.
        on_startup: Optional list of callbacks to execute on app startup.
            Executed in order after handler registration. Both sync and
            async callbacks are supported. A failing callback prevents
            the app from starting (fatal).
        on_shutdown: Optional list of callbacks to execute on app shutdown.
            Executed in order. Both sync and async callbacks are supported.
            Exceptions are logged but do not prevent other callbacks
            from running.
        title: FastAPI app title (default: "ENTR Adapter").
        settings: Optional settings object passed to the router for
            ENV-based debug endpoint gating.
        **kwargs: Additional keyword arguments passed to ``FastAPI()``.

    Returns:
        A configured ``FastAPI`` application ready to serve.

    Examples:
        Basic usage::

            app = create_adapter_app(
                handlers={"invoice": InvoiceHandler(settings=my_settings)}
            )

        With lifecycle callbacks::

            async def connect_db():
                await db.connect()

            async def close_db():
                await db.disconnect()

            app = create_adapter_app(
                handlers={"invoice": InvoiceHandler()},
                on_startup=[connect_db],
                on_shutdown=[close_db],
            )

        For custom middleware, use ``create_adapter_router()`` instead::

            from entr_adapter_core.app import create_adapter_router

            app = FastAPI()
            app.add_middleware(CORSMiddleware, ...)
            router = create_adapter_router(handlers={...})
            app.include_router(router)
    """

    # Ensure a StreamHandler exists on the root logger so handler logs reach
    # stdout/stderr (and therefore Docker logs / CloudWatch). basicConfig is a
    # no-op if the root logger already has handlers, so adapters that configure
    # their own logging are unaffected.
    logging.basicConfig(
        level=logging.INFO,
        format="%(asctime)s - %(name)s - %(levelname)s - %(message)s",
    )

    @asynccontextmanager
    async def lifespan(app: FastAPI):
        # Startup: run callbacks in order, let exceptions propagate (fatal)
        for callback in on_startup or []:
            logger.info("Running startup callback: %s", callback.__name__)
            if asyncio.iscoroutinefunction(callback):
                await callback()
            else:
                callback()

        yield

        # Shutdown: run callbacks, catch and log exceptions (non-fatal)
        for callback in on_shutdown or []:
            try:
                logger.info("Running shutdown callback: %s", callback.__name__)
                if asyncio.iscoroutinefunction(callback):
                    await callback()
                else:
                    callback()
            except Exception as exc:
                logger.error(
                    "Shutdown callback %s failed: %s",
                    callback.__name__,
                    exc,
                )

    app = FastAPI(title=title, lifespan=lifespan, **kwargs)

    router = create_adapter_router(handlers=handlers, settings=settings)
    app.include_router(router)

    logger.info(
        "ENTR Adapter app created with %d handler(s): %s",
        len(handlers),
        list(handlers.keys()),
    )

    return app
