"""MNEMOSYNTH MCP Server — FastMCP implementation with 8 memory tools.

Exposes Mnemosynth's memory system via the Model Context Protocol (MCP).
Works with Claude Desktop, Cursor, Windsurf, VS Code, and any MCP client.

Transport: stdio (default) or SSE (for remote/Docker).
"""

from __future__ import annotations

from mcp.server.fastmcp import FastMCP

# Create the MCP server instance
mcp = FastMCP(
    "mnemosynth",
    description="Universal AI Memory — persistent, verified, hallucination-resistant memory",
)

# Lazy-load Mnemosynth instance (initialized on first tool call)
_brain = None


def _get_brain():
    """Get or create the Mnemosynth singleton."""
    global _brain
    if _brain is None:
        from mnemosynth import Mnemosynth
        _brain = Mnemosynth()
    return _brain


# ── MCP Tools ────────────────────────────────────────────────────


@mcp.tool()
async def add_memory(content: str, memory_type: str = "auto") -> dict:
    """Store a new memory. Auto-classifies into episodic/semantic/procedural.

    Args:
        content: The information to remember.
        memory_type: One of 'auto', 'episodic', 'semantic', 'procedural'.
    """
    brain = _get_brain()
    try:
        node = brain.remember(content, memory_type=memory_type)
        return {
            "success": True,
            "id": node.id,
            "type": node.memory_type.value,
            "confidence": node.confidence,
            "message": f"Stored as {node.memory_type.value} memory (ID: {node.id[:8]}...)",
        }
    except Exception as e:
        return {"success": False, "error": str(e)}


@mcp.tool()
async def search_memory(query: str, limit: int = 5) -> list[dict]:
    """Search across all memory types using semantic similarity.

    Args:
        query: What to search for.
        limit: Maximum number of results (default: 5).
    """
    brain = _get_brain()
    try:
        results = brain.recall(query, limit=limit)
        return [
            {
                "id": mem.id,
                "content": mem.content,
                "type": mem.memory_type.value,
                "confidence": round(mem.confidence, 3),
                "created_at": mem.created_at.isoformat(),
                "status": mem.status.value,
            }
            for mem in results
        ]
    except Exception as e:
        return [{"error": str(e)}]


@mcp.tool()
async def get_digest(query: str, max_tokens: int = 150) -> str:
    """Get a compressed memory context block for the current query.

    Returns a <memory_digest> XML block that can be injected into LLM context.

    Args:
        query: The current topic or question.
        max_tokens: Maximum token budget for the digest (default: 150).
    """
    brain = _get_brain()
    try:
        digest = brain.digest(query=query, max_tokens=max_tokens)
        return digest if digest else "No relevant memories found."
    except Exception as e:
        return f"Error building digest: {e}"


@mcp.tool()
async def get_contradictions() -> list[dict]:
    """List detected contradictions in the knowledge base.

    Returns pairs of conflicting memories with confidence scores.
    """
    brain = _get_brain()
    # Placeholder — full NLI-based contradiction detection in v0.2
    try:
        semantic_memories = brain.semantic.search("", limit=50)
        contradictions = []

        # Simple check: look for deprecated memories that have superseded_by links
        for mem in semantic_memories:
            if mem.superseded_by:
                new_mem = brain.db.get_memory(mem.superseded_by)
                if new_mem:
                    contradictions.append({
                        "old": {
                            "id": mem.id,
                            "content": mem.content,
                            "confidence": mem.confidence,
                        },
                        "new": {
                            "id": new_mem.id,
                            "content": new_mem.content,
                            "confidence": new_mem.confidence,
                        },
                        "resolved": mem.status.value == "deprecated",
                    })

        return contradictions if contradictions else [{"message": "No contradictions detected."}]
    except Exception as e:
        return [{"error": str(e)}]


@mcp.tool()
async def run_dream() -> dict:
    """Trigger dream consolidation — cluster, promote, decay, resolve contradictions.

    Inspired by how the brain consolidates memories during sleep:
    1. Clusters recent episodic memories using HDBSCAN
    2. Promotes dense clusters to semantic knowledge graph
    3. Applies extra decay to noise/outlier memories
    """
    brain = _get_brain()
    try:
        report = brain.dream()
        return {
            "success": True,
            "promoted": report["promoted"],
            "contradictions_resolved": report["contradictions_resolved"],
            "decayed": report["decayed"],
            "message": (
                f"Dream complete: promoted {report['promoted']} facts, "
                f"resolved {report['contradictions_resolved']} contradictions, "
                f"decayed {report['decayed']} stale memories."
            ),
        }
    except Exception as e:
        return {"success": False, "error": str(e)}


@mcp.tool()
async def forget(memory_id: str) -> dict:
    """Remove a specific memory by its ID.

    Args:
        memory_id: The UUID of the memory to delete.
    """
    brain = _get_brain()
    try:
        success = brain.forget(memory_id)
        if success:
            return {"success": True, "message": f"Memory {memory_id[:8]}... deleted."}
        else:
            return {"success": False, "message": f"Memory {memory_id} not found."}
    except Exception as e:
        return {"success": False, "error": str(e)}


@mcp.tool()
async def get_stats() -> dict:
    """Get memory statistics — counts by type and status."""
    brain = _get_brain()
    try:
        return brain.stats()
    except Exception as e:
        return {"error": str(e)}


@mcp.tool()
async def get_provenance(memory_id: str) -> dict:
    """Get full audit trail for a memory — who said it, when, evidence chain.

    Args:
        memory_id: The UUID of the memory to inspect.
    """
    brain = _get_brain()
    try:
        memory = brain.db.get_memory(memory_id)
        if not memory:
            return {"error": f"Memory {memory_id} not found."}

        provenance = {
            "id": memory.id,
            "content": memory.content,
            "type": memory.memory_type.value,
            "confidence": memory.confidence,
            "status": memory.status.value,
            "created_at": memory.created_at.isoformat(),
            "last_accessed": memory.last_accessed.isoformat(),
            "access_count": memory.access_count,
            "corroboration_count": memory.corroboration_count,
            "source": memory.source.to_dict(),
            "supersedes": memory.supersedes,
            "superseded_by": memory.superseded_by,
        }

        # Get relationships
        relationships = brain.db.get_relationships(memory_id)
        provenance["relationships"] = [
            {
                "source": r.source_id,
                "target": r.target_id,
                "type": r.relation_type,
                "confidence": r.confidence,
            }
            for r in relationships
        ]

        return provenance
    except Exception as e:
        return {"error": str(e)}


# ── MCP Resources ────────────────────────────────────────────────


@mcp.resource("memory://stats")
async def resource_stats() -> str:
    """Memory statistics as a readable string."""
    brain = _get_brain()
    stats = brain.stats()
    return (
        f"MNEMOSYNTH Memory Statistics\n"
        f"Total: {stats['total']}\n"
        f"  Episodic: {stats['episodic']}\n"
        f"  Semantic: {stats['semantic']}\n"
        f"  Procedural: {stats['procedural']}\n"
        f"Status: {stats['active']} active, "
        f"{stats['deprecated']} deprecated, "
        f"{stats['quarantined']} quarantined"
    )


@mcp.resource("memory://recent")
async def resource_recent() -> str:
    """Recent memories as a readable list."""
    brain = _get_brain()
    recent = brain.db.get_recent_memories(days=7)
    if not recent:
        return "No memories from the last 7 days."

    lines = ["Recent Memories (last 7 days):"]
    for mem in recent[:20]:
        lines.append(
            f"  [{mem.memory_type.value}] {mem.content[:80]} "
            f"(confidence: {mem.confidence:.2f})"
        )
    return "\n".join(lines)


# ── Server Entry Point ───────────────────────────────────────────


def serve(transport: str = "stdio"):
    """Start the MCP server."""
    mcp.run(transport=transport)
