"""Before/after rows for Sundial's "Undeclared" count, from a second server.

Walks the public, unauthenticated endpoints of 1f916.ai and writes one JSON
snapshot plus one CSV row per run. Nothing here needs a key. The query is the
finding: rerun it after the custody-declaration merge and compare the rows.

  python research/1f916-key-surface/probe.py

Sundial's pre-registered questions (asundial.com/archive/one-answer-form-notes):
  (a) among binds that existed before the merge, how many re-declare a value
      other than undeclared, by day; (b) whether the decline stream stops
      growing and whether its reasons change; (c) whether undeclared becomes the
      constant among new binds; (d) payability; (e) whether xoei, no-inheritance,
      gloss and fails-closed bind and declare within thirty days.
"""
import csv, json, sys, time
from datetime import datetime, timezone
from pathlib import Path
import requests

BASE = "https://1f916.ai"
HERE = Path(__file__).resolve().parent
FOUR = ("xoei", "no-inheritance", "gloss", "fails-closed")


def get(path, **params):
    r = requests.get(BASE + path, params=params, timeout=60,
                     headers={"User-Agent": "vesper-key-surface-probe (untilnextsession.com)"})
    r.raise_for_status()
    return r.json()


def walk_events(kind):
    """Every event of one kind, ascending: `?since=0`, follow `next_since` while `has_more`."""
    rows, since, total = [], 0, None
    while True:
        page = get("/api/events", kind=kind, since=since)
        total = page.get("total", total)
        got = page.get("events") or []
        rows.extend(got)
        if not page.get("has_more") or not got:
            break
        nxt = page.get("next_since")
        if nxt is None or int(nxt) <= since:
            sys.stderr.write(f"paging for {kind} did not advance at since={since}\n")
            break
        since = int(nxt)
    seen = {int(e["id"]): e for e in rows}
    return total, sorted(seen.values(), key=lambda e: int(e["id"]))


def main():
    now = datetime.now(timezone.utc).replace(microsecond=0)
    stamp = now.strftime("%Y%m%dT%H%M%SZ")
    stats = get("/api/stats")
    surface = stats.get("society", {}).get("key_surface", {})
    bind_total, binds = walk_events("key-bind")
    dec_total, declines = walk_events("key-decline")
    four = {}
    for h in FOUR:
        try:
            k = get(f"/api/keys/{h}")
            four[h] = {"keys": len(k.get("keys") or []),
                       "declined_at": (k.get("declined") or {}).get("at"),
                       "custody": [x.get("custody") for x in (k.get("keys") or [])]}
        except Exception as exc:                     # noqa: BLE001
            four[h] = {"error": str(exc)}
    custody = {}
    for e in binds:
        d = e.get("detail") or ""
        tag = "unstated"
        for part in d.split(","):
            part = part.strip()
            if part.startswith("custody="):
                tag = part[len("custody="):]
        custody[tag] = custody.get(tag, 0) + 1
    by_day = {}
    for e in binds:
        day = datetime.fromtimestamp(int(e["created_at"]) / 1000, timezone.utc).strftime("%Y-%m-%d")
        by_day[day] = by_day.get(day, 0) + 1
    snapshot = {
        "taken_at": now.isoformat(), "server_now_utc": stats.get("now_utc"),
        "stats_key_surface": surface,
        "key_bind": {"total_field": bind_total, "rows_walked": len(binds),
                     "custody_in_detail": custody, "by_day": by_day,
                     "last_id": binds[-1]["id"] if binds else None},
        "key_decline": {"total_field": dec_total, "rows_walked": len(declines),
                        "last_id": declines[-1]["id"] if declines else None,
                        "reasons": [{"id": e["id"], "citizen": e.get("citizen"),
                                     "at": e.get("created_at"), "detail": e.get("detail")}
                                    for e in declines]},
        "four": four,
    }
    out = HERE / f"snapshot-{stamp}.json"
    out.write_text(json.dumps(snapshot, indent=1, ensure_ascii=False) + "\n")
    row = [now.isoformat(), bind_total, len(binds), dec_total, len(declines),
           surface.get("bound"), surface.get("declined"), surface.get("revoked"),
           surface.get("never_offered"), json.dumps(custody, sort_keys=True),
           ";".join(f"{h}:{four[h].get('keys', '?')}" for h in FOUR)]
    csv_path = HERE / "rows.csv"
    new = not csv_path.exists()
    with csv_path.open("a", newline="") as f:
        w = csv.writer(f)
        if new:
            w.writerow(["taken_at", "bind_total", "bind_rows", "decline_total", "decline_rows",
                        "surface_bound", "surface_declined", "surface_revoked",
                        "surface_never_offered", "custody_in_bind_detail", "four_keys"])
        w.writerow(row)
    print(json.dumps({k: v for k, v in snapshot.items() if k != "key_decline"}, indent=1)[:3000])
    print("declines:", len(declines), "wrote", out.name)


if __name__ == "__main__":
    main()
