from collections.abc import AsyncIterator
from contextlib import asynccontextmanager

from fastapi import FastAPI

from app.core.config import get_settings
from app.core.cors import UPLOAD_PATH, ScopedCORS
from app.core.errors import register_error_handlers
from app.core.upload_guard import UploadSizeGuard
from app.db.engine import dispose_engine, get_engine
from app.health.router import router as health_router
from app.imports.router import router as imports_router
from app.integrations.router import router as integrations_router
from app.jobs.loader import load_job_types
from app.jobs.router import router as jobs_router
from app.keywords.router import keyword_tags_router, settings_router, tags_router
from app.keywords.router import lists_router as keyword_lists_router
from app.keywords.router import router as keywords_router
from app.me.router import router as me_router
from app.sites.router import router as sites_router


@asynccontextmanager
async def lifespan(_: FastAPI) -> AsyncIterator[None]:
    get_engine()
    yield
    await dispose_engine()


def create_app() -> FastAPI:
    # Les types de jobs métier se déclarent à l'import de leur module (socle §2.2) et
    # `service.enqueue` refuse un type absent du registre : sans cet appel, tout POST qui
    # enfile un job lèverait. Même raison, même endroit unique que pour le worker.
    load_job_types()
    app = FastAPI(title="seoinsight api", docs_url="/docs", redoc_url=None, lifespan=lifespan)
    register_error_handlers(app)
    app.include_router(health_router)
    app.include_router(me_router)
    app.include_router(sites_router)
    app.include_router(jobs_router)
    app.include_router(imports_router)
    app.include_router(integrations_router)
    app.include_router(keywords_router)
    app.include_router(keyword_lists_router)
    app.include_router(tags_router)
    app.include_router(keyword_tags_router)
    app.include_router(settings_router)
    # Deux middlewares, sur le seul `POST /imports/files` (décision n°8) — c'est l'unique
    # endpoint que le navigateur appelle directement. `add_middleware` empile en dernier-arrivé
    # premier-servi : le garde de taille est **intérieur**, le CORS **extérieur**, pour que le
    # 413 et le 401 émis par le garde portent eux aussi leurs en-têtes CORS — sans quoi le
    # navigateur ne montrerait qu'une erreur réseau opaque au lieu du message.
    #
    # Le garde plafonne le corps **avant** routage, parsing multipart et authentification :
    # FastAPI lit la requête avant d'appeler l'endpoint, donc aucun contrôle écrit dans le
    # handler ne peut empêcher la réception (voir `app/core/upload_guard.py`).
    app.add_middleware(UploadSizeGuard, path=UPLOAD_PATH)
    # Tout le reste de l'api n'émet aucun en-tête CORS et reste donc inaccessible depuis une
    # page web — `test_imports_api.py` le vérifie en négatif sur trois autres chemins.
    app.add_middleware(ScopedCORS, path=UPLOAD_PATH, allow_origin=get_settings().auth_base_url)
    return app


app = create_app()
