import logging
import secrets
from contextlib import asynccontextmanager

from fastapi import FastAPI, Request
from fastapi.middleware.cors import CORSMiddleware
from fastapi.responses import JSONResponse

from app.config import ENV_FILE, settings
from app.db import connect, disconnect, get_db
from app.routers import (
    arxiv_auto, auth, biorxiv_auto, collected, lookups, medrxiv_auto, preprint_sources,
    preprints_auto, smtp, users,
)

log = logging.getLogger("uvicorn.error")


@asynccontextmanager
async def lifespan(_: FastAPI):
    log.info(
        "config: env_file=%s | mongo=%s db=%s | cors=%s | docs=%s",
        ENV_FILE if settings.env_file_loaded else "not found (using defaults)",
        settings.safe_mongo_uri,
        settings.mongo_db,
        settings.cors_origins,
        "on" if settings.docs_enabled else "off",
    )
    if settings.auth_required:
        log.info("auth: X-API-Key required on %s/*", settings.api_prefix)
    else:
        log.warning(
            "auth: NO API KEY SET — every %s route is open to anyone who can reach "
            "this address. Fine on localhost; set API_KEY before exposing it.",
            settings.api_prefix,
        )
    await connect()
    yield
    await disconnect()


app = FastAPI(
    title=settings.api_title,
    version="1.0.0",
    description=f"REST API over `{settings.mongo_db}`.",
    lifespan=lifespan,
    # Disabled in production: the schema maps every route and field.
    docs_url="/docs" if settings.docs_enabled else None,
    redoc_url="/redoc" if settings.docs_enabled else None,
    openapi_url="/openapi.json" if settings.docs_enabled else None,
)

app.add_middleware(
    CORSMiddleware,
    allow_origins=settings.cors_origin_list,
    allow_credentials="*" not in settings.cors_origin_list,
    allow_methods=["*"],
    allow_headers=["*"],
)

# The NAME of the header clients send. The secret VALUE lives in API_KEY (.env),
# not here.
API_KEY_HEADER = "X-API-Key"


@app.middleware("http")
async def require_api_key(request: Request, call_next):
    """Gate everything under the API prefix on a shared secret.

    Off when API_KEY is empty, so a localhost setup keeps working untouched.
    `/health` stays open either way, so a load balancer can probe it.
    """
    if settings.auth_required and request.url.path.startswith(settings.api_prefix):
        supplied = request.headers.get(API_KEY_HEADER, "")
        if not secrets.compare_digest(supplied, settings.api_key):
            return JSONResponse(
                status_code=401,
                content={"detail": f"A valid {API_KEY_HEADER} header is required."},
            )
    return await call_next(request)

app.include_router(users.router, prefix=settings.api_prefix)
app.include_router(auth.router, prefix=settings.api_prefix)
app.include_router(preprint_sources.router, prefix=settings.api_prefix)
app.include_router(collected.router, prefix=settings.api_prefix)
app.include_router(arxiv_auto.router, prefix=settings.api_prefix)
app.include_router(preprints_auto.router, prefix=settings.api_prefix)
app.include_router(biorxiv_auto.router, prefix=settings.api_prefix)
app.include_router(medrxiv_auto.router, prefix=settings.api_prefix)
app.include_router(lookups.router, prefix=settings.api_prefix)
app.include_router(smtp.router, prefix=settings.api_prefix)


@app.get("/health", tags=["meta"], summary="Liveness + Mongo reachability")
async def health():
    db = get_db()
    await db.client.admin.command("ping")
    return {
        "status": "ok",
        "database": settings.mongo_db,
        "users": await db["users"].estimated_document_count(),
    }


if __name__ == "__main__":
    import uvicorn

    uvicorn.run(
        "main:app",
        host=settings.api_host,
        port=settings.api_port,
        reload=settings.api_reload,
    )
