"""Tests for check_errors bridge wrapper and MCP tool registration."""

from __future__ import annotations

import asyncio
import pytest
from unittest.mock import AsyncMock, patch

from godotiq.bridge.connection import BridgeConnectionError, BridgeTimeoutError, BridgeError
from godotiq.tools.bridge.check_errors import check_errors


class TestCheckErrorsBridgeWrapper:
    """Bridge wrapper tests for check_errors."""

    async def test_clean_result(self) -> None:
        """Returns clean result when bridge reports no errors."""
        mock_bridge = AsyncMock()
        mock_bridge.request.return_value = {
            "errors": [],
            "total": 0,
            "scripts_checked": 5,
            "scope": "scene",
        }
        with patch("godotiq.tools.bridge.check_errors.get_bridge", return_value=mock_bridge):
            result = await check_errors()

        assert result["total"] == 0
        assert result["errors"] == []
        assert result["scripts_checked"] == 5

    async def test_forwards_errors(self) -> None:
        """Forwards errors array from bridge response."""
        mock_bridge = AsyncMock()
        mock_bridge.request.return_value = {
            "errors": [{"file": "res://player.gd", "line": 42, "error": "Identifier not found"}],
            "total": 1,
            "scripts_checked": 10,
            "scope": "scene",
        }
        with patch("godotiq.tools.bridge.check_errors.get_bridge", return_value=mock_bridge):
            result = await check_errors()

        assert result["total"] == 1
        assert result["errors"][0]["file"] == "res://player.gd"

    async def test_passes_scope_parameter(self) -> None:
        """Passes scope parameter to bridge.request."""
        mock_bridge = AsyncMock()
        mock_bridge.request.return_value = {"errors": [], "total": 0, "scripts_checked": 0, "scope": "project"}
        with patch("godotiq.tools.bridge.check_errors.get_bridge", return_value=mock_bridge):
            await check_errors(scope="project")

        mock_bridge.request.assert_called_once_with("check_errors", params={"scope": "project"})

    async def test_defaults_scope_to_scene(self) -> None:
        """Defaults scope to 'scene' when not specified."""
        mock_bridge = AsyncMock()
        mock_bridge.request.return_value = {"errors": [], "total": 0, "scripts_checked": 0, "scope": "scene"}
        with patch("godotiq.tools.bridge.check_errors.get_bridge", return_value=mock_bridge):
            await check_errors()

        mock_bridge.request.assert_called_once_with("check_errors", params={"scope": "scene"})

    async def test_propagates_connection_error(self) -> None:
        """BridgeConnectionError propagates to caller."""
        mock_bridge = AsyncMock()
        mock_bridge.request.side_effect = BridgeConnectionError("Not connected")
        with patch("godotiq.tools.bridge.check_errors.get_bridge", return_value=mock_bridge):
            with pytest.raises(BridgeConnectionError):
                await check_errors()

    async def test_propagates_timeout_error(self) -> None:
        """BridgeTimeoutError propagates to caller."""
        mock_bridge = AsyncMock()
        mock_bridge.request.side_effect = BridgeTimeoutError("Timed out")
        with patch("godotiq.tools.bridge.check_errors.get_bridge", return_value=mock_bridge):
            with pytest.raises(BridgeTimeoutError):
                await check_errors()

    async def test_propagates_bridge_error(self) -> None:
        """BridgeError propagates to caller."""
        mock_bridge = AsyncMock()
        mock_bridge.request.side_effect = BridgeError("CHECK_IN_PROGRESS", "Already running")
        with patch("godotiq.tools.bridge.check_errors.get_bridge", return_value=mock_bridge):
            with pytest.raises(BridgeError):
                await check_errors()


class TestCheckErrorsMCPRegistration:
    """MCP tool registration tests."""

    async def test_tool_registered(self) -> None:
        """godotiq_check_errors tool is registered in server tool list."""
        from godotiq.server import mcp
        tools = await mcp.list_tools()
        tool_names = [t.name for t in tools]
        assert "godotiq_check_errors" in tool_names

    async def test_catches_connection_error(self) -> None:
        """MCP wrapper catches BridgeConnectionError and returns error+hint."""
        from godotiq.tools.bridge import godotiq_check_errors
        mock_bridge = AsyncMock()
        mock_bridge.request.side_effect = BridgeConnectionError("Not connected")
        with patch("godotiq.tools.bridge.check_errors.get_bridge", return_value=mock_bridge):
            result = await godotiq_check_errors(scope="scene")

        assert "error" in result
        assert "hint" in result

    async def test_catches_timeout_error(self) -> None:
        """MCP wrapper catches BridgeTimeoutError and returns error+hint."""
        from godotiq.tools.bridge import godotiq_check_errors
        mock_bridge = AsyncMock()
        mock_bridge.request.side_effect = BridgeTimeoutError("Timed out")
        with patch("godotiq.tools.bridge.check_errors.get_bridge", return_value=mock_bridge):
            result = await godotiq_check_errors(scope="scene")

        assert "error" in result
        assert "hint" in result
        assert "timed out" in result["hint"].lower()

    async def test_catches_bridge_error(self) -> None:
        """MCP wrapper catches BridgeError and returns formatted error."""
        from godotiq.tools.bridge import godotiq_check_errors
        mock_bridge = AsyncMock()
        mock_bridge.request.side_effect = BridgeError("CHECK_IN_PROGRESS", "Already running")
        with patch("godotiq.tools.bridge.check_errors.get_bridge", return_value=mock_bridge):
            result = await godotiq_check_errors(scope="scene")

        assert "error" in result
        assert "[CHECK_IN_PROGRESS]" in result["error"]


    async def test_applies_output_limit(self) -> None:
        """MCP wrapper applies apply_output_limit to result."""
        from godotiq.tools.bridge import godotiq_check_errors
        mock_bridge = AsyncMock()
        mock_bridge.request.return_value = {
            "errors": [{"file": "res://a.gd", "line": 1, "error": "err"}],
            "total": 1,
            "scripts_checked": 5,
            "scope": "scene",
        }
        with patch("godotiq.tools.bridge.check_errors.get_bridge", return_value=mock_bridge):
            with patch("godotiq.tools.bridge.apply_output_limit", return_value={"limited": True}) as mock_limit:
                result = await godotiq_check_errors(scope="scene")

        mock_limit.assert_called_once()
        assert result == {"limited": True}


class TestRunPreCheckBehavior:
    """Tests for SCRIPT_ERRORS handling in the run tool."""

    async def test_run_reports_script_errors(self) -> None:
        """Run tool surfaces SCRIPT_ERRORS when bridge returns that error code."""
        from godotiq.tools.bridge import godotiq_run
        mock_bridge = AsyncMock()
        mock_bridge.request.side_effect = BridgeError(
            "SCRIPT_ERRORS",
            "Cannot start game: 2 script(s) have errors. Use godotiq_check_errors for details."
        )
        with patch("godotiq.tools.bridge.run.get_bridge", return_value=mock_bridge):
            result = await godotiq_run(action="play", scene="main")

        assert "error" in result
        assert "[SCRIPT_ERRORS]" in result["error"]

    async def test_run_succeeds_when_clean(self) -> None:
        """Run tool proceeds normally when bridge returns success."""
        from godotiq.tools.bridge import godotiq_run
        mock_bridge = AsyncMock()
        mock_bridge.request.return_value = {
            "action": "play", "success": True, "scene": "res://main.tscn", "waited_seconds": 1.0,
        }
        with patch("godotiq.tools.bridge.run.get_bridge", return_value=mock_bridge):
            result = await godotiq_run(action="play", scene="main")

        assert result["action"] == "play"
        assert result["success"] is True


class TestCheckErrorsTimeout:
    """Timeout configuration tests."""

    def test_timeout_configured(self) -> None:
        """check_errors has 15s timeout in TOOL_TIMEOUTS."""
        from godotiq.bridge.protocol import TOOL_TIMEOUTS
        assert TOOL_TIMEOUTS["check_errors"] == 15.0


class TestCheckErrorsEdgeCases:
    """Edge case tests for check_errors."""

    async def test_empty_project_zero_scripts(self) -> None:
        """Returns valid response when no scripts are found."""
        mock_bridge = AsyncMock()
        mock_bridge.request.return_value = {
            "errors": [],
            "total": 0,
            "scripts_checked": 0,
            "scope": "project",
        }
        with patch("godotiq.tools.bridge.check_errors.get_bridge", return_value=mock_bridge):
            result = await check_errors(scope="project")

        assert result["total"] == 0
        assert result["scripts_checked"] == 0
        assert result["errors"] == []

    async def test_single_file_scope(self) -> None:
        """Forwards specific res:// path as scope to bridge."""
        mock_bridge = AsyncMock()
        mock_bridge.request.return_value = {
            "errors": [],
            "total": 0,
            "scripts_checked": 1,
            "scope": "res://scripts/player.gd",
        }
        with patch("godotiq.tools.bridge.check_errors.get_bridge", return_value=mock_bridge):
            await check_errors(scope="res://scripts/player.gd")

        mock_bridge.request.assert_called_once_with(
            "check_errors", params={"scope": "res://scripts/player.gd"}
        )
