import asyncio
from collections.abc import AsyncIterator
from contextlib import asynccontextmanager, suppress

from fastapi import FastAPI
from fastapi.middleware.cors import CORSMiddleware
from sqlalchemy.ext.asyncio import async_sessionmaker

from app import health
from app.access import router as access_router
from app.access.sessions import SessionService
from app.core import db
from app.core.alerts import Alerts
from app.core.config import Settings, get_settings
from app.core.errors import register_error_handlers
from app.core.logging import configure_logging
from app.integrations.installed import build_registry
from app.integrations.registry import AdapterRegistry
from app.livekit import webhooks
from app.livekit.adapter import LiveKitAdapter
from app.meetings import controls_router, hands_router, room_router, share_router
from app.meetings import router as meetings_router
from app.meetings.jobs import (
    end_unmanaged_sessions_forever,
    forget_gone_waiting_forever,
    forget_unjoined_forever,
)
from app.revocation.recheck import recheck_forever, recheck_once


def create_app(
    settings: Settings | None = None,
    livekit_adapter: LiveKitAdapter | None = None,
    products: AdapterRegistry | None = None,
) -> FastAPI:
    """Build the app. Tests pass their own settings, product adapters and a fake LiveKit."""
    settings = settings or get_settings()
    configure_logging(settings.log_level)

    @asynccontextmanager
    async def lifespan(app: FastAPI) -> AsyncIterator[None]:
        engine = db.create_engine(settings.database_url.get_secret_value())
        app.state.db_engine = engine
        app.state.db_sessions = async_sessionmaker(engine, expire_on_commit=False)
        app.state.livekit = livekit_adapter or LiveKitAdapter(
            settings.livekit_api_url,
            settings.livekit_api_key,
            settings.livekit_api_secret.get_secret_value(),
        )
        jobs = [
            asyncio.create_task(forget_unjoined_forever(app.state.db_sessions)),
            asyncio.create_task(
                forget_gone_waiting_forever(app.state.db_sessions, app.state.livekit)
            ),
            asyncio.create_task(
                end_unmanaged_sessions_forever(app.state.db_sessions, app.state.livekit)
            ),
            asyncio.create_task(
                recheck_forever(
                    lambda: recheck_once(
                        app.state.db_sessions,
                        app.state.livekit,
                        app.state.products,
                        app.state.sessions,
                        app.state.alerts,
                    ),
                    settings.recheck_interval_seconds,
                )
            ),
        ]
        try:
            yield
        finally:
            for job in jobs:
                job.cancel()
            for job in jobs:
                with suppress(asyncio.CancelledError):
                    await job
            await app.state.products.aclose()
            await app.state.livekit.aclose()
            await engine.dispose()

    app = FastAPI(title="Meetings module", lifespan=lifespan)
    if settings.cors_allowed_origins:
        # Tokens travel in the Authorization header, never in cookies: no credentials mode.
        app.add_middleware(
            CORSMiddleware,
            allow_origins=settings.cors_allowed_origins,
            allow_methods=["GET", "POST", "PUT", "PATCH", "DELETE"],
            allow_headers=["Authorization", "Content-Type"],
            allow_credentials=False,
            max_age=600,
        )
    app.state.settings = settings
    app.state.alerts = Alerts()
    app.state.products = products or build_registry(settings.enabled_products)
    app.state.sessions = SessionService(
        settings.session_secret.get_secret_value(), settings.session_ttl_seconds
    )
    register_error_handlers(app)
    app.include_router(health.router)
    app.include_router(access_router.router)
    app.include_router(room_router.router)
    app.include_router(share_router.router)
    app.include_router(hands_router.router)
    app.include_router(controls_router.router)
    app.include_router(meetings_router.router)
    app.include_router(webhooks.router)
    return app
