"""Unit tests for dmesg_mark MCP tool."""

from __future__ import annotations

import re
from unittest.mock import AsyncMock, MagicMock, patch

import pytest

from vmcluster_mcp.models import ToolResult


# --- Helpers ---


def _make_node(name: str, ip: str) -> MagicMock:
    node = MagicMock()
    node.name = name
    node.ip = ip
    return node


def _make_topology(node_specs=None):
    topo = MagicMock()
    topo.cluster_name = "test-cluster"
    topo.ssh.user = "root"
    topo.ssh.key_path = "/etc/vmcluster/ssh/id_ed25519"
    if node_specs is None:
        topo.nodes = [
            _make_node("node1", "10.0.0.1"),
            _make_node("node2", "10.0.0.2"),
            _make_node("node3", "10.0.0.3"),
        ]
    else:
        topo.nodes = node_specs
    return topo


_MARKER_RE = re.compile(
    r"^vmcluster-marker-[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$"
)


# --- Tests ---


@pytest.mark.asyncio
async def test_dmesg_mark_all_nodes_returns_success():
    """dmesg_mark with no nodes arg targets all topology nodes, returns success."""
    with (
        patch("vmcluster_mcp.observe.load_topology", return_value=_make_topology()),
        patch("vmcluster_mcp.observe._ssh_manager") as mock_mgr,
    ):
        mock_mgr.execute = AsyncMock(return_value=MagicMock())
        from vmcluster_mcp.observe import dmesg_mark

        result: ToolResult = await dmesg_mark.__wrapped__("test-cluster")

    assert result.success is True
    assert result.result is not None
    assert set(result.result.keys()) == {"node1", "node2", "node3"}


@pytest.mark.asyncio
async def test_dmesg_mark_all_nodes_same_marker():
    """All nodes in a single dmesg_mark call receive the same marker string."""
    with (
        patch("vmcluster_mcp.observe.load_topology", return_value=_make_topology()),
        patch("vmcluster_mcp.observe._ssh_manager") as mock_mgr,
    ):
        mock_mgr.execute = AsyncMock(return_value=MagicMock())
        from vmcluster_mcp.observe import dmesg_mark

        result: ToolResult = await dmesg_mark.__wrapped__("test-cluster")

    assert result.success is True
    markers = list(result.result.values())
    # All three nodes get the same marker
    assert len(set(markers)) == 1, "All nodes must receive the same marker string"
    assert _MARKER_RE.match(markers[0]), f"Marker format invalid: {markers[0]}"


@pytest.mark.asyncio
async def test_dmesg_mark_marker_starts_with_prefix():
    """Marker string starts with 'vmcluster-marker-' and contains a valid UUID."""
    with (
        patch("vmcluster_mcp.observe.load_topology", return_value=_make_topology()),
        patch("vmcluster_mcp.observe._ssh_manager") as mock_mgr,
    ):
        mock_mgr.execute = AsyncMock(return_value=MagicMock())
        from vmcluster_mcp.observe import dmesg_mark

        result: ToolResult = await dmesg_mark.__wrapped__("test-cluster")

    marker = next(iter(result.result.values()))
    assert marker.startswith("vmcluster-marker-")
    assert _MARKER_RE.match(marker)


@pytest.mark.asyncio
async def test_dmesg_mark_ssh_command_writes_to_kmsg():
    """SSH command sent to each node is: echo "vmcluster-marker-..." > /dev/kmsg"""
    with (
        patch("vmcluster_mcp.observe.load_topology", return_value=_make_topology()),
        patch("vmcluster_mcp.observe._ssh_manager") as mock_mgr,
    ):
        mock_mgr.execute = AsyncMock(return_value=MagicMock())
        from vmcluster_mcp.observe import dmesg_mark

        result: ToolResult = await dmesg_mark.__wrapped__("test-cluster")

    assert mock_mgr.execute.call_count == 3
    for call_args in mock_mgr.execute.call_args_list:
        cmd = call_args.kwargs.get("command") or call_args.args[4]
        assert "> /dev/kmsg" in cmd
        assert "vmcluster-marker-" in cmd


@pytest.mark.asyncio
async def test_dmesg_mark_single_node_subset():
    """dmesg_mark with nodes=['node1'] only targets node1."""
    with (
        patch("vmcluster_mcp.observe.load_topology", return_value=_make_topology()),
        patch("vmcluster_mcp.observe._ssh_manager") as mock_mgr,
    ):
        mock_mgr.execute = AsyncMock(return_value=MagicMock())
        from vmcluster_mcp.observe import dmesg_mark

        result: ToolResult = await dmesg_mark.__wrapped__("test-cluster", ["node1"])

    assert result.success is True
    assert set(result.result.keys()) == {"node1"}
    assert mock_mgr.execute.call_count == 1


@pytest.mark.asyncio
async def test_dmesg_mark_invalid_node_name():
    """dmesg_mark with unknown node name returns ToolResult(success=False, NODE_NOT_FOUND)."""
    with (
        patch("vmcluster_mcp.observe.load_topology", return_value=_make_topology()),
        patch("vmcluster_mcp.observe._ssh_manager") as mock_mgr,
    ):
        mock_mgr.execute = AsyncMock(return_value=MagicMock())
        from vmcluster_mcp.observe import dmesg_mark

        result: ToolResult = await dmesg_mark.__wrapped__("test-cluster", ["nonexistent"])

    assert result.success is False
    assert result.error is not None
    assert result.error.code == "NODE_NOT_FOUND"


@pytest.mark.asyncio
async def test_dmesg_mark_ssh_failure_one_node_best_effort():
    """SSH failure on one node still returns marker for other nodes (best-effort)."""
    exec_call_count = 0

    async def _side_effect(**kwargs):
        nonlocal exec_call_count
        exec_call_count += 1
        if kwargs.get("ip") == "10.0.0.2":
            raise OSError("Connection refused")
        return MagicMock()

    with (
        patch("vmcluster_mcp.observe.load_topology", return_value=_make_topology()),
        patch("vmcluster_mcp.observe._ssh_manager") as mock_mgr,
    ):
        mock_mgr.execute = _side_effect
        from vmcluster_mcp.observe import dmesg_mark

        result: ToolResult = await dmesg_mark.__wrapped__("test-cluster")

    # success because at least one node succeeded
    assert result.success is True
    # node2 (10.0.0.2) failed, node1 and node3 succeeded
    assert "node1" in result.result
    assert "node3" in result.result
    assert "node2" not in result.result


@pytest.mark.asyncio
async def test_dmesg_mark_all_nodes_fail():
    """When all nodes fail SSH, returns success=False with ALL_NODES_FAILED."""

    async def _always_fail(**kwargs):
        raise OSError("Connection refused")

    with (
        patch("vmcluster_mcp.observe.load_topology", return_value=_make_topology()),
        patch("vmcluster_mcp.observe._ssh_manager") as mock_mgr,
    ):
        mock_mgr.execute = _always_fail
        from vmcluster_mcp.observe import dmesg_mark

        result: ToolResult = await dmesg_mark.__wrapped__("test-cluster")

    assert result.success is False
    assert result.error is not None
    assert result.error.code == "ALL_NODES_FAILED"


@pytest.mark.asyncio
async def test_dmesg_mark_unique_marker_per_call():
    """Two separate dmesg_mark calls produce different marker strings."""
    with (
        patch("vmcluster_mcp.observe.load_topology", return_value=_make_topology()),
        patch("vmcluster_mcp.observe._ssh_manager") as mock_mgr,
    ):
        mock_mgr.execute = AsyncMock(return_value=MagicMock())
        from vmcluster_mcp.observe import dmesg_mark

        result1: ToolResult = await dmesg_mark.__wrapped__("test-cluster")
        result2: ToolResult = await dmesg_mark.__wrapped__("test-cluster")

    marker1 = next(iter(result1.result.values()))
    marker2 = next(iter(result2.result.values()))
    assert marker1 != marker2, "Each call must generate a fresh UUID"
