"""
Snapshot store — automatic per-turn state capture via lifecycle hooks.

Each snapshot records:
  - git hash + branch at time of capture
  - transcript path (reference, not copy)
  - todo diff (what changed since last snapshot)
  - todo state (full, for O(1) reconstruction)
  - session/agent metadata
"""

from __future__ import annotations

import json
import os
import subprocess
from datetime import datetime
from pathlib import Path
from typing import Any, Optional
from uuid import uuid4

from .db import list_memories


def _snapshots_dir() -> Path:
    from .db import _db_path as dbp
    if not dbp:
        raise RuntimeError("DB not initialised — call db.init() first")
    d = dbp.parent / "snapshots"
    d.mkdir(parents=True, exist_ok=True)
    return d


def _index_path() -> Path:
    return _snapshots_dir() / "index.jsonl"


# ---------------------------------------------------------------------------
# Git helpers
# ---------------------------------------------------------------------------

def _git(args: list[str], cwd: str) -> Optional[str]:
    try:
        r = subprocess.run(
            ["git"] + args, cwd=cwd,
            capture_output=True, text=True, timeout=5
        )
        return r.stdout.strip() if r.returncode == 0 else None
    except Exception:
        return None


def git_hash(cwd: str) -> Optional[str]:
    return _git(["rev-parse", "HEAD"], cwd)


def git_branch(cwd: str) -> Optional[str]:
    return _git(["rev-parse", "--abbrev-ref", "HEAD"], cwd)


def git_status(cwd: str) -> Optional[str]:
    return _git(["status", "--short"], cwd)


# ---------------------------------------------------------------------------
# Todo diff
# ---------------------------------------------------------------------------

def _current_todos() -> list[dict[str, Any]]:
    return [
        {"id": t["id"], "content": t["content"], "status": t["status"], "tags": t["tags"]}
        for t in list_memories(type="todo")
    ]


def _compute_diff(prev: list[dict], curr: list[dict]) -> list[dict]:
    prev_map = {t["id"]: t for t in prev}
    curr_map = {t["id"]: t for t in curr}
    diff: list[dict] = []

    for id, c in curr_map.items():
        if id not in prev_map:
            diff.append({"op": "add", "id": id, "content": c["content"], "status": c["status"]})
        else:
            p = prev_map[id]
            changes: dict[str, Any] = {"op": "update", "id": id}
            if p["status"] != c["status"]:
                changes["status"] = {"from": p["status"], "to": c["status"]}
            if p["content"] != c["content"]:
                changes["content"] = {"from": p["content"], "to": c["content"]}
            if len(changes) > 2:  # has actual changes beyond op+id
                diff.append(changes)

    for id in prev_map:
        if id not in curr_map:
            diff.append({"op": "remove", "id": id})

    return diff


# ---------------------------------------------------------------------------
# Snapshot I/O
# ---------------------------------------------------------------------------

def _read_all() -> list[dict[str, Any]]:
    p = _index_path()
    if not p.exists():
        return []
    with open(p) as f:
        return [json.loads(l) for l in f if l.strip()]


def last_snapshot() -> Optional[dict[str, Any]]:
    snaps = _read_all()
    return snaps[-1] if snaps else None


def save_snapshot(
    session_id: Optional[str] = None,
    agent: str = "unknown",
    transcript_path: Optional[str] = None,
    cwd: Optional[str] = None,
    message_id: Optional[int] = None,
    trigger: str = "stop",
) -> dict[str, Any]:
    cwd = cwd or os.getcwd()
    curr_todos = _current_todos()
    prev = last_snapshot()
    prev_todos = prev.get("todos_state", []) if prev else []

    snapshot: dict[str, Any] = {
        "id":              str(uuid4()),
        "timestamp":       datetime.utcnow().isoformat(),
        "session_id":      session_id,
        "agent":           agent,
        "trigger":         trigger,
        "git_hash":        git_hash(cwd),
        "git_branch":      git_branch(cwd),
        "git_status":      git_status(cwd),
        "transcript_path": transcript_path,
        "cwd":             cwd,
        "message_id":      message_id,
        "todo_diff":       _compute_diff(prev_todos, curr_todos),
        "todos_state":     curr_todos,
    }

    with open(_index_path(), "a") as f:
        f.write(json.dumps(snapshot) + "\n")

    return snapshot


def list_snapshots(limit: int = 20) -> list[dict[str, Any]]:
    snaps = _read_all()
    return list(reversed(snaps[-limit:]))


def get_snapshot(id: str) -> Optional[dict[str, Any]]:
    for s in _read_all():
        if s["id"] == id:
            return s
    return None


def todos_at(snapshot_id: str) -> Optional[list[dict]]:
    s = get_snapshot(snapshot_id)
    return s.get("todos_state") if s else None
