"""Resumable pilot export to SQLite. Standard library only; credentials stay in env.

KUNGJORT_API_KEY=... python3 sync.py --database kungjort.sqlite
Run again to fetch changes. Do not reuse this database with another key/scope.
"""
import argparse
import json
import os
import sqlite3
import time
import urllib.error
import urllib.parse
import urllib.request


def fetch(base, key, path, params=None):
    url = base.rstrip("/") + path
    if params:
        url += "?" + urllib.parse.urlencode(params)
    for attempt in range(4):
        try:
            req = urllib.request.Request(url, headers={"Authorization": "Bearer " + key})
            with urllib.request.urlopen(req, timeout=60) as response:
                return json.load(response)
        except urllib.error.HTTPError as error:
            if error.code == 404:
                return None
            if error.code not in (429, 503) or attempt == 3:
                raise
            time.sleep(min(60, int(error.headers.get("Retry-After", "5"))))


def save(db, document_id, revision, payload):
    # Keep tombstones and compare revisions so an old replay cannot undo newer data.
    db.execute("""INSERT INTO documents(id,revision,payload) VALUES(?,?,?)
        ON CONFLICT(id) DO UPDATE SET revision=excluded.revision,payload=excluded.payload
        WHERE excluded.revision >= documents.revision""",
               (document_id, int(revision), json.dumps(payload, ensure_ascii=False) if payload else None))


def sync(db, base, key):
    db.executescript("""CREATE TABLE IF NOT EXISTS documents(id TEXT PRIMARY KEY, revision INTEGER NOT NULL, payload TEXT);
        CREATE TABLE IF NOT EXISTS sync_state(id INTEGER PRIMARY KEY CHECK(id=1), phase TEXT, cursor TEXT);""")
    state = db.execute("SELECT phase,cursor FROM sync_state WHERE id=1").fetchone() or ("documents", None)
    phase, cursor = state
    while True:
        params = {"limit": 50, **({"cursor": cursor} if cursor else {})}
        batch = fetch(base, key, "/api/v1/" + phase, params)
        if batch is None:
            raise RuntimeError("Pilot API is unavailable; export checkpoint was preserved")
        entries = batch["documents"] if phase == "documents" else batch["changes"]
        # Fetch the whole batch before changing local state. No partial checkpoint.
        writes = []
        for entry in entries:
            doc_id = entry.get("document_id", entry.get("id"))
            document = None if entry.get("operation") == "delete" else fetch(base, key, f"/api/v1/documents/{doc_id}")
            writes.append((doc_id, document["revision"] if document else entry["revision"], document))
        if phase == "documents":
            next_phase = "documents" if batch["next_cursor"] else "changes"
            next_cursor = batch["next_cursor"] or batch["changes_cursor"]
        else:
            next_phase, next_cursor = "changes", batch["next_cursor"]
        with db:
            for values in writes:
                save(db, *values)
            db.execute("INSERT OR REPLACE INTO sync_state VALUES(1,?,?)", (next_phase, next_cursor))
        print(f"{phase}: applied {len(writes)} records")
        if phase == "changes" and not batch["has_more"]:
            break
        phase, cursor = next_phase, next_cursor


if __name__ == "__main__":
    parser = argparse.ArgumentParser()
    parser.add_argument("--database", required=True)
    parser.add_argument("--base-url", default="https://handlingar.kungjort.se")
    args = parser.parse_args()
    with sqlite3.connect(args.database) as connection:
        sync(connection, args.base_url, os.environ["KUNGJORT_API_KEY"])
