"""Unit tests for main server functionality."""

import os
from collections.abc import Generator
from pathlib import Path
from typing import Any
from unittest.mock import AsyncMock, MagicMock, NonCallableMock, patch

import pytest

from mcp_server_neurolorap.server import create_server, get_project_root


class ToolMock(AsyncMock):
    """Custom AsyncMock that matches the expected tool callable type."""

    def __init__(
        self,
        spec: list[str] | object | type[object] | None = None,
        wraps: Any | None = None,
        name: str | None = None,
        spec_set: list[str] | object | type[object] | None = None,
        parent: NonCallableMock | None = None,
        _spec_state: Any | None = None,
        _new_name: str = "",
        _new_parent: NonCallableMock | None = None,
        _spec_as_instance: bool = False,
        _eat_self: bool | None = None,
        unsafe: bool = False,
        **kwargs: Any,
    ) -> None:
        super().__init__(
            spec=spec,
            wraps=wraps,
            name=name,
            spec_set=spec_set,
            parent=parent,
            _spec_state=_spec_state,
            _new_name=_new_name,
            _new_parent=_new_parent,
            _spec_as_instance=_spec_as_instance,
            _eat_self=_eat_self,
            unsafe=unsafe,
            **kwargs,
        )
        self._collector: AsyncMock | None = None

    def set_collector(self, collector: AsyncMock | None) -> None:
        """Set the collector instance for this tool."""
        self._collector = collector


class MockFastMCP:
    """Mock FastMCP server."""

    def __init__(self, name: str) -> None:
        self.name = name
        self.tools = {"code_collector": ToolMock()}
        self.tool_called = False
        self.info = MagicMock()
        self.debug = MagicMock()
        self.error = MagicMock()
        self._tool_error: Exception | None = None

    def set_tool_error(self, error: Exception) -> None:
        """Set error to be raised during tool registration."""
        self._tool_error = error

    def tool(self, *args: Any, **kwargs: Any) -> Any:
        """Tool decorator."""
        if self._tool_error is not None:
            self.error(
                "Failed to initialize server: Test error", exc_info=True
            )
            raise self._tool_error

        def decorator(func: Any) -> Any:
            self.tool_called = True
            self.debug("Registering tool: code_collector")
            self.info("Starting MCP server: neurolorap")
            return self.tools["code_collector"]

        return decorator

    def run(self) -> None | Any:
        """Run the server."""
        return MagicMock()


@pytest.fixture
def mock_fastmcp() -> Generator[MockFastMCP, None, None]:
    """Mock FastMCP server."""
    with patch("mcp_server_neurolorap.server.FastMCP") as mock:
        mock_server = MockFastMCP("neurolorap")
        mock.return_value = mock_server
        yield mock_server


@pytest.fixture
def mock_logger() -> Generator[MagicMock, None, None]:
    """Mock logger."""
    with patch("mcp_server_neurolorap.server.logger") as mock_logger:
        yield mock_logger


def test_create_server(mock_fastmcp: MockFastMCP) -> None:
    """Test server creation and configuration."""
    server = create_server()
    assert server.name == "neurolorap"
    assert server.tool_called
    assert "code_collector" in server.tools


def test_project_root_environment(mock_logger: MagicMock) -> None:
    """Test project root environment variable handling."""
    # Test with environment variable set
    test_path = "/test/path"
    with patch.dict(os.environ, {"MCP_PROJECT_ROOT": test_path}, clear=True):
        root = get_project_root()
        assert str(root) == test_path
        mock_logger.info.assert_not_called()

    # Test with environment variable not set
    with patch.dict(os.environ, {}, clear=True):
        with patch("pathlib.Path.cwd") as mock_cwd:
            mock_cwd.return_value = Path("/current/dir")
            root = get_project_root()
            assert str(root) == "/current/dir"
            mock_logger.info.assert_called_with(
                "Set MCP_PROJECT_ROOT to: %s", Path("/current/dir")
            )


@pytest.mark.asyncio
async def test_server_initialization(mock_fastmcp: MockFastMCP) -> None:
    """Test server initialization process."""
    server = create_server()
    assert server.name == "neurolorap"
    mock_fastmcp.info.assert_called_with("Starting MCP server: neurolorap")


@pytest.mark.asyncio
async def test_server_tool_registration(mock_fastmcp: MockFastMCP) -> None:
    """Test tool registration process."""
    server = create_server()
    assert "code_collector" in server.tools
    mock_fastmcp.debug.assert_any_call("Registering tool: code_collector")


@pytest.mark.asyncio
async def test_server_error_handling(mock_fastmcp: MockFastMCP) -> None:
    """Test server error handling."""
    # Test initialization error
    test_error = Exception("Test error")
    mock_fastmcp.set_tool_error(test_error)
    with pytest.raises(Exception) as exc_info:
        create_server()
    assert str(exc_info.value) == "Test error"
    mock_fastmcp.error.assert_called_with(
        "Failed to initialize server: Test error", exc_info=True
    )
