"""Tests for node_ops bridge tool implementation."""

from __future__ import annotations

import pytest
from unittest.mock import AsyncMock, patch

from godotiq.tools.bridge.node_ops import node_ops

MAX_OPS = 50


class TestNodeOps:
    """node_ops parameter validation and bridge delegation."""

    async def test_valid_operations_forwarded(self) -> None:
        """A valid operations list is forwarded to bridge.request('node_ops', ...)."""
        ops = [{"op": "move", "node": "Player", "position": [1, 2, 3]}]
        mock_bridge = AsyncMock()
        mock_bridge.request.return_value = {
            "results": [{"op": "move", "node": "Player", "status": "ok"}]
        }
        with patch("godotiq.tools.bridge.node_ops.get_bridge", return_value=mock_bridge):
            result = await node_ops(operations=ops)

        mock_bridge.request.assert_called_once_with(
            "node_ops", params={"operations": ops}
        )
        assert result["results"][0]["status"] == "ok"

    async def test_empty_operations_rejected(self) -> None:
        """An empty operations list raises ValueError."""
        with pytest.raises(ValueError, match="[Ee]mpty|[Nn]o operations|at least"):
            await node_ops(operations=[])

    async def test_limit_enforced(self) -> None:
        """An operations list exceeding 50 items raises ValueError."""
        ops = [{"op": "move", "node": f"N{i}", "position": [0, 0, 0]} for i in range(MAX_OPS + 1)]
        with pytest.raises(ValueError, match="50|limit|exceed"):
            await node_ops(operations=ops)

    async def test_exactly_50_operations_allowed(self) -> None:
        """Exactly 50 operations should be accepted (boundary test)."""
        ops = [{"op": "move", "node": f"N{i}", "position": [0, 0, 0]} for i in range(MAX_OPS)]
        mock_bridge = AsyncMock()
        mock_bridge.request.return_value = {"results": []}
        with patch("godotiq.tools.bridge.node_ops.get_bridge", return_value=mock_bridge):
            await node_ops(operations=ops)

        mock_bridge.request.assert_called_once()

    async def test_returns_bridge_response(self) -> None:
        """The bridge response dict is returned as-is."""
        expected = {"results": [{"op": "delete", "node": "Enemy", "status": "ok"}]}
        mock_bridge = AsyncMock()
        mock_bridge.request.return_value = expected
        with patch("godotiq.tools.bridge.node_ops.get_bridge", return_value=mock_bridge):
            result = await node_ops(operations=[{"op": "delete", "node": "Enemy"}])

        assert result == expected

    async def test_bridge_connection_error_propagates(self) -> None:
        """BridgeConnectionError from get_bridge propagates to caller."""
        from godotiq.bridge.connection import BridgeConnectionError

        with patch(
            "godotiq.tools.bridge.node_ops.get_bridge",
            side_effect=BridgeConnectionError("Not connected"),
        ):
            with pytest.raises(BridgeConnectionError):
                await node_ops(operations=[{"op": "move", "node": "X", "position": [0, 0, 0]}])

    async def test_bridge_timeout_error_propagates(self) -> None:
        """BridgeTimeoutError from bridge.request propagates to caller."""
        from godotiq.bridge.connection import BridgeTimeoutError

        mock_bridge = AsyncMock()
        mock_bridge.request.side_effect = BridgeTimeoutError("Timed out")
        with patch("godotiq.tools.bridge.node_ops.get_bridge", return_value=mock_bridge):
            with pytest.raises(BridgeTimeoutError):
                await node_ops(operations=[{"op": "move", "node": "X", "position": [0, 0, 0]}])

    async def test_bridge_error_propagates(self) -> None:
        """BridgeError from bridge.request propagates to caller."""
        from godotiq.bridge.connection import BridgeError

        mock_bridge = AsyncMock()
        mock_bridge.request.side_effect = BridgeError(code="INVALID_OP", error_message="Unknown op type")
        with patch("godotiq.tools.bridge.node_ops.get_bridge", return_value=mock_bridge):
            with pytest.raises(BridgeError):
                await node_ops(operations=[{"op": "invalid", "node": "X"}])
