"""Twice-daily backup of the Jarvis memory graph (FalkorDB) into the Ultron
Supabase database — a SEPARATE system from the memory DB, so a FalkorDB or volume
failure can't take the backup with it. Runs inside jarvis-tools (which already
has both the FalkorDB and Supabase connections). No new service, no OAuth.

Table `memory_backups(id, created_at, counts jsonb, snapshot jsonb)` — the full
graph as portable JSON (embeddings excluded; they regenerate from text on
restore). Keeps the last KEEP snapshots.

Env: FALKOR_HOST/FALKOR_PORT (+ MEMORY_GRAPH) to read the graph; SUPABASE_URL /
SUPABASE_DB_PASSWORD (already set for the ultron tools) to write the backup.
"""
from __future__ import annotations

import asyncio
import json
import os
from datetime import datetime, timezone

import ultron_db  # reuse the Supabase connection

GRAPH = os.environ.get("MEMORY_GRAPH", "jarvis")
KEEP = 14
INTERVAL_S = 12 * 3600  # twice a day


def configured() -> bool:
    return bool(os.environ.get("FALKOR_HOST")) and ultron_db.configured()


def _graph():
    from falkordb import FalkorDB
    db = FalkorDB(host=os.environ["FALKOR_HOST"],
                  port=int(os.environ.get("FALKOR_PORT", "6379")),
                  password=os.environ.get("FALKOR_PASSWORD") or None)
    return db.select_graph(GRAPH)


def export_graph() -> dict:
    g = _graph()

    def rows(q):
        try:
            return g.query(q).result_set
        except Exception:  # noqa: BLE001 — a missing label just yields nothing
            return []

    mem = rows("MATCH (m:Memory) RETURN m.id, m.text, m.kind, m.group, m.valid, "
               "m.created_at, m.confidence, m.tone, m.refers_to")
    ent = rows("MATCH (e:Entity) RETURN e.key, e.name, e.type")
    ins = rows("MATCH (i:Insight) RETURN i.id, i.text, i.created_at")
    rel = rows("MATCH (a)-[r]->(b) RETURN coalesce(a.id,a.key), type(r), "
               "coalesce(b.id,b.key)")
    mk = lambda cols, data: [dict(zip(cols, r)) for r in data]  # noqa: E731
    return {
        "exported_at": datetime.now(timezone.utc).isoformat(),
        "graph": GRAPH,
        "counts": {"memories": len(mem), "entities": len(ent),
                   "insights": len(ins), "relationships": len(rel)},
        "memories": mk(["id", "text", "kind", "group", "valid", "created_at",
                        "confidence", "tone", "refers_to"], mem),
        "entities": mk(["key", "name", "type"], ent),
        "insights": mk(["id", "text", "created_at"], ins),
        "relationships": mk(["from", "rel", "to"], rel),
    }


def run_backup() -> str:
    data = export_graph()
    counts, snap = json.dumps(data["counts"]), json.dumps(data, ensure_ascii=False)
    with ultron_db._connect() as conn:
        conn.execute(
            "CREATE TABLE IF NOT EXISTS memory_backups ("
            "id bigserial PRIMARY KEY, created_at timestamptz DEFAULT now(), "
            "counts jsonb, snapshot jsonb)")
        conn.execute(
            "INSERT INTO memory_backups (counts, snapshot) VALUES (%s::jsonb, %s::jsonb)",
            (counts, snap))
        # rotate — keep only the newest KEEP rows
        conn.execute(
            "DELETE FROM memory_backups WHERE id NOT IN "
            "(SELECT id FROM memory_backups ORDER BY id DESC LIMIT %s)", (KEEP,))
    c = data["counts"]
    return (f"→ Supabase memory_backups: {c['memories']} memories / "
            f"{c['entities']} entities / {c['insights']} insights (keep last {KEEP})")


async def scheduler():
    """Backup on startup, then every 12h. Never crashes the service."""
    while True:
        try:
            if configured():
                out = await asyncio.to_thread(run_backup)
                print(f"[memory-backup] {out}", flush=True)
            else:
                print("[memory-backup] skipped (FALKOR/SUPABASE env not set)",
                      flush=True)
        except Exception as e:  # noqa: BLE001
            print(f"[memory-backup] error: {str(e)[:200]}", flush=True)
        await asyncio.sleep(INTERVAL_S)
