"""Health endpoint (task 5.6, spec reminders §7.7).

Three checks, because there are three distinct ways this system dies quietly:

* **database** — loud and obvious, everything fails at once.
* **worker** — completely silent. The site keeps serving, RSVPs keep landing, and nobody
  discovers reminders stopped until the wedding. This is the check that matters.
* **provider** — configuration rather than liveness: an unset or placeholder API key means
  jobs will queue, fail three times, and land in the message log. Worth surfacing before
  the first wave rather than after it.

Deliberately unauthenticated and deliberately thin: it is what the container healthcheck
and any uptime monitor poll, so it must not leak counts, names, or addresses.
"""

from typing import Any

from fastapi import APIRouter, Depends, Response, status
from sqlalchemy import text
from sqlalchemy.ext.asyncio import AsyncSession

from app.config import get_settings
from app.db import get_session
from app.services import heartbeat

router = APIRouter(tags=["health"])

#: Placeholder values that are fine in dev and fatal in prod. `assert_production_ready()`
#: refuses to boot on these; here they only degrade the report, so a dev stack is honest
#: about being unable to send rather than claiming health.
_PLACEHOLDER_PREFIXES = ("your-", "changeme", "dev-only", "test-")


@router.get("/health")
async def health(
    response: Response, session: AsyncSession = Depends(get_session)
) -> dict[str, Any]:
    settings = get_settings()
    checks: dict[str, Any] = {}

    try:
        await session.execute(text("SELECT 1"))
        checks["database"] = {"status": "ok"}
    except Exception as exc:
        checks["database"] = {"status": "error", "detail": type(exc).__name__}
        # Nothing else can be read without the database; report and stop.
        response.status_code = status.HTTP_503_SERVICE_UNAVAILABLE
        return {
            "status": "down",
            "environment": settings.environment,
            "dry_run": settings.dry_run,
            "checks": checks,
        }

    expected = {
        heartbeat.PLANNER: settings.planner_interval_seconds,
        heartbeat.SENDER: settings.sender_poll_seconds,
    }
    try:
        tasks = await heartbeat.read_liveness(session, expected)
        checks["worker"] = {
            "status": "ok" if all(t.is_alive for t in tasks) else "stale",
            "tasks": {
                t.name: {
                    "alive": t.is_alive,
                    "last_beat": t.beat_at.isoformat() if t.beat_at else None,
                    "age_seconds": round(t.age_seconds) if t.age_seconds is not None else None,
                    "last_error": t.last_error,
                }
                for t in tasks
            },
        }
    except Exception as exc:  # the table may predate a migration on a half-deployed host
        checks["worker"] = {"status": "unknown", "detail": type(exc).__name__}

    key = settings.email_provider_api_key or ""
    if settings.dry_run:
        # DryRunProvider never calls out, so a missing key is not a fault here.
        checks["provider"] = {"status": "ok", "mode": "dry_run"}
    elif not key or key.lower().startswith(_PLACEHOLDER_PREFIXES):
        checks["provider"] = {"status": "unconfigured", "mode": "live"}
    else:
        checks["provider"] = {"status": "ok", "mode": "live"}

    degraded = any(c.get("status") not in {"ok"} for c in checks.values())
    if degraded:
        # 200 with status=degraded, not 503: the site is serving and a load balancer must
        # not pull it out of rotation because the worker is restarting.
        response.status_code = status.HTTP_200_OK

    return {
        "status": "degraded" if degraded else "ok",
        "environment": settings.environment,
        "dry_run": settings.dry_run,
        "checks": checks,
    }
