"""
Memory Nexus MCP Server — Claude Code plugin.

9 tools for persistent project memory that compounds across sessions.
Cloud-only — all intelligence is server-side at api.memory-nexus.org.

Usage:
  MEMORY_NEXUS_API_KEY=<key> python -m mcp_server.server
"""

import atexit
import logging
import os

import httpx
from mcp.server.fastmcp import FastMCP

from mcp_server.instructions import INSTRUCTIONS
from mcp_server.project import (
    detect_project,
    get_context_prefix,
    get_hat_id,
    is_first_connection,
    register_project,
)
from mcp_server.seeder import parse_claude_md

logger = logging.getLogger(__name__)

# ---------------------------------------------------------------------------
# Config
# ---------------------------------------------------------------------------

_BASE_URL = os.environ.get("MEMORY_NEXUS_BASE_URL", "http://localhost:8000")
_API_KEY = os.environ.get("MEMORY_NEXUS_API_KEY", "")

# Persistent HTTP client with connection pooling
_client = httpx.Client(
    base_url=_BASE_URL,
    headers={"X-API-Key": _API_KEY, "Content-Type": "application/json"},
    timeout=30,
)
atexit.register(_client.close)

mcp = FastMCP("Memory Nexus", instructions=INSTRUCTIONS)

# Cache project detection per session
_project_hash: str = ""
_project_name: str = ""


def _detect_once() -> tuple[str, str]:
    """Detect project once per session, cache the result."""
    global _project_hash, _project_name
    if not _project_hash:
        _project_hash, _project_name = detect_project()
    return _project_hash, _project_name


def _hat_id() -> str:
    h, _ = _detect_once()
    return get_hat_id(h)


def _ctx(context: str = "") -> str:
    """Prefix context with project hash."""
    h, _ = _detect_once()
    prefix = get_context_prefix(h)
    if context and context != "general":
        return f"{prefix}:{context}"
    return prefix


def _handle(resp: httpx.Response) -> dict:
    if resp.status_code >= 400:
        detail = resp.text[:500]
        raise Exception(f"Memory Nexus API error ({resp.status_code}): {detail}")
    return resp.json()


def _safe_call(func):
    """Wrap API calls — failures skip silently per server instructions."""
    try:
        return func()
    except Exception as e:
        logger.warning("Memory Nexus API call failed: %s", e)
        return {"error": str(e), "skipped": True}


# ---------------------------------------------------------------------------
# Tools
# ---------------------------------------------------------------------------


@mcp.tool()
def memory_nexus_session_start(task_context: str = "") -> dict:
    """Start a Memory Nexus session — loads your project briefing with directives,
    objectives, knowledge, and intelligence from previous sessions. Call this at
    the beginning of every session.

    Args:
        task_context: Brief description of what you're working on this session.
    """
    h, name = _detect_once()

    # Seed CLAUDE.md on first connection
    if is_first_connection(h):
        events = parse_claude_md()
        if events:
            _safe_call(lambda: _handle(_client.post(
                f"/v1/hats/{_hat_id()}/writeback/batch",
                json={"events": events, "agent_id": "claude-code-plugin", "session_id": "seed"},
            )))
        register_project(h, name)

    # Combined session start (stats + briefing in one call)
    result = _safe_call(lambda: _handle(_client.post(
        "/v1/session/start",
        json={"task_context": task_context, "project_hash": h, "project_name": name},
    )))
    return result


@mcp.tool()
def memory_nexus_remember(
    content: str,
    context: str = "general",
    importance: float = 0.5,
) -> dict:
    """Store a memory that persists across sessions — architecture decisions,
    preferences, bug fixes, conventions, or anything worth remembering.

    Args:
        content: What to remember.
        context: Category (e.g. "architecture", "preferences", "debugging").
        importance: How important (0.0-1.0). Decisions=0.8, preferences=0.7, patterns=0.6.
    """
    return _handle(_client.post("/v1/remember", json={
        "content": content,
        "context": _ctx(context),
        "importance": importance,
        "metadata": {},
    }))


@mcp.tool()
def memory_nexus_recall(
    query: str,
    context: str = "",
    scope: str = "project",
    limit: int = 5,
) -> dict:
    """Search your accumulated project intelligence by meaning — finds relevant
    decisions, patterns, and knowledge from previous sessions.

    Args:
        query: What to search for (searches by meaning, not exact keywords).
        context: Filter to a category. Empty searches everything in scope.
        scope: "project" (this project only) or "empire" (all your projects).
        limit: Max results (1-20).
    """
    body: dict = {"query": query, "limit": min(max(limit, 1), 20)}
    if scope == "empire":
        # No context filter — search everything
        pass
    elif context:
        body["context"] = _ctx(context)
    else:
        h, _ = _detect_once()
        body["context"] = get_context_prefix(h)
    return _handle(_client.post("/v1/recall", json=body))


@mcp.tool()
def memory_nexus_briefing(task_context: str = "", max_items: int = 10) -> dict:
    """Get an intelligence briefing for your current project — surfaces patterns,
    decisions, and insights from previous sessions. Structured with directives,
    objectives, knowledge, and intelligence sections.

    Args:
        task_context: What you're working on (improves briefing relevance).
        max_items: Max briefing items.
    """
    return _handle(_client.post(
        f"/v1/hats/{_hat_id()}/briefing",
        json={
            "session_id": "mcp-session",
            "task_context": {"description": task_context} if task_context else {},
            "max_items": max_items,
        },
    ))


@mcp.tool()
def memory_nexus_write_back(events: list[dict]) -> dict:
    """Feed observations back into the intelligence loop so your project knowledge
    compounds. Call at session end with a batch of observations.

    Each event needs: {"event_type": "observation|directive_set|objective_upsert|build_logged", "content": "..."}

    Args:
        events: List of observation dicts. Each must have event_type and content.
    """
    return _handle(_client.post(
        f"/v1/hats/{_hat_id()}/writeback/batch",
        json={
            "events": events,
            "agent_id": "claude-code-plugin",
            "session_id": "mcp-session",
        },
    ))


@mcp.tool()
def memory_nexus_stats() -> dict:
    """Get memory statistics — count of memories, relationships, vectors, patterns
    for your account."""
    return _handle(_client.get("/v1/stats"))


@mcp.tool()
def memory_nexus_forget(memory_id: str) -> dict:
    """Delete a specific memory by ID. Use when a memory is incorrect or outdated.

    Args:
        memory_id: The ID of the memory to delete.
    """
    return _handle(_client.delete(f"/v1/forget/{memory_id}"))


@mcp.tool()
def memory_nexus_session_end(
    handoff_summary: str = "",
    worked_on: list[str] | None = None,
    decisions: list[dict] | None = None,
    new_rules: list[str] | None = None,
    preferences: list[str] | None = None,
    issues: list[dict] | None = None,
    next_steps: list[str] | None = None,
    objective_updates: list[dict] | None = None,
    observations: list[str] | None = None,
) -> dict:
    """Save your session handoff — what you worked on, decisions made, and what's next.
    Call this at the end of every session. Accepts partial data — save whatever you have.

    Args:
        handoff_summary: One line — what you were doing when the session ended.
        worked_on: List of things completed this session.
        decisions: List of {"content": "...", "decision_type": "architecture|process|tooling"}.
        new_rules: List of persistent rules discovered ("always do X", "never do Y").
        preferences: List of user preferences learned.
        issues: List of {"content": "...", "root_cause": "...", "fix": "..."}.
        next_steps: List of things to do next session.
        objective_updates: List of {"title": "...", "state": "active|done|blocked"}.
        observations: List of other notable observations.
    """
    import time as _time
    from datetime import datetime as _dt, timezone as _tz

    h, _ = _detect_once()
    worked_on = worked_on or []
    decisions = decisions or []
    new_rules = new_rules or []
    preferences = preferences or []
    issues = issues or []
    next_steps = next_steps or []
    objective_updates = objective_updates or []
    observations = observations or []

    body = {
        "session_id": f"sess_{h}_{int(_time.time())}",
        "project_hash": h,
        "ended_at": _dt.now(_tz.utc).isoformat(),
        "handoff_summary": handoff_summary,
    }

    if worked_on:
        body["worked_on"] = [{"content": w, "confidence": 0.8, "source": "claude_inferred"} for w in worked_on]
    if decisions:
        body["decisions"] = [
            {"content": d.get("content", d) if isinstance(d, dict) else d,
             "decision_type": d.get("decision_type", "architecture") if isinstance(d, dict) else "architecture",
             "confidence": 0.9, "source": "user_explicit"}
            for d in decisions
        ]
    if new_rules:
        body["new_rules"] = [{"content": r, "confidence": 0.9, "source": "user_explicit"} for r in new_rules]
    if preferences:
        body["preferences"] = [{"content": p, "confidence": 0.85, "source": "user_explicit"} for p in preferences]
    if issues:
        body["issues"] = [
            {"content": i.get("content", "") if isinstance(i, dict) else i,
             "root_cause": i.get("root_cause", "") if isinstance(i, dict) else "",
             "fix": i.get("fix", "") if isinstance(i, dict) else "",
             "confidence": 0.8, "source": "claude_inferred"}
            for i in issues
        ]
    if next_steps:
        body["next_steps"] = [{"content": s, "confidence": 0.8, "source": "claude_inferred"} for s in next_steps]
    if objective_updates:
        body["objective_updates"] = [
            {"title": o.get("title", ""), "state": o.get("state", "active"),
             "progress_note": o.get("progress_note", ""), "confidence": 0.75}
            for o in objective_updates
        ]
    if observations:
        body["observations"] = [{"content": o, "confidence": 0.7, "source": "claude_inferred"} for o in observations]

    return _safe_call(lambda: _handle(_client.post("/v1/session/end", json=body)))


# ---------------------------------------------------------------------------
# Entry point
# ---------------------------------------------------------------------------

if __name__ == "__main__":
    if not _API_KEY:
        raise SystemExit(
            "MEMORY_NEXUS_API_KEY environment variable is required.\n"
            "Get one at https://memory-nexus.org"
        )
    mcp.run()
