"""MCP Server for agentic-memory.

Exposes memory operations as MCP tools for Claude Code and other MCP clients.

Usage:
    python -m agentic_memory.mcp_server [--repo /path/to/repo]
"""

from __future__ import annotations

import argparse
import os

from mcp.server.fastmcp import FastMCP

from agentic_memory.evidence import FileRef, GitCommitRef, ManualRef, URLRef
from agentic_memory.memory import Memory

mcp = FastMCP(
    "agentic-memory",
    instructions="Repo memory for AI agents — every memory has a source, every source gets verified.",
)

_memory: Memory | None = None


def _get_memory() -> Memory:
    global _memory
    if _memory is None:
        repo_path = os.environ.get("AGENTIC_MEMORY_REPO", ".")
        _memory = Memory(repo_path)
    return _memory


def _build_evidence(
    evidence_type: str,
    file_path: str | None = None,
    lines_start: int | None = None,
    lines_end: int | None = None,
    url: str | None = None,
    commit_sha: str | None = None,
    commit_file: str | None = None,
    note: str | None = None,
) -> FileRef | GitCommitRef | URLRef | ManualRef:
    """Build an Evidence object from parameters."""
    if evidence_type == "file":
        if not file_path:
            raise ValueError("file_path is required for file evidence")
        lines = (lines_start, lines_end) if lines_start and lines_end else None
        return FileRef(path=file_path, lines=lines)
    elif evidence_type == "url":
        if not url:
            raise ValueError("url is required for url evidence")
        return URLRef(url=url)
    elif evidence_type == "git_commit":
        if not commit_sha:
            raise ValueError("commit_sha is required for git_commit evidence")
        return GitCommitRef(sha=commit_sha, file_path=commit_file)
    elif evidence_type == "manual":
        return ManualRef(note=note or "manually added")
    else:
        raise ValueError(f"Unknown evidence_type: {evidence_type}. Use: file, url, git_commit, manual")


@mcp.tool()
def memory_add(
    content: str,
    evidence_type: str,
    file_path: str | None = None,
    lines_start: int | None = None,
    lines_end: int | None = None,
    url: str | None = None,
    commit_sha: str | None = None,
    commit_file: str | None = None,
    note: str | None = None,
    tags: list[str] | None = None,
) -> str:
    """Add a memory with required evidence citation.

    Every memory must have a verifiable source. Choose an evidence_type and provide
    the corresponding parameters.

    Args:
        content: The knowledge to remember (e.g., "This project uses ruff with line-length=120")
        evidence_type: One of "file", "url", "git_commit", "manual"
        file_path: (file) Relative path to the evidence file
        lines_start: (file) Start line number (1-indexed)
        lines_end: (file) End line number (1-indexed)
        url: (url) Web URL as evidence source
        commit_sha: (git_commit) Git commit SHA
        commit_file: (git_commit) File path within the commit
        note: (manual) Human-provided evidence note
        tags: Optional tags for categorization
    """
    mem = _get_memory()
    evidence = _build_evidence(
        evidence_type=evidence_type,
        file_path=file_path,
        lines_start=lines_start,
        lines_end=lines_end,
        url=url,
        commit_sha=commit_sha,
        commit_file=commit_file,
        note=note,
    )

    try:
        record = mem.add(content, evidence=evidence, tags=tags)
        return (
            f"Memory added [{record.id}]\n"
            f"Content: {record.content}\n"
            f"Evidence: {record.evidence_label}\n"
            f"Status: {record.validation_status.value}\n"
            f"Confidence: {record.confidence}"
        )
    except (TypeError, ValueError) as e:
        return f"Error: {e}"


@mcp.tool()
def memory_query(
    query: str,
    limit: int = 5,
    validate: bool = True,
    include_stale: bool = True,
) -> str:
    """Query memories with automatic citation re-validation.

    Searches stored memories and re-validates their evidence citations before returning.
    Stale or invalid citations are flagged with reduced confidence scores.

    Args:
        query: Search query string
        limit: Maximum number of results (default: 5)
        validate: Re-validate citations before returning (default: true)
        include_stale: Include stale/invalid memories in results (default: true)
    """
    mem = _get_memory()
    result = mem.query(query, limit=limit, validate=validate, include_stale=include_stale)

    if not result.memories:
        return "No memories found."

    status_icons = {"valid": "VALID", "stale": "STALE", "invalid": "INVALID", "unchecked": "UNCHECKED"}
    lines = []
    for i, (memory, citation) in enumerate(zip(result.memories, result.citations), 1):
        icon = status_icons.get(citation.status.value, "?")
        lines.append(
            f"{i}. {memory.content}\n"
            f"   [{icon}] {citation.evidence.short_label()}\n"
            f"   Confidence: {memory.confidence:.1f}"
        )
        if citation.message:
            lines.append(f"   Note: {citation.message}")

    lines.append(f"\nOverall confidence: {result.confidence:.2f}")
    return "\n".join(lines)


@mcp.tool()
def memory_validate() -> str:
    """Validate all stored memories by re-checking their evidence citations.

    Checks whether referenced files still exist and their content hasn't changed.
    Returns a summary of valid, stale, and invalid memories.
    """
    mem = _get_memory()
    problematic = mem.validate()
    status = mem.status()

    lines = [
        "Validation complete:",
        f"  VALID:     {status['valid']}",
        f"  STALE:     {status['stale']}",
        f"  INVALID:   {status['invalid']}",
        f"  UNCHECKED: {status['unchecked']}",
        f"  TOTAL:     {status['total']}",
    ]

    if problematic:
        lines.append("\nProblematic memories:")
        for record in problematic:
            label = "STALE" if record.validation_status.value == "stale" else "INVALID"
            lines.append(f"  [{label}] {record.id}: {record.content}")
            lines.append(f"    Reason: {record.validation_message}")

    return "\n".join(lines)


@mcp.tool()
def memory_status() -> str:
    """Get a summary of all stored memories and their validation status."""
    mem = _get_memory()
    s = mem.status()

    return (
        f"Memory Status:\n"
        f"  Total:     {s['total']}\n"
        f"  Valid:     {s['valid']}\n"
        f"  Stale:     {s['stale']}\n"
        f"  Invalid:   {s['invalid']}\n"
        f"  Unchecked: {s['unchecked']}"
    )


@mcp.tool()
def memory_list(limit: int = 20) -> str:
    """List all stored memories with their evidence and validation status.

    Args:
        limit: Maximum number of memories to list (default: 20)
    """
    mem = _get_memory()
    records = mem.list_all(limit=limit)

    if not records:
        return "No memories stored."

    status_icons = {"valid": "VALID", "stale": "STALE", "invalid": "INVALID", "unchecked": "UNCHECKED"}
    lines = []
    for record in records:
        icon = status_icons.get(record.validation_status.value, "?")
        lines.append(
            f"[{record.id}] {record.content}\n"
            f"  [{icon}] {record.evidence_label} | confidence: {record.confidence:.1f}"
        )

    lines.append(f"\nShowing {len(records)} of {mem.status()['total']} memories")
    return "\n".join(lines)


@mcp.tool()
def memory_delete(memory_id: str) -> str:
    """Delete a specific memory by its ID.

    Args:
        memory_id: The memory ID to delete
    """
    mem = _get_memory()
    if mem.delete(memory_id):
        return f"Deleted memory {memory_id}"
    return f"Memory {memory_id} not found"


def main():
    parser = argparse.ArgumentParser(description="agentic-memory MCP server")
    parser.add_argument(
        "--repo", default=None, help="Repository path (default: cwd or AGENTIC_MEMORY_REPO env)"
    )
    args = parser.parse_args()

    if args.repo:
        os.environ["AGENTIC_MEMORY_REPO"] = args.repo

    mcp.run(transport="stdio")


if __name__ == "__main__":
    main()
