from contextlib import asynccontextmanager

from pathlib import Path

from fastapi import Depends, FastAPI, Request
from fastapi.middleware.cors import CORSMiddleware
from fastapi.openapi.utils import get_openapi
from fastapi.responses import JSONResponse
from fastapi.staticfiles import StaticFiles

from app.application.exceptions import ApplicationError
from app.application.services.recaptcha_service import verify_recaptcha
from app.core.config import settings
from app.core.database import close_database_connection, connect_to_database

from app.presentation.api.v1 import (
    ai_agent,
    auth,
    chart_of_accounts,
    companies,
    inventory,
    purchase,
    reports,
    sales,
    settings as settings_api,
    users_roles,
    vouchers,
)
from app.presentation.auth_dependencies import get_current_user

PUBLIC_OPERATIONS = {
    ("get", "/health"),
    ("post", f"{settings.API_V1_PREFIX}/auth/login"),
    ("post", f"{settings.API_V1_PREFIX}/auth/refresh"),
    ("post", f"{settings.API_V1_PREFIX}/user-registrations"),
    ("get", f"{settings.API_V1_PREFIX}/settings/branding"),
}


@asynccontextmanager
async def lifespan(_: FastAPI):
    try:
        await connect_to_database()
    except Exception as exc:
        print(f"WARNING: MySQL startup connection failed: {exc}")
    yield
    await close_database_connection()


app = FastAPI(
    title=settings.APP_TITLE,
    version=settings.APP_VERSION,
    lifespan=lifespan,
)

app.add_middleware(
    CORSMiddleware,
    allow_origins=settings.cors_origins,
    allow_credentials=True,
    allow_methods=["*"],
    allow_headers=["*"],
    expose_headers=["*"],
)

_RECAPTCHA_SKIP_PREFIXES = (
    "/health",
    "/static",
    "/docs",
    "/redoc",
    "/openapi.json",
)
_RECAPTCHA_SKIP_PATHS = {
    f"{settings.API_V1_PREFIX}/auth/refresh",
}


def _client_ip(request: Request) -> str | None:
    forwarded = (request.headers.get("x-forwarded-for") or "").split(",")[0].strip()
    if forwarded:
        return forwarded
    return request.client.host if request.client else None


@app.middleware("http")
async def recaptcha_middleware(request: Request, call_next):
    method = request.method.upper()
    path = request.url.path
    if (
        not settings.RECAPTCHA_ENABLED
        or not (settings.RECAPTCHA_SECRET_KEY or "").strip()
        or method in {"GET", "HEAD", "OPTIONS"}
        or path in _RECAPTCHA_SKIP_PATHS
        or any(path == prefix or path.startswith(f"{prefix}/") for prefix in _RECAPTCHA_SKIP_PREFIXES)
    ):
        return await call_next(request)

    token = request.headers.get("X-Recaptcha-Token") or request.query_params.get(
        "recaptcha_token"
    )
    action = request.headers.get("X-Recaptcha-Action")
    ok, message = await verify_recaptcha(token, action, _client_ip(request))
    if not ok:
        return JSONResponse(
            status_code=400,
            content={"detail": message, "success": False},
        )
    return await call_next(request)

static_dir = Path(__file__).resolve().parent.parent / "static"
if static_dir.is_dir():
    app.mount("/static", StaticFiles(directory=static_dir), name="static")


def custom_openapi():
    if app.openapi_schema:
        return app.openapi_schema

    openapi_schema = get_openapi(
        title=settings.APP_TITLE,
        version=settings.APP_VERSION,
        routes=app.routes,
    )
    openapi_schema.setdefault("components", {}).setdefault("securitySchemes", {})["BearerAuth"] = {
        "type": "http",
        "scheme": "bearer",
        "bearerFormat": "JWT",
    }

    for path, path_item in openapi_schema.get("paths", {}).items():
        for method, operation in path_item.items():
            if not isinstance(operation, dict):
                continue
            if (method.lower(), path) in PUBLIC_OPERATIONS:
                continue
            operation["security"] = [{"BearerAuth": []}]

    app.openapi_schema = openapi_schema
    return app.openapi_schema


app.openapi = custom_openapi


@app.exception_handler(ApplicationError)
async def application_error_handler(_: Request, exc: ApplicationError):
    return JSONResponse(
        status_code=exc.status_code,
        content={"detail": exc.message, "success": False},
    )


@app.exception_handler(RuntimeError)
async def runtime_error_handler(_: Request, exc: RuntimeError):
    message = str(exc)
    status = 503 if "MySQL" in message or "Database" in message else 500
    return JSONResponse(
        status_code=status,
        content={"detail": message, "success": False},
    )


@app.get("/health")
async def health_check():
    return {"status": "healthy", "service": settings.APP_TITLE, "version": settings.APP_VERSION}


protected = [Depends(get_current_user)]

app.include_router(auth.router, prefix=settings.API_V1_PREFIX)
app.include_router(companies.router, prefix=settings.API_V1_PREFIX, dependencies=protected)
app.include_router(chart_of_accounts.router, prefix=settings.API_V1_PREFIX, dependencies=protected)
app.include_router(vouchers.router, prefix=settings.API_V1_PREFIX, dependencies=protected)
app.include_router(reports.router, prefix=settings.API_V1_PREFIX, dependencies=protected)
app.include_router(ai_agent.router, prefix=settings.API_V1_PREFIX, dependencies=protected)
app.include_router(inventory.router, prefix=settings.API_V1_PREFIX, dependencies=protected)
app.include_router(purchase.router, prefix=settings.API_V1_PREFIX, dependencies=protected)
app.include_router(sales.router, prefix=settings.API_V1_PREFIX, dependencies=protected)
app.include_router(users_roles.router, prefix=settings.API_V1_PREFIX, dependencies=protected)
app.include_router(settings_api.public_router, prefix=settings.API_V1_PREFIX)
app.include_router(settings_api.router, prefix=settings.API_V1_PREFIX, dependencies=protected)
