"""Python 3.10+: pip install requests; export AMAZINGCAT_TOKEN='your browser-issued token'."""
import csv, json, os, time
from datetime import datetime, timezone
from pathlib import Path
import requests
URL = "https://mcp.amazingcat.net/mcp"
# Seventh cassation court, civil acts mentioning Article 112 GPK, dated 2023–2025.
QUERY = {"court": 7, "kind": "civil", "norms": "ГПК 112", "year_from": 2023,
         "year_to": 2025, "limit": 10, "detail": "brief"}
state_path, csv_path = Path("kas-export.state.json"), Path("kas-export.csv")
state = json.loads(state_path.read_text()) if state_path.exists() else {"cursor": 0, "calls": 0}
if time.time() < state.get("retry_not_before", 0):
    raise SystemExit("Quota pause; resume in " + str(int(state["retry_not_before"] - time.time()) + 1) + " seconds")
http = requests.Session()
http.headers.update({"Authorization": "Bearer " + os.environ["AMAZINGCAT_TOKEN"],
                     "Accept": "application/json, text/event-stream"})
last = 0.0
def save():
    tmp = state_path.with_suffix(".tmp")
    tmp.write_text(json.dumps(state)); tmp.replace(state_path)
def rpc(method, params=None):
    global last
    body = {"jsonrpc": "2.0", "method": method, "params": params or {}}
    if method != "notifications/initialized": body["id"] = method
    for attempt in range(5):
        day = datetime.now(timezone.utc).date().isoformat()
        if state.get("day") != day: state.update(day=day, calls=0)
        # Count every attempt except initialize; include the initialized notification.
        if method != "initialize":
            if state["calls"] >= 300: raise SystemExit("300 requests reached; resume on the next UTC day")
            state["calls"] += 1; save()
        time.sleep(max(0, 1.05 - (time.monotonic() - last))); last = time.monotonic()
        with http.post(URL, json=body, stream=True, timeout=(15, 120)) as r:
            # The server can signal a quota delay with HTTP 429 or HTTP 200 + Retry-After.
            if r.status_code == 429 or r.headers.get("Retry-After"):
                delay = int(r.headers.get("Retry-After", "60"))
                state["retry_not_before"] = time.time() + delay; save()
                if delay > 60: raise SystemExit("Quota pause; checkpoint saved. Resume after Retry-After: " + str(delay))
                time.sleep(delay); continue
            if r.status_code == 404: raise SystemExit("Session lost; run the script again to initialize and resume")
            r.raise_for_status()
            if r.headers.get("Mcp-Session-Id"): http.headers["Mcp-Session-Id"] = r.headers["Mcp-Session-Id"]
            if r.status_code == 202: return None
            if "text/event-stream" in r.headers.get("Content-Type", ""):
                r.encoding = "utf-8"; data = []; reply = None
                for line in r.iter_lines(decode_unicode=True):
                    if line.startswith("data:"): data.append(line[5:].lstrip())
                    elif not line and data:
                        event = json.loads("\n".join(data)); data = []
                        if event.get("id") == body.get("id"): reply = event; break
                if reply is None: raise RuntimeError("SSE ended without a response")
            else: reply = r.json()
        if "error" in reply: raise RuntimeError(reply["error"])
        return reply["result"]
    raise SystemExit("Retry limit reached; checkpoint saved")
if state.get("done"): raise SystemExit("Export already completed: " + str(csv_path))
if state.get("cursor", 0) and not csv_path.exists(): raise SystemExit("CSV missing; restore it before resuming")
init = rpc("initialize", {"protocolVersion": "2025-06-18", "capabilities": {},
                         "clientInfo": {"name": "cat-csv", "version": "1"}})
http.headers["MCP-Protocol-Version"] = init["protocolVersion"]
rpc("notifications/initialized")
seen = {row["act_id"] for row in csv.DictReader(csv_path.open(encoding="utf-8"))} if csv_path.exists() else set()
with csv_path.open("a", newline="", encoding="utf-8") as f:
    writer = csv.DictWriter(f, fieldnames=["act_id", "суд", "дата", "номер_определения", "класс_исхода"])
    if f.tell() == 0: writer.writeheader()
    pages = 0
    while True:
        result = rpc("tools/call", {"name": "kas_search", "arguments": {**QUERY, "cursor": state["cursor"]}})
        if result.get("isError"): raise RuntimeError(result)
        data = json.loads(next(b["text"] for b in result["content"] if b["type"] == "text"))
        if data.get("error") or data.get("ошибка") or data.get("частично"): raise RuntimeError("Search failed; cursor unchanged")
        for row in data["результаты"]:
            if str(row["act_id"]) not in seen:
                writer.writerow({k: row.get(k, "") for k in writer.fieldnames}); seen.add(str(row["act_id"]))
        f.flush(); os.fsync(f.fileno())  # Save rows before advancing the checkpoint; deduplicate on resume.
        cursor = data["page"]["next_cursor"]
        if cursor is not None and cursor <= state["cursor"]: raise RuntimeError("Cursor did not advance; checkpoint unchanged")
        state.update(cursor=cursor or 0, done=cursor is None); save()
        pages += 1; print(f"rows={len(seen)} found={data['найдено']} next_cursor={cursor}")
        if state["done"] or pages == int(os.environ.get("AMAZINGCAT_MAX_PAGES", "0")): break
