import os

from fastapi import Depends, FastAPI, Request, Response, status
from redis.exceptions import RedisError
from sqlalchemy import text
from sqlalchemy.exc import SQLAlchemyError

from .cache import get_cache
from .settings import get_settings
from .db import engine
from .routers import domains, accounts, aliases, redirects, reputation, admin, operations, stats
from .routers import topology_api, auth as auth_router
from .routers import mailbox_import as mailbox_import_router
from .security import require_admin

settings = get_settings()

# OpenAPI/docs stay off by default in production; enable with LIMRISTEM_MAIL_ENABLE_API_DOCS=yes.
_enable_api_docs = os.getenv("LIMRISTEM_MAIL_ENABLE_API_DOCS", "no").lower() in {"1", "true", "yes", "on"}
app = FastAPI(
    title="Limristem eMail API",
    version=settings.app_version,
    docs_url="/docs" if _enable_api_docs else None,
    redoc_url="/redoc" if _enable_api_docs else None,
    openapi_url="/openapi.json" if _enable_api_docs else None,
)
app.include_router(auth_router.router)
app.include_router(domains.router)
app.include_router(accounts.router)
app.include_router(aliases.router)
app.include_router(redirects.router)
app.include_router(reputation.router)
app.include_router(operations.router)
app.include_router(stats.router)
app.include_router(topology_api.router)
app.include_router(mailbox_import_router.router)
app.include_router(admin.router)


@app.on_event("startup")
def _start_mailbox_import_worker() -> None:
    """Start the background IMAP import worker when the API process boots."""
    try:
        from .mailbox_import import ensure_schema, ensure_worker_started

        ensure_schema()
        ensure_worker_started()
    except Exception:
        # Schema may be unavailable during install; worker starts on first job.
        pass


@app.middleware("http")
async def add_security_headers(request: Request, call_next):
    """Add security hardening headers to every response."""
    response: Response = await call_next(request)
    response.headers["X-Content-Type-Options"] = "nosniff"
    response.headers["X-Frame-Options"] = "DENY"
    response.headers["X-XSS-Protection"] = "0"
    response.headers["Referrer-Policy"] = "strict-origin-when-cross-origin"
    response.headers["Permissions-Policy"] = "geolocation=(), microphone=(), camera=()"
    response.headers["Cross-Origin-Opener-Policy"] = "same-origin"
    response.headers["Cross-Origin-Resource-Policy"] = "same-origin"
    # Panel routes already get a richer CSP from nginx; keep a strict default for API JSON.
    response.headers.setdefault(
        "Content-Security-Policy",
        "default-src 'none'; frame-ancestors 'none'; base-uri 'none'; form-action 'none'",
    )
    if settings.ssl_mode != "plain":
        response.headers["Strict-Transport-Security"] = "max-age=63072000; includeSubDomains; preload"
    return response


def collect_health() -> dict:
    checks = {
        "database": {"status": "ok"},
        "redis": {"status": "ok"},
    }
    overall = "ok"

    try:
        with engine.connect() as connection:
            connection.execute(text("SELECT 1"))
    except SQLAlchemyError as exc:
        checks["database"] = {"status": "error", "detail": exc.__class__.__name__}
        overall = "degraded"

    try:
        get_cache().ping()
    except RedisError as exc:
        checks["redis"] = {"status": "error", "detail": exc.__class__.__name__}
        overall = "degraded"

    return {"status": overall, "checks": checks}


@app.get("/health")
def health(response: Response):
    payload = collect_health()
    if payload["status"] != "ok":
        response.status_code = status.HTTP_503_SERVICE_UNAVAILABLE
    return payload


@app.get("/health/details")
def health_details(response: Response, _: str = Depends(require_admin)):
    payload = collect_health()
    payload["hostname"] = settings.hostname
    payload["mail_home"] = str(settings.mail_home)
    payload["dkim_keys_dir"] = str(settings.dkim_keys_dir)
    if payload["status"] != "ok":
        response.status_code = status.HTTP_503_SERVICE_UNAVAILABLE
    return payload
