#!/usr/bin/env python3
"""KYCLESS Reviews API v1 -> local SQLite mirror. Python 3.9+, standard library.
Run: python3 kycless_reviews_sync.py --database kycless-reviews.sqlite
Re-run every 1-5 minutes. No API key. Your database contains public data only.
"""
import argparse
import json
import sqlite3
import sys
import time
import urllib.error
import urllib.parse
import urllib.request


def fetch(url):
    for attempt in range(5):
        try:
            request = urllib.request.Request(url, headers={"Accept": "application/json", "User-Agent": "KYCLESS-Reviews-Sync/1.0"})
            with urllib.request.urlopen(request, timeout=30) as response:
                return json.load(response)
        except urllib.error.HTTPError as error:
            if error.code == 409:
                return None
            if error.code not in (429, 500, 502, 503, 504) or attempt == 4:
                raise
            delay = error.headers.get("Retry-After", "5")
            time.sleep(min(300, max(1, int(delay) if delay.isdigit() else 5)))
        except (urllib.error.URLError, TimeoutError):
            if attempt == 4:
                raise
            time.sleep(2 ** attempt)
    raise RuntimeError("Retry budget exhausted")


def sync(args):
    filters = {"type": args.type, "limit": "100"}
    if args.company_id:
        filters["company_id"] = str(args.company_id)
    scope = args.base_url.rstrip("/") + "/api/v1/reviews?" + urllib.parse.urlencode(filters)
    db = sqlite3.connect(args.database)
    db.execute("CREATE TABLE IF NOT EXISTS kycless_reviews (id TEXT PRIMARY KEY, revision TEXT NOT NULL, payload TEXT NOT NULL)")
    db.execute("CREATE TABLE IF NOT EXISTS kycless_review_sync_state (id INTEGER PRIMARY KEY CHECK(id=1), scope TEXT NOT NULL, cursor TEXT NOT NULL)")
    db.commit()
    changed = deleted = 0
    try:
        db.execute("BEGIN IMMEDIATE")
        state = db.execute("SELECT scope,cursor FROM kycless_review_sync_state WHERE id=1").fetchone()
        cursor = state[1] if state and state[0] == scope and not args.full else "0"
        if args.full or not state or state[0] != scope:
            db.execute("DELETE FROM kycless_reviews")
        resets = 0
        while True:
            result = fetch(scope + "&cursor=" + urllib.parse.quote(cursor))
            if result is None:
                if resets:
                    raise RuntimeError("Server cursor still invalid after reset")
                resets += 1
                db.execute("DELETE FROM kycless_reviews")
                cursor = "0"
                continue
            if result.get("version") != "1":
                raise RuntimeError("Unsupported API version")
            for item in result["data"]:
                if item.get("origin") != "kycless":
                    raise RuntimeError("Unexpected origin")
                if item["status"] == "deleted":
                    db.execute("DELETE FROM kycless_reviews WHERE id=?", (item["id"],))
                    deleted += 1
                elif item["status"] == "published":
                    old = db.execute("SELECT revision FROM kycless_reviews WHERE id=?", (item["id"],)).fetchone()
                    if old is None or int(item["revision"]) > int(old[0]):
                        db.execute("INSERT INTO kycless_reviews(id,revision,payload) VALUES(?,?,?) ON CONFLICT(id) DO UPDATE SET revision=excluded.revision,payload=excluded.payload", (item["id"], item["revision"], json.dumps(item, ensure_ascii=False)))
                        changed += 1
                else:
                    raise RuntimeError("Unknown record status")
            following = result["pagination"]["next_cursor"]
            if result["pagination"]["has_more"] and int(following) <= int(cursor):
                raise RuntimeError("Non-advancing cursor")
            cursor = following
            if not result["pagination"]["has_more"]:
                break
        db.execute("INSERT INTO kycless_review_sync_state(id,scope,cursor) VALUES(1,?,?) ON CONFLICT(id) DO UPDATE SET scope=excluded.scope,cursor=excluded.cursor", (scope, cursor))
        db.commit()
        count = db.execute("SELECT count(*) FROM kycless_reviews").fetchone()[0]
        print(f"Synchronized {count} public records; upserts={changed}, deletions={deleted}, cursor={cursor}")
    except BaseException:
        db.rollback()
        raise
    finally:
        db.close()


if __name__ == "__main__":
    parser = argparse.ArgumentParser(description=__doc__)
    parser.add_argument("--database", default="kycless-reviews.sqlite", help="Dedicated SQLite file; do not use your application's main database")
    parser.add_argument("--base-url", default="https://kycless.cc", help="API origin (default: https://kycless.cc)")
    parser.add_argument("--type", choices=("all", "review", "reply"), default="all")
    parser.add_argument("--company-id", type=int)
    parser.add_argument("--full", action="store_true", help="Atomically rebuild the mirror from cursor=0 (e.g. weekly reconciliation)")
    try:
        sync(parser.parse_args())
    except Exception as error:
        print(f"Sync failed; local changes were rolled back: {error}", file=sys.stderr)
        sys.exit(1)
