from contextlib import asynccontextmanager
from typing import AsyncIterator

from fastapi import FastAPI, Request
from fastapi.exceptions import RequestValidationError
from fastapi.middleware.cors import CORSMiddleware
from fastapi.responses import JSONResponse
from fastapi.staticfiles import StaticFiles
from starlette.exceptions import HTTPException as StarletteHTTPException

from app.api.v1.router import api_router
from app.core.config import settings
from app.core.database import SessionFactory
from app.core.exceptions import AppError
from app.core.logging import get_logger, setup_logging
from app.core.response import error, success
from app.services import AuthService, SettingsService

logger = get_logger("app.main")


@asynccontextmanager
async def lifespan(_: FastAPI) -> AsyncIterator[None]:
    setup_logging()
    settings.upload_dir.mkdir(parents=True, exist_ok=True)
    for folder in ("products", "receipts", "cards", "temp"):
        (settings.upload_dir / folder).mkdir(parents=True, exist_ok=True)

    async with SessionFactory() as session:
        try:
            await SettingsService(session).ensure_defaults()
            await AuthService(session).ensure_default_admin()
            await session.commit()
        except Exception as exc:  # noqa: BLE001 - startup must not crash on a cold database
            await session.rollback()
            logger.error("startup bootstrap skipped: %s", exc)

    logger.info("%s started (%s)", settings.app_name, settings.app_env)
    yield


app = FastAPI(
    title=settings.app_name,
    version="1.0.0",
    docs_url="/docs",
    openapi_url="/openapi.json",
    lifespan=lifespan,
)

app.add_middleware(
    CORSMiddleware,
    allow_origins=settings.cors_origin_list,
    allow_credentials=True,
    allow_methods=["*"],
    allow_headers=["*"],
)


@app.exception_handler(AppError)
async def app_error_handler(_: Request, exc: AppError) -> JSONResponse:
    return JSONResponse(status_code=exc.status_code, content=error(exc.message, exc.errors))


@app.exception_handler(RequestValidationError)
async def validation_error_handler(_: Request, exc: RequestValidationError) -> JSONResponse:
    details = [f"{'.'.join(str(part) for part in err['loc'][1:])}: {err['msg']}" for err in exc.errors()]
    return JSONResponse(status_code=422, content=error("validation_error", details))


@app.exception_handler(StarletteHTTPException)
async def http_error_handler(_: Request, exc: StarletteHTTPException) -> JSONResponse:
    return JSONResponse(status_code=exc.status_code, content=error(str(exc.detail)))


@app.exception_handler(Exception)
async def unhandled_error_handler(request: Request, exc: Exception) -> JSONResponse:
    logger.exception("unhandled error on %s %s: %s", request.method, request.url.path, exc)
    return JSONResponse(status_code=500, content=error("internal_error"))


@app.get("/api/health", tags=["health"])
async def health() -> dict:
    return success({"status": "ok"}, "Application is running.")


app.include_router(api_router, prefix=settings.api_prefix)
app.mount(
    settings.upload_url_prefix,
    StaticFiles(directory=str(settings.upload_dir), check_dir=False),
    name="uploads",
)
