"""FastAPI application factory."""

from __future__ import annotations

import asyncio
from contextlib import asynccontextmanager, suppress
from datetime import UTC, datetime

import structlog
from fastapi import FastAPI, Request
from fastapi.responses import JSONResponse
from fastapi.staticfiles import StaticFiles

from hive_compute.config import get_settings
from hive_compute.coordinator.database import init_db
from hive_compute.coordinator.payouts import release_held_payouts
from hive_compute.coordinator.rate_limit import RateLimitMiddleware
from hive_compute.coordinator.routes_admin import router as admin_router
from hive_compute.coordinator.routes_billing import router as billing_router
from hive_compute.coordinator.routes_jobs import router as jobs_router
from hive_compute.coordinator.routes_manager import router as manager_router
from hive_compute.coordinator.routes_site import STATIC_ROOT
from hive_compute.coordinator.routes_site import router as site_router
from hive_compute.coordinator.routes_workers import router as workers_router
from hive_compute.coordinator.scheduler import reap_stale_jobs
from hive_compute.coordinator.security_headers import SecurityHeadersMiddleware
from hive_compute.coordinator.webhooks import retry_pending_webhooks

logger = structlog.get_logger()
_MAINTENANCE_STEPS = ("reap_stale_jobs", "retry_pending_webhooks", "release_held_payouts")


@asynccontextmanager
async def lifespan(app: FastAPI):
    _initialize_maintenance_state(app)
    init_db()
    task = asyncio.create_task(_reaper_loop(app))
    try:
        yield
    finally:
        task.cancel()
        with suppress(asyncio.CancelledError):
            await task


def create_app() -> FastAPI:
    _validate_public_download_config(get_settings())
    app = FastAPI(
        title="HiveCompute",
        description="Distributed transcription API powered by idle Apple Silicon Macs",
        version="0.1.0",
        lifespan=lifespan,
    )

    app.add_middleware(RateLimitMiddleware)
    app.add_middleware(SecurityHeadersMiddleware)
    app.include_router(jobs_router, prefix="/api/v1")
    app.include_router(billing_router, prefix="/api/v1")
    app.include_router(workers_router, prefix="/api/v1")
    app.include_router(admin_router, prefix="/api/v1")
    app.include_router(manager_router)
    app.include_router(site_router)
    app.mount("/static", StaticFiles(directory=STATIC_ROOT), name="static")

    @app.middleware("http")
    async def collect_http_metrics(request: Request, call_next):
        from hive_compute.coordinator.metrics import record_http_request

        try:
            response = await call_next(request)
        except Exception:
            record_http_request(request.method, 500)
            raise

        record_http_request(request.method, response.status_code)
        return response

    @app.get("/health")
    def health():
        payload = _health_payload(app)
        return JSONResponse(status_code=200 if payload["status"] == "ok" else 503, content=payload)

    @app.get("/metrics")
    def metrics():
        from starlette.responses import Response as StarletteResponse

        from hive_compute.coordinator.metrics import get_metrics_response

        body, content_type = get_metrics_response()
        return StarletteResponse(content=body, media_type=content_type)

    return app


def _validate_public_download_config(settings) -> None:
    try:
        app_download_source = settings.get_app_download_source(validate=True)
    except ValueError as exc:
        raise RuntimeError(str(exc)) from exc

    if settings.should_require_app_download_url() and not app_download_source:
        raise RuntimeError(
            "Configure HIVE_APP_DOWNLOAD_URL or HIVE_APP_DOWNLOAD_FILE for hosted "
            "deployments before advertising the public /download flow."
        )


def _initialize_maintenance_state(app: FastAPI) -> None:
    app.state.maintenance = {
        name: {"last_success_at": None, "last_error_at": None, "last_error": None}
        for name in _MAINTENANCE_STEPS
    }


def _mark_maintenance_success(app: FastAPI, step: str) -> None:
    state = app.state.maintenance[step]
    state["last_success_at"] = datetime.now(UTC).isoformat()
    state["last_error_at"] = None
    state["last_error"] = None


def _mark_maintenance_failure(app: FastAPI, step: str, exc: Exception) -> None:
    state = app.state.maintenance[step]
    state["last_error_at"] = datetime.now(UTC).isoformat()
    state["last_error"] = str(exc)[:200]


def _health_payload(app: FastAPI) -> dict:
    maintenance = getattr(app.state, "maintenance", {})
    degraded = any(item.get("last_error") for item in maintenance.values())
    return {
        "status": "degraded" if degraded else "ok",
        "maintenance": maintenance,
    }


async def _run_maintenance_step(app: FastAPI, step: str, func, *args) -> None:
    try:
        await asyncio.to_thread(func, *args)
    except Exception as exc:
        _mark_maintenance_failure(app, step, exc)
        logger.exception("Maintenance step failed", step=step)
    else:
        _mark_maintenance_success(app, step)


async def _reaper_loop(app: FastAPI):
    """Background task: requeue stale jobs, retry webhooks, release payouts.

    All operations are synchronous (SQLite + httpx) so they run in a thread
    pool to avoid blocking the async event loop.
    """
    settings = get_settings()
    while True:
        await asyncio.sleep(settings.reaper_interval_seconds)
        await _run_maintenance_step(app, "reap_stale_jobs", reap_stale_jobs, settings.stale_job_minutes)
        await _run_maintenance_step(app, "retry_pending_webhooks", retry_pending_webhooks)
        await _run_maintenance_step(app, "release_held_payouts", release_held_payouts)
