"""Tests for MCP memory tools."""

from __future__ import annotations

import sys
from pathlib import Path
from unittest.mock import MagicMock, patch

import pytest
import yaml

sys.path.insert(0, str(Path(__file__).parent.parent / "src"))


# ------------------------------------------------------------------
# Helpers
# ------------------------------------------------------------------


def _read_fm(path: Path) -> dict:
    """Parse YAML frontmatter from a markdown file."""
    text = path.read_text()
    if text.startswith("---"):
        end = text.index("---", 3)
        return yaml.safe_load(text[3:end])
    return {}


# ------------------------------------------------------------------
# Fixtures
# ------------------------------------------------------------------


@pytest.fixture(autouse=True)
def _patch_dirs(tmp_path, monkeypatch):
    """Point ENTITIES_DIR and INDEX_PATH at tmp_path."""
    monkeypatch.setattr("engram.config.ENTITIES_DIR", tmp_path)
    monkeypatch.setattr(
        "engram.config.INDEX_PATH",
        tmp_path / "test_index.sqlite",
    )
    # Also patch the module-level reference in mcp_tools
    monkeypatch.setattr("engram.mcp_tools.ENTITIES_DIR", tmp_path)
    return tmp_path


# ------------------------------------------------------------------
# save_memory
# ------------------------------------------------------------------


class TestSaveMemory:
    def test_creates_file(self, tmp_path: Path) -> None:
        from engram.mcp_tools import save_memory

        result = save_memory(
            content="Hello world",
            topics=["Testing"],
            importance="high",
            summary="test note",
        )

        files = list(tmp_path.glob("*.md"))
        assert len(files) == 1
        fm = _read_fm(files[0])
        assert fm["importance"] == "high"
        assert "created_at" in fm
        assert "Hello world" in files[0].read_text()
        assert "path" in result

    def test_returns_topics(self, tmp_path: Path) -> None:
        from engram.mcp_tools import save_memory

        result = save_memory(
            content="Body",
            topics=["Alpha", "Beta"],
        )

        # Topics may be pruned by guardrail; check
        # they appear in either verified or pruned
        all_topics = result["verified_topics"] + result["pruned_topics"]
        assert "Alpha" in all_topics
        assert "Beta" in all_topics


# ------------------------------------------------------------------
# update_memory
# ------------------------------------------------------------------


class TestUpdateMemory:
    def _seed(self, d: Path) -> Path:
        f = d / "note.md"
        f.write_text("---\ntopics:\n- Seed\nimportance: low\naccess_count: 3\n---\nOriginal body\n")
        return f

    def test_merges_fields(self, tmp_path: Path) -> None:
        from engram.mcp_tools import update_memory

        f = self._seed(tmp_path)
        update_memory(path=str(f), importance="critical")

        fm = _read_fm(f)
        assert fm["importance"] == "critical"
        assert fm["access_count"] == 3
        assert "Seed" in fm["topics"]

    def test_rejects_path_traversal(self, tmp_path: Path) -> None:
        from engram.mcp_tools import update_memory

        with pytest.raises(ValueError):
            update_memory(
                path=str(tmp_path / ".." / "etc" / "passwd"),
                importance="critical",
            )


# ------------------------------------------------------------------
# delete_memory
# ------------------------------------------------------------------


class TestDeleteMemory:
    def _seed(self, d: Path) -> Path:
        f = d / "doomed.md"
        f.write_text("---\ntopics:\n- Temp\n---\nBye\n")
        return f

    def test_archives(self, tmp_path: Path) -> None:
        from engram.mcp_tools import delete_memory

        f = self._seed(tmp_path)
        delete_memory(path=str(f), archive=True)

        assert not f.exists()
        archive = tmp_path / ".archive" / "doomed.md"
        assert archive.exists()

    def test_hard_delete(self, tmp_path: Path) -> None:
        from engram.mcp_tools import delete_memory

        f = self._seed(tmp_path)
        delete_memory(path=str(f), archive=False)

        assert not f.exists()
        archive = tmp_path / ".archive" / "doomed.md"
        assert not archive.exists()


# ------------------------------------------------------------------
# search_memory
# ------------------------------------------------------------------


class TestSearchMemory:
    def test_finds_content(self, tmp_path: Path) -> None:
        from engram.mcp_tools import search_memory

        f = tmp_path / "findme.md"
        f.write_text("---\ntopics:\n- Search\n---\nUnique needle in haystack\n")

        mock_proc = MagicMock()
        mock_proc.returncode = 0
        mock_proc.stdout = f"{f}:Unique needle in haystack"

        with patch(
            "engram.mcp_tools.subprocess.run",
            return_value=mock_proc,
        ):
            results = search_memory(query="needle")

        assert len(results) >= 1
        assert "needle" in results[0]["snippet"]


# ------------------------------------------------------------------
# get_memory_stats
# ------------------------------------------------------------------


class TestGetMemoryStats:
    def test_counts_files(self, tmp_path: Path) -> None:
        from engram.mcp_tools import get_memory_stats

        for i in range(3):
            (tmp_path / f"note_{i}.md").write_text(f"---\ntopics:\n- T{i}\n---\nBody\n")

        stats = get_memory_stats()
        assert stats["entity_count"] >= 3


# ------------------------------------------------------------------
# consolidate_memory
# ------------------------------------------------------------------


class TestConsolidateMemory:
    def test_runs(self, tmp_path: Path) -> None:
        from engram.mcp_tools import consolidate_memory

        with patch(
            "engram.consolidator.MemoryConsolidator",
        ) as MockCons:
            inst = MockCons.return_value
            inst.run = MagicMock()
            inst.report = {"merged": [], "pruned": []}
            result = consolidate_memory()

        inst.run.assert_called_once()
        assert result["status"] == "complete"


class TestLintVault:
    def test_lint_vault_dry_run(self, _patch_dirs, monkeypatch):
        """lint_vault in dry_run mode returns findings without modifying."""
        from unittest.mock import patch

        from engram.mcp_tools import lint_vault

        # Patch ENGRAM_ROOT so report writes to tmp_path, not production vault
        monkeypatch.setattr("engram.config.ENGRAM_ROOT", str(_patch_dirs))

        # Create a test entity
        entities_dir = _patch_dirs
        (entities_dir / "test.md").write_text(
            "---\ntopics: [test]\nimportance: medium\n"
            "memory_type: episodic\naccess_count: 0\n"
            "created_at: '2026-04-01T00:00:00'\n"
            "summary: Test entity\n---\n\nTest content.\n"
        )

        with patch("engram.lint.analyze_batch", return_value=[]):
            result = lint_vault(dry_run=True)

        assert "error" not in result
        assert "auto_fixed" in result
