#!/usr/bin/env python3
"""odysseus-memory — shell wrapper for the AI memory store.

The chat assistant accumulates durable facts about the user (name,
projects, preferences, contacts) into `data/memory.json`. This CLI
lets you inspect, search, and prune them from the shell.

    odysseus-memory list                          # all entries (paged)
    odysseus-memory list --category preference
    odysseus-memory search "tokyo"                # substring match
    odysseus-memory show MEMORY_ID
    odysseus-memory add "User lives in Tokyo" --category fact
    odysseus-memory delete MEMORY_ID
    odysseus-memory categories                    # counts per category

Reads/writes the same `data/memory.json` the web UI uses via the
`MemoryManager` helper. Atomic — the manager handles the temp-file
swap so partial writes can't corrupt the file.
"""

from __future__ import annotations
import sys
import os, sys
sys.path.insert(0, os.path.join(os.path.dirname(__file__), "_lib"))
from cli import quiet_logs, emit, fail, common_parser, run, REPO_ROOT as _REPO_ROOT
quiet_logs()

import argparse, json, logging, os, sys
from pathlib import Path

try:
    from services.memory.memory import MemoryManager
    quiet_logs()
except ModuleNotFoundError as e:
    sys.stderr.write(f"error: {e}\nhint: run from repo root with venv active.\n")
    sys.exit(2)


_DATA_DIR = str(_REPO_ROOT / "data")
_mgr: MemoryManager | None = None


def _manager() -> MemoryManager:
    global _mgr
    if _mgr is None:
        _mgr = MemoryManager(_DATA_DIR)
    return _mgr


def _memory_entries(entries):
    return [e for e in entries or [] if isinstance(e, dict)]


def cmd_list(args):
    entries = _memory_entries(_manager().load_all())
    if args.category:
        entries = [e for e in entries if (e.get("category") or "fact") == args.category]
    if args.source:
        entries = [e for e in entries if (e.get("source") or "user") == args.source]
    if args.owner:
        entries = [e for e in entries if (e.get("owner") or "") == args.owner]
    # Newest first
    entries = sorted(entries, key=lambda e: e.get("timestamp", 0), reverse=True)
    emit(entries[: args.limit], args)


def cmd_search(args):
    q = args.query.lower()
    entries = _memory_entries(_manager().load_all())
    matches = [e for e in entries if q in (e.get("text") or "").lower()]
    matches = sorted(matches, key=lambda e: e.get("timestamp", 0), reverse=True)
    emit(matches[: args.limit], args)


def cmd_show(args):
    for e in _memory_entries(_manager().load_all()):
        if e.get("id") == args.id:
            emit(e, args)
            return
    fail(f"no memory with id {args.id!r}")


def cmd_add(args):
    entry = _manager().add_entry(
        args.text,
        source="cli",
        category=args.category,
        owner=args.owner,
    )
    # add_entry doesn't save by default — the call in chat does it
    # after dedup checks. Persist here so a one-shot CLI add sticks.
    all_entries = _manager().load_all()
    if not any(isinstance(e, dict) and e.get("id") == entry.get("id") for e in all_entries):
        all_entries.append(entry)
        _manager().save(all_entries)
    emit(entry, args)


def cmd_delete(args):
    entries = _memory_entries(_manager().load_all())
    target = next((e for e in entries if e.get("id") == args.id), None)
    if not target:
        fail(f"no memory with id {args.id!r}")
    remaining = [e for e in entries if e.get("id") != args.id]
    _manager().save(remaining)
    emit({"ok": True, "deleted": target}, args)


def cmd_categories(args):
    counts: dict[str, int] = {}
    for e in _memory_entries(_manager().load_all()):
        cat = e.get("category") or "fact"
        counts[cat] = counts.get(cat, 0) + 1
    rows = sorted(counts.items(), key=lambda kv: -kv[1])
    emit([{"category": c, "count": n} for c, n in rows], args)


def _build_parser():
    common = argparse.ArgumentParser(add_help=False)
    common.add_argument("--pretty", action="store_true")
    p = argparse.ArgumentParser(prog="odysseus-memory", parents=[common])
    sub = p.add_subparsers(dest="cmd", required=True)

    pl = sub.add_parser("list", parents=[common])
    pl.add_argument("--category")
    pl.add_argument("--source")
    pl.add_argument("--owner")
    pl.add_argument("--limit", type=int, default=50)
    pl.set_defaults(func=cmd_list)

    ps = sub.add_parser("search", parents=[common])
    ps.add_argument("query")
    ps.add_argument("--limit", type=int, default=50)
    ps.set_defaults(func=cmd_search)

    psh = sub.add_parser("show", parents=[common])
    psh.add_argument("id")
    psh.set_defaults(func=cmd_show)

    pa = sub.add_parser("add", parents=[common])
    pa.add_argument("text")
    pa.add_argument("--category", default="fact")
    pa.add_argument("--owner")
    pa.set_defaults(func=cmd_add)

    pd = sub.add_parser("delete", parents=[common])
    pd.add_argument("id")
    pd.set_defaults(func=cmd_delete)

    pc = sub.add_parser("categories", parents=[common])
    pc.set_defaults(func=cmd_categories)

    return p


if __name__ == "__main__":
    sys.exit(run(_build_parser()))
