"""Tests for document tools module."""

from unittest.mock import AsyncMock, MagicMock

import pytest

from freecad_mcp.bridge.base import DocumentInfo, ExecutionResult


class TestDocumentTools:
    """Tests for document management tools."""

    @pytest.fixture
    def mock_mcp(self):
        """Create a mock MCP server that captures tool registrations."""
        mcp = MagicMock()
        mcp._registered_tools = {}

        def tool_decorator():
            def wrapper(func):
                mcp._registered_tools[func.__name__] = func
                return func

            return wrapper

        mcp.tool = tool_decorator
        return mcp

    @pytest.fixture
    def mock_bridge(self):
        """Create a mock FreeCAD bridge."""
        return AsyncMock()

    @pytest.fixture
    def register_tools(self, mock_mcp, mock_bridge):
        """Register document tools and return the registered functions."""
        from freecad_mcp.tools.documents import register_document_tools

        async def get_bridge():
            return mock_bridge

        register_document_tools(mock_mcp, get_bridge)
        return mock_mcp._registered_tools

    @pytest.mark.asyncio
    async def test_list_documents_empty(self, register_tools, mock_bridge):
        """list_documents should return empty list when no documents."""
        mock_bridge.get_documents = AsyncMock(return_value=[])

        list_documents = register_tools["list_documents"]
        result = await list_documents()

        assert result == []
        mock_bridge.get_documents.assert_called_once()

    @pytest.mark.asyncio
    async def test_list_documents_with_docs(self, register_tools, mock_bridge):
        """list_documents should return document info."""
        mock_docs = [
            DocumentInfo(
                name="Doc1",
                label="Document 1",
                path="/tmp/doc1.FCStd",
                objects=["Box", "Cylinder"],
                is_modified=False,
                active_object="Box",
            ),
            DocumentInfo(
                name="Doc2",
                label="Document 2",
                path=None,
                objects=["Sphere"],
                is_modified=True,
                active_object=None,
            ),
        ]
        mock_bridge.get_documents = AsyncMock(return_value=mock_docs)

        list_documents = register_tools["list_documents"]
        result = await list_documents()

        assert len(result) == 2
        assert result[0]["name"] == "Doc1"
        assert result[0]["object_count"] == 2
        assert result[0]["is_modified"] is False
        assert result[1]["name"] == "Doc2"
        assert result[1]["object_count"] == 1
        assert result[1]["is_modified"] is True

    @pytest.mark.asyncio
    async def test_get_active_document_none(self, register_tools, mock_bridge):
        """get_active_document should return None when no active document."""
        mock_bridge.get_active_document = AsyncMock(return_value=None)

        get_active_document = register_tools["get_active_document"]
        result = await get_active_document()

        assert result is None
        mock_bridge.get_active_document.assert_called_once()

    @pytest.mark.asyncio
    async def test_get_active_document_returns_info(self, register_tools, mock_bridge):
        """get_active_document should return document info when available."""
        mock_doc = DocumentInfo(
            name="ActiveDoc",
            label="Active Document",
            path="/tmp/active.FCStd",
            objects=["Part1", "Part2"],
            is_modified=True,
            active_object="Part1",
        )
        mock_bridge.get_active_document = AsyncMock(return_value=mock_doc)

        get_active_document = register_tools["get_active_document"]
        result = await get_active_document()

        assert result["name"] == "ActiveDoc"
        assert result["label"] == "Active Document"
        assert result["objects"] == ["Part1", "Part2"]
        assert result["is_modified"] is True

    @pytest.mark.asyncio
    async def test_create_document_default_name(self, register_tools, mock_bridge):
        """create_document should create with default name."""
        mock_doc = DocumentInfo(
            name="Unnamed",
            label="Unnamed",
            path=None,
            objects=[],
            is_modified=False,
        )
        mock_bridge.create_document = AsyncMock(return_value=mock_doc)

        create_document = register_tools["create_document"]
        result = await create_document()

        assert result["name"] == "Unnamed"
        mock_bridge.create_document.assert_called_once_with("Unnamed", None)

    @pytest.mark.asyncio
    async def test_create_document_with_name_and_label(
        self, register_tools, mock_bridge
    ):
        """create_document should use provided name and label."""
        mock_doc = DocumentInfo(
            name="MyPart",
            label="My Part Design",
            path=None,
            objects=[],
            is_modified=False,
        )
        mock_bridge.create_document = AsyncMock(return_value=mock_doc)

        create_document = register_tools["create_document"]
        result = await create_document(name="MyPart", label="My Part Design")

        assert result["name"] == "MyPart"
        assert result["label"] == "My Part Design"
        mock_bridge.create_document.assert_called_once_with("MyPart", "My Part Design")

    @pytest.mark.asyncio
    async def test_open_document(self, register_tools, mock_bridge):
        """open_document should open and return document info."""
        mock_doc = DocumentInfo(
            name="OpenedDoc",
            label="Opened Document",
            path="/tmp/test.FCStd",
            objects=["Box", "Fillet"],
            is_modified=False,
        )
        mock_bridge.open_document = AsyncMock(return_value=mock_doc)

        open_document = register_tools["open_document"]
        result = await open_document(path="/tmp/test.FCStd")

        assert result["name"] == "OpenedDoc"
        assert result["path"] == "/tmp/test.FCStd"
        assert result["objects"] == ["Box", "Fillet"]
        mock_bridge.open_document.assert_called_once_with("/tmp/test.FCStd")

    @pytest.mark.asyncio
    async def test_save_document_default(self, register_tools, mock_bridge):
        """save_document should save active document."""
        mock_bridge.save_document = AsyncMock(return_value="/tmp/saved.FCStd")

        save_document = register_tools["save_document"]
        result = await save_document()

        assert result["success"] is True
        assert result["path"] == "/tmp/saved.FCStd"
        mock_bridge.save_document.assert_called_once_with(None, None)

    @pytest.mark.asyncio
    async def test_save_document_with_path(self, register_tools, mock_bridge):
        """save_document should save to specified path."""
        mock_bridge.save_document = AsyncMock(return_value="/new/path.FCStd")

        save_document = register_tools["save_document"]
        result = await save_document(doc_name="MyDoc", path="/new/path.FCStd")

        assert result["success"] is True
        assert result["path"] == "/new/path.FCStd"
        mock_bridge.save_document.assert_called_once_with("MyDoc", "/new/path.FCStd")

    @pytest.mark.asyncio
    async def test_close_document_without_save(self, register_tools, mock_bridge):
        """close_document should close without saving by default."""
        mock_bridge.close_document = AsyncMock()

        close_document = register_tools["close_document"]
        result = await close_document(doc_name="TestDoc")

        assert result["success"] is True
        assert result["saved"] is False
        mock_bridge.close_document.assert_called_once_with("TestDoc")

    @pytest.mark.asyncio
    async def test_close_document_with_save(self, register_tools, mock_bridge):
        """close_document should save before closing when requested."""
        mock_bridge.save_document = AsyncMock(return_value="/tmp/doc.FCStd")
        mock_bridge.close_document = AsyncMock()

        close_document = register_tools["close_document"]
        result = await close_document(doc_name="TestDoc", save_changes=True)

        assert result["success"] is True
        assert result["saved"] is True
        mock_bridge.save_document.assert_called_once_with("TestDoc")
        mock_bridge.close_document.assert_called_once_with("TestDoc")

    @pytest.mark.asyncio
    async def test_close_document_save_failure(self, register_tools, mock_bridge):
        """close_document should still close even if save fails."""
        mock_bridge.save_document = AsyncMock(side_effect=Exception("Save failed"))
        mock_bridge.close_document = AsyncMock()

        close_document = register_tools["close_document"]
        result = await close_document(doc_name="TestDoc", save_changes=True)

        assert result["success"] is True
        assert result["saved"] is False
        mock_bridge.close_document.assert_called_once()

    @pytest.mark.asyncio
    async def test_recompute_document_success(self, register_tools, mock_bridge):
        """recompute_document should return success on recompute."""
        mock_bridge.execute_python = AsyncMock(
            return_value=ExecutionResult(
                success=True,
                result=True,
                stdout="",
                stderr="",
                execution_time_ms=5.0,
            )
        )

        recompute_document = register_tools["recompute_document"]
        result = await recompute_document(doc_name="TestDoc")

        assert result["success"] is True
        assert result.get("error") is None
        mock_bridge.execute_python.assert_called_once()

    @pytest.mark.asyncio
    async def test_recompute_document_failure(self, register_tools, mock_bridge):
        """recompute_document should return error on failure."""
        mock_bridge.execute_python = AsyncMock(
            return_value=ExecutionResult(
                success=False,
                result=None,
                stdout="",
                stderr="",
                execution_time_ms=5.0,
                error_type="ValueError",
                error_traceback="No document found",
            )
        )

        recompute_document = register_tools["recompute_document"]
        result = await recompute_document(doc_name="NonExistent")

        assert result["success"] is False
        assert result["error"] == "No document found"
