"""Tests for exec tool."""

import pytest
from unittest.mock import AsyncMock, patch

from godotiq.tools.bridge.exec_code import TOOL_DESCRIPTION, exec_code


class TestExecCodeTool:
    @pytest.mark.asyncio
    async def test_empty_code(self):
        result = await exec_code(code="")
        assert result["status"] == "ERROR"

    @pytest.mark.asyncio
    async def test_no_code(self):
        result = await exec_code()
        assert result["status"] == "ERROR"
        assert "No code" in result["error"]

    @pytest.mark.asyncio
    async def test_valid_code(self):
        mock_bridge = AsyncMock()
        mock_bridge.request = AsyncMock(return_value={
            "status": "OK",
            "result": "42",
            "error": "",
        })
        with patch("godotiq.tools.bridge.exec_code.get_bridge", return_value=mock_bridge):
            result = await exec_code(code="func run():\n\treturn 42")
            assert result["status"] == "OK"
            assert result["result"] == "42"

    @pytest.mark.asyncio
    async def test_blocked_on_server(self):
        mock_bridge = AsyncMock()
        mock_bridge.request = AsyncMock(return_value={
            "status": "BLOCKED",
            "result": "",
            "error": "Code must start with 'func run():'",
        })
        with patch("godotiq.tools.bridge.exec_code.get_bridge", return_value=mock_bridge):
            result = await exec_code(code="print('hello')")
            assert result["status"] == "BLOCKED"

    @pytest.mark.asyncio
    async def test_compile_error(self):
        mock_bridge = AsyncMock()
        mock_bridge.request = AsyncMock(return_value={
            "status": "COMPILE_ERROR",
            "result": "",
            "error": "Compilation failed",
        })
        with patch("godotiq.tools.bridge.exec_code.get_bridge", return_value=mock_bridge):
            result = await exec_code(code="func run():\n\tvar x = :")
            assert result["status"] == "COMPILE_ERROR"

    @pytest.mark.asyncio
    async def test_default_timeout(self):
        mock_bridge = AsyncMock()
        mock_bridge.request = AsyncMock(return_value={"status": "OK"})
        with patch("godotiq.tools.bridge.exec_code.get_bridge", return_value=mock_bridge):
            await exec_code(code="func run():\n\treturn 1")
            call_kwargs = mock_bridge.request.call_args
            assert call_kwargs.kwargs.get("timeout") == 10.0 or call_kwargs[1].get("timeout") == 10.0

    @pytest.mark.asyncio
    async def test_custom_timeout(self):
        mock_bridge = AsyncMock()
        mock_bridge.request = AsyncMock(return_value={"status": "OK"})
        with patch("godotiq.tools.bridge.exec_code.get_bridge", return_value=mock_bridge):
            await exec_code(code="func run():\n\treturn 1", timeout_ms=3000)
            call_params = mock_bridge.request.call_args[1]["params"]
            assert call_params["timeout_ms"] == 3000

    @pytest.mark.asyncio
    async def test_params_passed_correctly(self):
        mock_bridge = AsyncMock()
        mock_bridge.request = AsyncMock(return_value={"status": "OK"})
        with patch("godotiq.tools.bridge.exec_code.get_bridge", return_value=mock_bridge):
            await exec_code(code="func run():\n\treturn 1", timeout_ms=7000)
            call_args = mock_bridge.request.call_args
            assert call_args[0][0] == "exec"
            params = call_args[1]["params"]
            assert params["code"] == "func run():\n\treturn 1"
            assert params["timeout_ms"] == 7000


class TestExecErrorDetail:
    """Tests for error_detail extraction from COMPILE_ERROR responses (Section 05)."""

    @pytest.mark.asyncio
    async def test_compile_error_with_error_detail_from_bridge(self):
        """When bridge returns error_detail list, Python side formats it."""
        mock_bridge = AsyncMock()
        mock_bridge.request = AsyncMock(return_value={
            "status": "COMPILE_ERROR",
            "result": "",
            "error": "Compilation failed (error 43: Parse Error)",
            "error_detail": [
                {"line": 5, "message": "Expected end of statement after expression"},
            ],
        })
        with patch("godotiq.tools.bridge.exec_code.get_bridge", return_value=mock_bridge):
            result = await exec_code(code="func run():\n\tvar x = :")
            assert result["status"] == "COMPILE_ERROR"
            assert "error_detail" in result
            assert "line 5" in result["error_detail"]
            assert "Expected end of statement" in result["error_detail"]

    @pytest.mark.asyncio
    async def test_compile_error_multiple_errors(self):
        """Multiple error entries are joined into a single string."""
        mock_bridge = AsyncMock()
        mock_bridge.request = AsyncMock(return_value={
            "status": "COMPILE_ERROR",
            "result": "",
            "error": "Compilation failed (error 43: Parse Error)",
            "error_detail": [
                {"line": 3, "message": "Unexpected token"},
                {"line": 7, "message": "Invalid assignment"},
            ],
        })
        with patch("godotiq.tools.bridge.exec_code.get_bridge", return_value=mock_bridge):
            result = await exec_code(code="func run():\n\tvar x = :")
            assert "line 3" in result["error_detail"]
            assert "line 7" in result["error_detail"]

    @pytest.mark.asyncio
    async def test_compile_error_no_error_detail(self):
        """When bridge returns no error_detail, response has no error_detail key."""
        mock_bridge = AsyncMock()
        mock_bridge.request = AsyncMock(return_value={
            "status": "COMPILE_ERROR",
            "result": "",
            "error": "Compilation failed (error 43)",
        })
        with patch("godotiq.tools.bridge.exec_code.get_bridge", return_value=mock_bridge):
            result = await exec_code(code="func run():\n\tvar x = :")
            assert result["status"] == "COMPILE_ERROR"
            assert "error_detail" not in result

    @pytest.mark.asyncio
    async def test_compile_error_empty_error_detail(self):
        """Empty error_detail list means no error_detail in response."""
        mock_bridge = AsyncMock()
        mock_bridge.request = AsyncMock(return_value={
            "status": "COMPILE_ERROR",
            "result": "",
            "error": "Compilation failed (error 43)",
            "error_detail": [],
        })
        with patch("godotiq.tools.bridge.exec_code.get_bridge", return_value=mock_bridge):
            result = await exec_code(code="func run():\n\tvar x = :")
            assert "error_detail" not in result

    @pytest.mark.asyncio
    async def test_ok_response_passes_through(self):
        """OK responses are not modified by error_detail logic."""
        mock_bridge = AsyncMock()
        mock_bridge.request = AsyncMock(return_value={
            "status": "OK",
            "result": "42",
            "error": "",
        })
        with patch("godotiq.tools.bridge.exec_code.get_bridge", return_value=mock_bridge):
            result = await exec_code(code="func run():\n\treturn 42")
            assert result["status"] == "OK"
            assert "error_detail" not in result


class TestExecAwaitWarning:
    """Tests for await/timeout warning in TOOL_DESCRIPTION and docstring (Section 06)."""

    def test_tool_description_contains_await_warning(self):
        """TOOL_DESCRIPTION must warn about await/timeout."""
        assert "WARNING" in TOOL_DESCRIPTION
        assert "await" in TOOL_DESCRIPTION
        assert "timeout" in TOOL_DESCRIPTION

    def test_exec_code_docstring_contains_await_warning(self):
        """The function docstring must mention the await/timeout caveat."""
        docstring = exec_code.__doc__
        assert docstring is not None
        assert "await" in docstring
        assert "timeout" in docstring
