"""SSR content JSON API — the data a frontend SSR layer needs to render the
climate hub / per-city / month / records / homepage surfaces, without importing
backend code in-process. See backend/api/content_payloads.py for the builders;
this module is just FastAPI route wiring + the same derived-store/ETag caching
pattern web/app.py's own endpoints use.
"""
import hashlib
import os

from fastapi import APIRouter, HTTPException, Request, Response

from api import content_payloads as payloads
from api import sitemap as sitemap_mod
from api.payloads import history_token
from data import climate
from data import cities
from data import grid
from data import store

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

# Same convention as web/app.py and web/content.py: the deployment's base path,
# used only for the breadcrumb hrefs and jsonld URL this endpoint returns.
_BASE = os.environ.get("THERMOGRAPH_BASE", "/thermograph").strip("/")
BASE = f"/{_BASE}" if _BASE else ""


def _etag_for(kind: str, cell_id: str, key: str, token: str) -> str:
    h = hashlib.sha1(f"{kind}:{cell_id}:{key}:{token}".encode()).hexdigest()[:20]
    return f'W/"{h}"'


def _not_modified(request: Request, etag: str) -> bool:
    inm = request.headers.get("if-none-match")
    if not inm:
        return False
    tags = {t.strip() for t in inm.split(",")}
    return "*" in tags or etag in tags or etag.removeprefix("W/") in tags


def _json_response(body: bytes, etag: str) -> Response:
    return Response(content=body, media_type="application/json", headers={"ETag": etag})


def _cached(request: Request, kind: str, cell_id: str, key: str, token: str, build) -> Response:
    """Same shape as web/app.py's _cached_response: If-None-Match -> empty 304;
    a token-valid store row -> replay it; otherwise build, persist, serve.
    store.get_payload/put_payload both deal in already-encoded JSON bytes, not
    the payload dict -- put_payload returns the exact bytes it persisted so the
    caller can serve them without re-serializing."""
    etag = _etag_for(kind, cell_id, key, token)
    if _not_modified(request, etag):
        return Response(status_code=304, headers={"ETag": etag})
    body = store.get_payload(kind, cell_id, key, token)
    if body is not None:
        return _json_response(body, etag)
    payload = build()
    return _json_response(store.put_payload(kind, cell_id, key, token, payload), etag)


def _resolve_city(slug: str):
    """(city, cell, history) for a slug, or raise 404 (unknown) / 503 (warming) --
    mirrors web/content.py's _resolve_city."""
    city = cities.get(slug)
    if city is None:
        raise HTTPException(status_code=404, detail="Unknown city.")
    cell = grid.snap(city["lat"], city["lon"])
    history = climate.load_cached_history(cell)
    if history is None or history.is_empty():
        try:
            history, _ = climate.get_history(cell)
        except Exception:  # noqa: BLE001
            history = None
    if history is None or history.is_empty():
        raise HTTPException(status_code=503, detail="Climate data is warming up; please retry shortly.")
    return city, cell, history


def _origin(request: Request) -> str:
    proto = request.headers.get("x-forwarded-proto") or request.url.scheme
    host = request.headers.get("host") or request.url.netloc
    return f"{proto}://{host}"


@router.get("/content/hub")
def content_hub(request: Request):
    return payloads.hub_payload()


@router.get("/content/sitemap")
def content_sitemap(request: Request):
    return [{"path": p, "changefreq": cf, "priority": pr} for p, cf, pr in sitemap_mod.sitemap_entries()]


@router.get("/content/indexnow-key")
def content_indexnow_key(request: Request):
    import indexnow
    return {"key": indexnow.key()}


@router.get("/content/home")
def content_home(request: Request):
    return payloads.home_payload()


@router.get("/content/city/{slug}")
def content_city(request: Request, slug: str):
    city, cell, history = _resolve_city(slug)
    recent = None
    try:
        recent = climate.get_recent_forecast(cell)
    except Exception:  # noqa: BLE001 - today_vs_normal degrades to None
        pass
    token = history_token(history)
    origin = _origin(request)

    def build():
        return payloads.city_payload(origin, BASE, city, history, recent)

    # origin is folded into the cache key (not just slug) because the payload's
    # jsonld.url is origin-qualified -- without this, whichever origin's request
    # happens to populate the cache first would be served to every other origin
    # too (and the ETag, derived from this same key, wouldn't vary either, so a
    # different origin's client could get a false 304). A no-op in the default
    # single-canonical-origin prod topology; load-bearing once something is
    # genuinely served cross-origin (see Stage 5).
    return _cached(request, "content-city", cell["id"], f"{slug}:{origin}", token, build)


@router.get("/content/city/{slug}/month/{month}")
def content_city_month(request: Request, slug: str, month: str):
    if month not in payloads.MONTH_INDEX:
        raise HTTPException(status_code=404, detail="Unknown month.")
    city, cell, history = _resolve_city(slug)
    token = history_token(history)

    def build():
        return payloads.month_payload(BASE, city, history, payloads.MONTH_INDEX[month])

    return _cached(request, "content-month", cell["id"], f"{slug}:{month}", token, build)


@router.get("/content/city/{slug}/records")
def content_city_records(request: Request, slug: str):
    city, cell, history = _resolve_city(slug)
    token = history_token(history)
    origin = _origin(request)

    def build():
        return payloads.records_payload(origin, BASE, city, history)

    # See content_city's comment: origin folded into the cache key for the
    # same jsonld.url / ETag-correctness reason.
    return _cached(request, "content-records", cell["id"], f"{slug}:{origin}", token, build)
