"""One-off: extract entities from every EXISTING memory with the local model and
attach them to the shared graph — so Layer 2 covers the facts saved before it
existed. Idempotent (MERGE), so re-running is safe.

    set -a; . ./.env; set +a
    .venv/bin/python tools/backfill_entities.py
"""
from __future__ import annotations

import json
import os
import sys
import time
import urllib.parse
import urllib.request

sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
from voice import memory_entities as me  # noqa: E402

URL = os.environ["MEMORY_API_URL"].rstrip("/")
KEY = os.environ["MEMORY_API_KEY"]
GROUP = os.environ.get("MEMORY_GROUP", "ahmed")


def req(method: str, path: str, body: dict | None = None, timeout: int = 45):
    data = json.dumps(body).encode() if body is not None else None
    r = urllib.request.Request(URL + path, data=data, method=method)
    r.add_header("Authorization", f"Bearer {KEY}")
    r.add_header("Content-Type", "application/json")
    with urllib.request.urlopen(r, timeout=timeout) as resp:
        raw = resp.read()
        return json.loads(raw) if raw else {}


# wait for the (just-deployed) /attach_entities endpoint to be live
for _ in range(45):
    try:
        req("POST", "/attach_entities", {"memory_id": "__ping__", "entities": []})
        break
    except Exception:  # noqa: BLE001
        time.sleep(4)

mems = req("GET", "/memories?" + urllib.parse.urlencode(
    {"group": GROUP, "limit": 500})).get("memories", [])
print(f"{len(mems)} memories to scan", flush=True)
total = 0
for i, m in enumerate(mems, 1):
    try:
        ents = me.extract(m.get("fact", ""))
        if ents:
            req("POST", "/attach_entities",
                {"memory_id": m["id"], "entities": ents})
            total += len(ents)
    except Exception as e:  # noqa: BLE001
        print(f"  [skip {m.get('id','?')[:8]}: {str(e)[:60]}]", flush=True)
    if i % 10 == 0:
        print(f"  {i}/{len(mems)} … {total} entity mentions so far", flush=True)
print(f"DONE: {total} entity mentions attached", flush=True)
