"""Tests for the snapshot engine."""

from __future__ import annotations

import json

import pytest

from mcp_forge._types import ContentBlock, ToolInfo, ToolResult
from mcp_forge.snapshot import SnapshotManager, SnapshotMismatchError


@pytest.fixture
def snap_dir(tmp_path):
    return tmp_path / "__snapshots__"


@pytest.fixture
def manager(snap_dir):
    return SnapshotManager(snap_dir=snap_dir, test_name="test_example", update=False)


@pytest.fixture
def update_manager(snap_dir):
    return SnapshotManager(snap_dir=snap_dir, test_name="test_example", update=True)


def _make_result(text: str = "hello", is_error: bool = False) -> ToolResult:
    return ToolResult(
        content=[ContentBlock(type="text", text=text)],
        is_error=is_error,
    )


class TestSnapshotCreation:
    """Test first-run snapshot creation."""

    def test_creates_snapshot_on_first_run(self, manager):
        """First assertion creates the snapshot file."""
        result = _make_result("hello world")
        manager.assert_match(result)

        snap_files = list(manager.snap_dir.glob("*.json"))
        assert len(snap_files) == 1

    def test_snapshot_content_matches(self, manager):
        """Snapshot file contains the expected data."""
        result = _make_result("hello world")
        manager.assert_match(result)

        snap_file = list(manager.snap_dir.glob("*.json"))[0]
        data = json.loads(snap_file.read_text())
        assert data["content"][0]["text"] == "hello world"
        assert data["is_error"] is False


class TestSnapshotComparison:
    """Test subsequent-run snapshot comparison."""

    def test_matching_snapshot_passes(self, manager):
        """Identical result on second run passes."""
        result = _make_result("hello")
        manager.assert_match(result)  # creates

        manager2 = SnapshotManager(
            snap_dir=manager.snap_dir, test_name="test_example", update=False
        )
        manager2.assert_match(result)  # compares — should pass

    def test_mismatched_snapshot_raises(self, manager):
        """Different result on second run raises SnapshotMismatchError."""
        result1 = _make_result("hello")
        manager.assert_match(result1)

        manager2 = SnapshotManager(
            snap_dir=manager.snap_dir, test_name="test_example", update=False
        )
        result2 = _make_result("goodbye")

        with pytest.raises(SnapshotMismatchError, match="Snapshot mismatch"):
            manager2.assert_match(result2)

    def test_update_mode_overwrites(self, snap_dir):
        """Update mode overwrites the existing snapshot."""
        mgr1 = SnapshotManager(snap_dir=snap_dir, test_name="test_example", update=False)
        mgr1.assert_match(_make_result("original"))

        mgr2 = SnapshotManager(snap_dir=snap_dir, test_name="test_example", update=True)
        mgr2.assert_match(_make_result("updated"))

        mgr3 = SnapshotManager(snap_dir=snap_dir, test_name="test_example", update=False)
        mgr3.assert_match(_make_result("updated"))  # should pass


class TestSnapshotIgnoreKeys:
    """Test ignore_keys functionality."""

    def test_ignores_specified_keys(self, manager):
        """Ignored keys don't cause mismatch."""
        result1 = ToolResult(
            content=[ContentBlock(type="text", text="hello")],
            is_error=False,
            raw={"timestamp": "2026-01-01"},
        )
        manager.assert_match(result1)

        manager2 = SnapshotManager(
            snap_dir=manager.snap_dir, test_name="test_example", update=False
        )
        result2 = ToolResult(
            content=[ContentBlock(type="text", text="hello")],
            is_error=False,
            raw={"timestamp": "2026-12-31"},
        )
        # The "raw" field isn't in to_dict() by default, but the test
        # validates the ignore_keys mechanism works for content-level fields
        manager2.assert_match(result2)


class TestSchemaSnapshot:
    """Test schema snapshotting."""

    def test_schema_snapshot_creation(self, manager):
        """Schema snapshot is created on first run."""
        tools = [
            ToolInfo(name="tool_a", description="A", input_schema={"type": "object"}),
            ToolInfo(name="tool_b", description="B", input_schema={"type": "object"}),
        ]
        manager.assert_schema_unchanged(tools)

        snap_files = list(manager.snap_dir.glob("*schema*.json"))
        assert len(snap_files) == 1

    def test_schema_change_detected(self, manager):
        """Schema changes are detected."""
        tools_v1 = [
            ToolInfo(name="tool_a", description="A", input_schema={"type": "object"}),
        ]
        manager.assert_schema_unchanged(tools_v1)

        manager2 = SnapshotManager(
            snap_dir=manager.snap_dir, test_name="test_example", update=False
        )
        tools_v2 = [
            ToolInfo(
                name="tool_a",
                description="A (updated)",
                input_schema={"type": "object"},
            ),
        ]

        with pytest.raises(SnapshotMismatchError, match="schema has changed"):
            manager2.assert_schema_unchanged(tools_v2)
