import os
import secrets
from pathlib import Path

from fastapi import Depends, FastAPI, Request, Response, status
from fastapi.staticfiles import StaticFiles
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 .routers import autoresponders as autoresponders_router
from .routers import jmap_router
from .routers import contacts as contacts_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,
)
_static_dir = Path(__file__).resolve().parent / "static"
if _static_dir.is_dir():
    app.mount("/panel/assets", StaticFiles(directory=str(_static_dir)), name="panel-assets")

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(autoresponders_router.router)
app.include_router(jmap_router.router)
app.include_router(contacts_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
    try:
        from .db import SessionLocal
        from .mailbox_ops import ensure_primary_hostname_domain

        db = SessionLocal()
        try:
            ensure_primary_hostname_domain(db)
        finally:
            db.close()
    except Exception:
        pass


@app.middleware("http")
async def add_security_headers(request: Request, call_next):
    """Add security hardening headers to every response (panel CSP uses per-request nonces)."""
    csp_nonce = secrets.token_urlsafe(16)
    request.state.csp_nonce = csp_nonce
    response: Response = await call_next(request)
    response.headers["X-Content-Type-Options"] = "nosniff"
    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"
    if request.url.path.startswith("/panel"):
        response.headers["X-Frame-Options"] = "SAMEORIGIN"
        # Nonce-based CSP: no 'unsafe-inline'. External assets under /panel/assets/.
        response.headers["Content-Security-Policy"] = (
            "default-src 'self'; "
            f"script-src 'self' 'nonce-{csp_nonce}'; "
            f"style-src 'self' 'nonce-{csp_nonce}' https://fonts.googleapis.com; "
            "font-src 'self' https://fonts.gstatic.com data:; "
            "img-src 'self' data:; "
            "connect-src 'self'; "
            "frame-ancestors 'self'; "
            "base-uri 'self'; "
            "form-action 'self'; "
            "object-src 'none'"
        )
    else:
        response.headers["X-Frame-Options"] = "DENY"
        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
