"""soagila_snapshot + soagila_restore — context persistence across compaction.

Anthropic's Claude Desktop/Code automatically compacts long conversations to
save tokens. Compaction is lossy: fine-grained detail gets summarised and
subtle decisions disappear. SOAGILA's snapshot tools solve that by letting
Claude capture the full session state BEFORE compaction fires, then replay
it AFTER compaction so the conversation picks up exactly where it left off.

Design:
    - ``soagila_snapshot`` takes a comprehensive payload (session summary,
      decisions, code changes, open questions, file paths, etc.) and writes
      it as a special ``snapshot`` episodic memory with importance=1.0.
    - Snapshots live in the same AES-GCM encrypted SQLite as regular
      memories, but carry the ``is_snapshot`` metadata flag so we can list
      just snapshots without them polluting normal recall.
    - ``soagila_restore`` fetches the most recent snapshot for the current
      project (or a specific ``snapshot_id``) and returns its full content
      so Claude can feed it back into its own context.

Claude is instructed (via tool description + CLAUDE.md) to call
``soagila_snapshot`` proactively when the conversation is getting long and
``soagila_restore`` as its very first action after a compaction event.
"""

from __future__ import annotations

import hashlib
import json
import logging
from datetime import datetime, timezone
from uuid import uuid4

from mcp.types import TextContent, Tool

logger = logging.getLogger(__name__)

_SNAPSHOT_TAG = "__soagila_snapshot__"


# ═══════════════════════════════════════════════════════════════════════
# soagila_snapshot
# ═══════════════════════════════════════════════════════════════════════


def get_definition() -> Tool:
    return Tool(
        name="soagila_snapshot",
        description=(
            "PRE-COMPACTION CONTEXT CAPTURE — Call this tool PROACTIVELY when "
            "the conversation is getting long and approaching Anthropic's "
            "automatic context compaction. Compaction is lossy: it summarises "
            "older messages and the fine-grained details — exact file paths, "
            "decision rationales, bug root causes, code diffs — disappear "
            "forever. This tool captures ALL of that into an encrypted, "
            "persistent snapshot BEFORE compaction fires. After compaction "
            "happens, call soagila_restore immediately as your first action "
            "to recover the full context.\n\n"
            "WHEN TO CALL:\n"
            "1. You notice the conversation has produced >10 significant "
            "changes, decisions, or file edits\n"
            "2. The user says anything like 'bu chat uzadı' or 'context'i "
            "toparla'\n"
            "3. You see an automatic reminder about context window usage\n"
            "4. Before any action that might trigger a summarisation pass\n\n"
            "WHAT TO INCLUDE in the payload: exhaustive detail — session "
            "summary, every significant decision with its rationale, every "
            "code change with file path + line numbers + before/after, every "
            "open question, every known bug, every TODO, every file the user "
            "cares about, every commit message, every credential name (NOT "
            "values), every external URL. Be verbose; storage is cheap."
        ),
        inputSchema={
            "type": "object",
            "properties": {
                "session_summary": {
                    "type": "string",
                    "description": "1-2 paragraph high-level summary of what happened in this session so far.",
                },
                "decisions": {
                    "type": "array",
                    "items": {"type": "string"},
                    "description": "List of significant decisions made with rationale.",
                },
                "code_changes": {
                    "type": "array",
                    "items": {"type": "string"},
                    "description": "File paths + what changed + why, one entry per logical change.",
                },
                "open_questions": {
                    "type": "array",
                    "items": {"type": "string"},
                    "description": "Things the user hasn't decided yet, outstanding discussions.",
                },
                "next_actions": {
                    "type": "array",
                    "items": {"type": "string"},
                    "description": "Ordered TODO list for what comes after the compaction.",
                },
                "user_intent": {
                    "type": "string",
                    "description": "What the user is ultimately trying to accomplish (the meta-goal).",
                },
                "artifacts": {
                    "type": "object",
                    "description": "Freeform dict of other state — file paths, URLs, credentials names, error messages, etc.",
                },
                "topic_tags": {
                    "type": "array",
                    "items": {"type": "string"},
                    "description": "Tags for later retrieval. Default: ['snapshot', 'context'].",
                },
            },
            "required": ["session_summary"],
        },
    )


async def handle(arguments: dict) -> list[TextContent]:
    """Store a comprehensive pre-compaction snapshot as an encrypted memory."""
    session_summary = arguments.get("session_summary", "").strip()
    if not session_summary:
        return [TextContent(
            type="text",
            text=json.dumps({"error": "session_summary is required"}),
        )]

    payload = {
        "session_summary": session_summary,
        "decisions": arguments.get("decisions", []),
        "code_changes": arguments.get("code_changes", []),
        "open_questions": arguments.get("open_questions", []),
        "next_actions": arguments.get("next_actions", []),
        "user_intent": arguments.get("user_intent", ""),
        "artifacts": arguments.get("artifacts", {}),
        "captured_at": datetime.now(timezone.utc).isoformat(),
        "tag": _SNAPSHOT_TAG,
    }
    # Serialise the whole structured payload; remember() will encrypt it
    content = json.dumps(payload, indent=2, ensure_ascii=False)

    topic_tags = arguments.get("topic_tags") or ["snapshot", "context"]
    if "snapshot" not in topic_tags:
        topic_tags.insert(0, "snapshot")

    try:
        from soagila.core.tcmg.memory_models import EpisodicMemory
        from soagila.core.tcmg.episodic_memory import store as ep_store
        from soagila.core.idl.ledger import create_receipt

        memory = EpisodicMemory(
            user_id="local",
            org_id="local",
            session_id="current",
            content=content,
            encrypted_content=b"",  # store() will encrypt
            pii_tags=[],
            classification="snapshot",
            topic_tags=topic_tags,
            confidence_score=1.0,
            relevance_score=1.0,
            importance=1.0,
            source_type="snapshot",
            metadata={"is_snapshot": True, "tag": _SNAPSHOT_TAG},
        )
        memory_id = await ep_store(memory)

        receipt = await create_receipt(
            receipt_type="snapshot",
            user_id="local",
            memories_created=[memory_id],
            context_hash=hashlib.sha256(content.encode("utf-8")).hexdigest(),
            metadata={
                "tag": _SNAPSHOT_TAG,
                "payload_size": len(content),
                "decisions_count": len(payload["decisions"]),
                "code_changes_count": len(payload["code_changes"]),
            },
        )

        return [TextContent(
            type="text",
            text=json.dumps({
                "status": "snapshot_stored",
                "snapshot_id": memory_id,
                "receipt_id": receipt.id,
                "size_bytes": len(content),
                "encrypted": True,
                "restored_by": "Call soagila_restore after compaction to recover this snapshot.",
            }, indent=2),
        )]
    except Exception as exc:  # noqa: BLE001
        logger.error("snapshot store failed: %s", exc, exc_info=True)
        return [TextContent(
            type="text",
            text=json.dumps({"error": f"Snapshot failed: {exc}"}),
        )]