"""Unit tests for node_exec MCP tool."""

from __future__ import annotations

from unittest.mock import AsyncMock, MagicMock, patch

import pytest

from vmcluster_mcp.models import ExecResult, ToolResult


# --- Test fixtures ---

def _make_topology(nodes=None):
    """Build a minimal TopologySpec-like mock."""
    topo = MagicMock()
    topo.cluster_name = "test-cluster"
    topo.ssh.user = "root"
    topo.ssh.key_path = "/etc/vmcluster/ssh/id_ed25519"

    if nodes is None:
        node = MagicMock()
        node.name = "node1"
        node.ip = "10.0.0.1"
        topo.nodes = [node]
    else:
        topo.nodes = nodes

    return topo


def _make_exec_result(exit_code=0, stdout="ok", stderr="", duration_s=0.1, timed_out=False):
    return ExecResult(
        exit_code=exit_code,
        stdout=stdout,
        stderr=stderr,
        duration_s=duration_s,
        timed_out=timed_out,
    )


@pytest.mark.asyncio
async def test_node_exec_valid_node_returns_success():
    """Test 1: node_exec with valid cluster_name and node_name returns ToolResult success=True."""
    exec_result = _make_exec_result(exit_code=0, stdout="hello")

    with (
        patch("vmcluster_mcp.exec.load_topology", return_value=_make_topology()),
        patch("vmcluster_mcp.exec._ssh_manager") as mock_mgr,
    ):
        mock_mgr.execute = AsyncMock(return_value=exec_result)

        from vmcluster_mcp.exec import node_exec
        result: ToolResult = await node_exec.__wrapped__("test-cluster", "node1", "echo hello")

    assert result.success is True
    assert result.result is exec_result
    assert result.result.exit_code == 0
    assert result.result.stdout == "hello"


@pytest.mark.asyncio
async def test_node_exec_unknown_node_returns_node_not_found():
    """Test 2: node_exec with non-existent node_name returns ToolResult success=False, NODE_NOT_FOUND."""
    with (
        patch("vmcluster_mcp.exec.load_topology", return_value=_make_topology()),
        patch("vmcluster_mcp.exec._ssh_manager"),
    ):
        from vmcluster_mcp.exec import node_exec
        result: ToolResult = await node_exec.__wrapped__("test-cluster", "no-such-node", "echo hi")

    assert result.success is False
    assert result.error is not None
    assert result.error.code == "NODE_NOT_FOUND"
    assert "no-such-node" in result.error.message


@pytest.mark.asyncio
async def test_node_exec_nonzero_exit_code_is_success():
    """Test 3: Non-zero exit code returns success=True with ExecResult.exit_code != 0."""
    exec_result = _make_exec_result(exit_code=1, stdout="", stderr="error msg")

    with (
        patch("vmcluster_mcp.exec.load_topology", return_value=_make_topology()),
        patch("vmcluster_mcp.exec._ssh_manager") as mock_mgr,
    ):
        mock_mgr.execute = AsyncMock(return_value=exec_result)

        from vmcluster_mcp.exec import node_exec
        result: ToolResult = await node_exec.__wrapped__("test-cluster", "node1", "false")

    # Non-zero exit is NOT a tool failure
    assert result.success is True
    assert result.result.exit_code == 1


@pytest.mark.asyncio
async def test_node_exec_timeout_returns_timed_out():
    """Test 4: SSH timeout returns success=True with ExecResult.timed_out=True."""
    exec_result = _make_exec_result(exit_code=-1, timed_out=True, stderr="Command timed out")

    with (
        patch("vmcluster_mcp.exec.load_topology", return_value=_make_topology()),
        patch("vmcluster_mcp.exec._ssh_manager") as mock_mgr,
    ):
        mock_mgr.execute = AsyncMock(return_value=exec_result)

        from vmcluster_mcp.exec import node_exec
        result: ToolResult = await node_exec.__wrapped__("test-cluster", "node1", "sleep 999", 1)

    assert result.success is True
    assert result.result.timed_out is True


@pytest.mark.asyncio
async def test_node_exec_ssh_exception_returns_failure():
    """Test 5: SSHConnectionManager raising an exception returns ToolResult success=False."""
    with (
        patch("vmcluster_mcp.exec.load_topology", return_value=_make_topology()),
        patch("vmcluster_mcp.exec._ssh_manager") as mock_mgr,
    ):
        mock_mgr.execute = AsyncMock(side_effect=RuntimeError("unexpected SSH error"))

        from vmcluster_mcp import exec as exec_module
        result: ToolResult = await exec_module.node_exec("test-cluster", "node1", "cmd")

    # structured_tool_handler wraps to ToolResult(success=False)
    assert result.success is False


@pytest.mark.asyncio
async def test_node_exec_passes_timeout_to_ssh_manager():
    """Test 6: node_exec passes timeout_s parameter to SSHConnectionManager.execute()."""
    exec_result = _make_exec_result()

    with (
        patch("vmcluster_mcp.exec.load_topology", return_value=_make_topology()),
        patch("vmcluster_mcp.exec._ssh_manager") as mock_mgr,
    ):
        mock_mgr.execute = AsyncMock(return_value=exec_result)

        from vmcluster_mcp.exec import node_exec
        await node_exec.__wrapped__("test-cluster", "node1", "cmd", 60)

    call_kwargs = mock_mgr.execute.call_args
    assert call_kwargs.kwargs.get("timeout_s") == 60 or (
        len(call_kwargs.args) >= 6 and call_kwargs.args[5] == 60
    )
