"""Tests for the homepage's cache-only "unusual right now" precompute
(api/homepage.py) -- the rendered-page assertions (repo-split Stage 4) moved
to frontend_ssr/tests/test_homepage.py, since content.py's Jinja rendering
lives there now, not in this process.

The precompute must never touch the network — a sweep over ~1000 cities that
fetched would burst the archive quota — so the tests here assert that the
fetching climate helpers are not called at all.
"""
import datetime
import json
import time

from api import homepage
from data import climate
from data import store


# --- the precompute ----------------------------------------------------------
def test_build_never_fetches_upstream(monkeypatch):
    """The sweep uses the cache-only readers, never the fetching ones."""
    def boom(*a, **k):
        raise AssertionError("the homepage sweep must not fetch upstream")

    monkeypatch.setattr(climate, "get_history", boom)
    monkeypatch.setattr(climate, "get_recent_forecast", boom)
    feed = homepage.build(limit=20)
    assert set(feed) >= {"generated_at", "date", "considered", "picks", "ranked"}


def test_build_grades_cached_cities(monkeypatch, history, recent):
    monkeypatch.setattr(climate, "load_cached_history", lambda cell: history.clone())
    monkeypatch.setattr(climate, "load_cached_recent_forecast", lambda cell: recent.clone())
    feed = homepage.build(limit=5)
    assert feed["considered"] == 5
    for row in feed["ranked"]:
        assert row["percentile"] is not None
        assert row["cls"] and not row["cls"].startswith("--")   # token name, no prefix
        assert row["tail"] in ("warm", "cold")
        # The card needs the reading itself and something to compare it against —
        # a percentile alone says nothing about what the weather actually is.
        assert row["value"] is not None
        assert row["normal"] is not None, "the ±7-day median must reach the card"
        assert row["metric_label"] in ("High temp", "Low temp")
        assert row["pct_label"].endswith(("st", "nd", "rd", "th"))
        # "Tehran, Iran", not "Dar es Salaam, Dar es Salaam Region, Tanzania".
        assert row["display"].count(",") <= 1


def test_percentile_label_never_reads_100th_or_0th():
    """An empirical percentile can round to 100 (or 0). "100th percentile" reads
    as a bug rather than "as hot as it has ever been", so the label floors into
    the real 1-99 range."""
    assert homepage._pct_label(99.6) == "99th"
    assert homepage._pct_label(100.0) == "99th"
    assert homepage._pct_label(0.4) == "1st"
    assert homepage._pct_label(0.0) == "1st"
    # The ordinary teens/ones rules still apply in between.
    assert homepage._pct_label(11) == "11th"
    assert homepage._pct_label(22) == "22nd"
    assert homepage._pct_label(3) == "3rd"


def test_near_record_is_disambiguated_by_tail():
    """"Near Record" is the band label at BOTH ends of the scale. Unqualified, a
    cold extreme and a hot one render identically and colour becomes the only
    signal — which the design rules forbid."""
    assert homepage._grade_label("Near Record", "warm") == "Near Record hot"
    assert homepage._grade_label("Near Record", "cold") == "Near Record cold"
    # Tiers that already read directionally are left alone.
    assert homepage._grade_label("Very High", "warm") == "Very High"
    assert homepage._grade_label("Low", "cold") == "Low"


def test_missing_cell_cache_is_skipped_not_fatal(monkeypatch):
    monkeypatch.setattr(climate, "load_cached_history", lambda cell: None)
    monkeypatch.setattr(climate, "load_cached_recent_forecast", lambda cell: None)
    feed = homepage.build(limit=10)
    assert feed["considered"] == 0 and feed["ranked"] == []


def test_load_survives_missing_and_corrupt_file(monkeypatch, tmp_path):
    monkeypatch.setattr(homepage, "FEED_PATH", str(tmp_path / "nope.json"))
    assert homepage.load() is None

    bad = tmp_path / "homepage.json"
    bad.write_text("{not json")
    monkeypatch.setattr(homepage, "FEED_PATH", str(bad))
    assert homepage.load() is None

    bad.write_text('{"unexpected": true}')
    assert homepage.load() is None


def test_refresh_writes_atomically(monkeypatch, tmp_path):
    monkeypatch.setattr(homepage, "FEED_PATH", str(tmp_path / "homepage.json"))
    monkeypatch.setattr(climate, "load_cached_history", lambda cell: None)
    homepage.refresh(limit=3, fetch=0)
    written = json.loads((tmp_path / "homepage.json").read_text())
    assert written["ranked"] == []
    # No temp files left behind.
    assert [p.name for p in tmp_path.iterdir()] == ["homepage.json"]


def test_refresh_persists_via_store_on_postgres_not_the_file(monkeypatch, tmp_path):
    """On Postgres every web replica has its own disk, so the feed must go through
    store.py's shared table instead — and must NOT also write the (per-replica,
    stale-to-everyone-else) local file."""
    monkeypatch.setattr(homepage, "FEED_PATH", str(tmp_path / "homepage.json"))
    monkeypatch.setattr(climate, "load_cached_history", lambda cell: None)
    monkeypatch.setattr(store, "IS_POSTGRES", True)
    calls = []
    monkeypatch.setattr(
        store, "put_payload",
        lambda kind, cell_id, key, token, payload, **kw: calls.append(
            (kind, cell_id, key, token, payload)))
    feed = homepage.refresh(limit=3, fetch=0)
    assert len(calls) == 1
    kind, cell_id, key, token, payload = calls[0]
    assert (kind, cell_id, key) == ("homepage", "_global", "feed")
    assert token and payload is feed
    assert not (tmp_path / "homepage.json").exists()


def test_load_reads_via_store_on_postgres(monkeypatch):
    monkeypatch.setattr(store, "IS_POSTGRES", True)
    sentinel = {"ranked": [], "date": "2026-01-01"}
    monkeypatch.setattr(store, "get_json", lambda *a, **k: sentinel)
    assert homepage.load() is sentinel


def test_load_on_postgres_handles_absent_or_malformed(monkeypatch):
    monkeypatch.setattr(store, "IS_POSTGRES", True)
    monkeypatch.setattr(store, "get_json", lambda *a, **k: None)
    assert homepage.load() is None
    monkeypatch.setattr(store, "get_json", lambda *a, **k: {"no_ranked_key": True})
    assert homepage.load() is None


def test_refresh_can_be_strictly_cache_only(monkeypatch, tmp_path):
    """fetch=0 must spend no upstream request at all — that is the contract the
    test suite and any offline rebuild rely on."""
    monkeypatch.setattr(homepage, "FEED_PATH", str(tmp_path / "homepage.json"))

    def boom(*a, **k):
        raise AssertionError("refresh(fetch=0) must not fetch")

    monkeypatch.setattr(climate, "get_recent_forecast", boom)
    monkeypatch.setattr(climate, "get_history", boom)
    feed = homepage.refresh(limit=3, fetch=0)
    assert feed["refreshed"] == 0


def test_stale_observations_are_not_ranked(monkeypatch, history, recent):
    """A reading older than a day is not "right now". The recent/forecast cache
    goes stale for any city nobody visits, and grading it anyway is what showed a
    days-old reading under the "Unusual right now" heading, disagreeing with the
    Day page for the same cell."""
    import polars as pl

    stale = recent.clone()
    old = datetime.date.today() - datetime.timedelta(days=5)
    stale = stale.with_columns(
        (pl.col("date") - pl.duration(days=(stale["date"].max() - old).days)).alias("date")
    )
    monkeypatch.setattr(climate, "load_cached_history", lambda cell: history.clone())
    monkeypatch.setattr(climate, "load_cached_recent_forecast", lambda cell: stale.clone())
    feed = homepage.build(limit=5)
    assert feed["ranked"] == [], "stale readings must not be presented as current"
    assert feed["considered"] == 0


def test_staleness(monkeypatch):
    today = datetime.date.today().isoformat()
    assert not homepage.is_stale({"date": today, "generated_at": time.time()})
    assert homepage.is_stale({"date": today, "generated_at": time.time() - 7 * 3600})
    assert homepage.is_stale({"date": "1999-01-01", "generated_at": time.time()})
