"""Route tests for the SSR content JSON API (api/content_routes.py).

The point of these is the latency fix: a derived-store cache HIT must be O(1) —
it must NOT load the ~45-year archive. So the weather layer is faked with call
counters, and the core assertion is that a second identical request loads the
history zero more times than the first. ETag/304 revalidation and the 404/payload
shapes are covered alongside, against a fresh per-test store.
"""
import pytest
from fastapi.testclient import TestClient

from web import app as appmod
from data import climate
from data import cities

BASE = "/thermograph/api/v2/content"

CITY = {
    "slug": "testville",
    "name": "Testville",
    "admin1": "Washington",
    "country": "United States",
    "country_code": "US",
    "lat": 47.6062,
    "lon": -122.3321,
    "population": 100000,
}


@pytest.fixture
def counts():
    return {"load_cached_history": 0, "get_history": 0}


@pytest.fixture
def client(monkeypatch, history, recent, counts, tmp_store):
    """TestClient with the weather + city layer faked and the history loaders
    wrapped in counters. tmp_store gives each test a fresh derived store so the
    miss->hit sequence is deterministic."""
    def load_cached_history(cell):
        counts["load_cached_history"] += 1
        return history.clone()

    def get_history(cell):
        counts["get_history"] += 1
        return history.clone(), {"cached": True, "cache_age_days": 3}

    monkeypatch.setattr(climate, "load_cached_history", load_cached_history)
    monkeypatch.setattr(climate, "get_history", get_history)
    monkeypatch.setattr(climate, "get_recent_forecast", lambda cell: recent.clone())
    # Stable token: content_token() -> payloads -> climate.history_max_date.
    monkeypatch.setattr(climate, "history_max_date", lambda cell_id: "2026-07-01")
    monkeypatch.setattr(climate, "reverse_geocode", lambda lat, lon: "Testville, Washington")
    # Only "testville" is a known city; anything else -> 404.
    monkeypatch.setattr(cities, "get", lambda slug: CITY if slug == "testville" else None)
    return TestClient(appmod.app)


# --- the regression guard: a cache hit loads no history ----------------------

def test_cache_hit_does_not_load_history(client, counts):
    """First request misses -> builds -> loads history exactly once. A second
    identical request is served from the derived store and loads history ZERO
    more times. This is the whole point of the fix."""
    r1 = client.get(f"{BASE}/city/testville")
    assert r1.status_code == 200
    assert counts["load_cached_history"] == 1
    assert counts["get_history"] == 0  # cached load was non-empty, no live fetch

    r2 = client.get(f"{BASE}/city/testville")
    assert r2.status_code == 200
    assert r2.content == r1.content            # replayed from the store
    assert counts["load_cached_history"] == 1  # unchanged: no history load on the hit
    assert counts["get_history"] == 0


def test_304_revalidation_loads_no_history(client, counts):
    """If-None-Match on a stored payload -> 304 with no history load."""
    r1 = client.get(f"{BASE}/city/testville")
    assert r1.status_code == 200
    etag = r1.headers["etag"]
    before = counts["load_cached_history"]

    r304 = client.get(f"{BASE}/city/testville", headers={"If-None-Match": etag})
    assert r304.status_code == 304
    assert r304.headers["etag"] == etag
    assert counts["load_cached_history"] == before  # 304 loaded nothing


def test_month_and_records_hits_skip_history_load(client, counts):
    """Same O(1)-hit guarantee for the month and records handlers."""
    for path in ("month/july", "records"):
        counts["load_cached_history"] = 0
        assert client.get(f"{BASE}/city/testville/{path}").status_code == 200
        assert counts["load_cached_history"] == 1
        assert client.get(f"{BASE}/city/testville/{path}").status_code == 200
        assert counts["load_cached_history"] == 1  # second call is a store hit


# --- payload shapes ----------------------------------------------------------

def test_city_payload_shape(client):
    body = client.get(f"{BASE}/city/testville").json()
    assert body["city"]["slug"] == "testville"
    assert body["canonical_path"] == "/climate/testville"
    assert len(body["months"]) == 12
    assert body["today_vs_normal"] is not None  # recent forecast folded in


def test_month_payload_shape(client):
    body = client.get(f"{BASE}/city/testville/month/july").json()
    assert body["month_slug"] == "july"
    assert body["canonical_path"] == "/climate/testville/july"


def test_records_payload_shape(client):
    body = client.get(f"{BASE}/city/testville/records").json()
    assert body["canonical_path"] == "/climate/testville/records"


# --- https origin (jsonld url) -----------------------------------------------

def test_public_host_jsonld_url_is_https(client):
    """The public site is HTTPS-only behind Caddy, which proxies plain HTTP, so
    x-forwarded-proto reads "http". A request on the configured public host
    (THERMOGRAPH_BASE_URL defaults to https://thermograph.org) must still fold an
    https:// origin into the payload's jsonld url; otherwise the frontend renders
    an http:// canonical/JSON-LD that 308-redirects."""
    import json as _json
    hdr = {"host": "thermograph.org", "x-forwarded-proto": "http"}
    for path in ("city/testville", "city/testville/records"):
        s = _json.dumps(client.get(f"{BASE}/{path}", headers=hdr).json())
        assert "https://thermograph.org/thermograph/climate/testville" in s
        assert "http://thermograph.org" not in s


def test_non_public_host_jsonld_keeps_forwarded_scheme(client):
    hdr = {"host": "192.168.1.10:8000", "x-forwarded-proto": "http"}
    body = client.get(f"{BASE}/city/testville", headers=hdr).json()
    assert body["jsonld"]["@graph"][0]["url"].startswith("http://192.168.1.10:8000/")


# --- 404s --------------------------------------------------------------------

def test_unknown_slug_is_404(client, counts):
    for path in ("city/nope", "city/nope/month/july", "city/nope/records"):
        assert client.get(f"{BASE}/{path}").status_code == 404
    assert counts["load_cached_history"] == 0  # never reached the archive


def test_unknown_month_is_404(client):
    assert client.get(f"{BASE}/city/testville/month/smarch").status_code == 404
