"""Unit tests for cluster_status and cluster_handle tools."""

from __future__ import annotations

from unittest.mock import AsyncMock, MagicMock, patch

import pytest


@pytest.fixture
def mock_topology():
    from vmcluster_mcp.topology.schema import NetworkSpec, NodeSpec, SshSpec, TopologySpec

    return TopologySpec(
        cluster_name="testcluster",
        base_image="/images/ubuntu.qcow2",
        overlay_dir="/overlays",
        network=NetworkSpec(name="testcluster-net", bridge="virbr100", subnet="192.168.100.0/24"),
        nodes=[
            NodeSpec(name="node1", role="worker", vcpus=2, memory_mb=2048, ip="192.168.100.10"),
            NodeSpec(name="node2", role="worker", vcpus=2, memory_mb=2048, ip="192.168.100.11"),
        ],
        ssh=SshSpec(key_path="/etc/vmcluster/ssh/id_ed25519"),
    )


# ---------------------------------------------------------------------------
# cluster_status tests
# ---------------------------------------------------------------------------


@pytest.mark.asyncio
async def test_cluster_status_returns_cluster_name(mock_topology):
    """cluster_status result has the correct cluster_name."""
    with (
        patch("vmcluster_mcp.cluster.load_topology", return_value=mock_topology),
        patch("vmcluster_mcp.cluster._client") as mock_client,
        patch("vmcluster_mcp.cluster._wait_for_ssh", new=AsyncMock(return_value=(False, 0.1))),
    ):
        mock_dom = MagicMock()
        mock_client.lookup_domain = AsyncMock(return_value=mock_dom)
        mock_client.get_domain_state = AsyncMock(return_value="shut off")
        mock_client.lookup_network = AsyncMock(return_value=MagicMock())
        mock_client.is_network_active = AsyncMock(return_value=True)

        from vmcluster_mcp.cluster import cluster_status

        result = await cluster_status(cluster_name="testcluster")

    assert result.success is True
    assert result.result.cluster_name == "testcluster"


@pytest.mark.asyncio
async def test_cluster_status_network_active_true(mock_topology):
    """cluster_status reports network_active=True when the network is up."""
    with (
        patch("vmcluster_mcp.cluster.load_topology", return_value=mock_topology),
        patch("vmcluster_mcp.cluster._client") as mock_client,
        patch("vmcluster_mcp.cluster._wait_for_ssh", new=AsyncMock(return_value=(False, 0.1))),
    ):
        mock_dom = MagicMock()
        mock_client.lookup_domain = AsyncMock(return_value=mock_dom)
        mock_client.get_domain_state = AsyncMock(return_value="shut off")
        mock_client.lookup_network = AsyncMock(return_value=MagicMock())
        mock_client.is_network_active = AsyncMock(return_value=True)

        from vmcluster_mcp.cluster import cluster_status

        result = await cluster_status(cluster_name="testcluster")

    assert result.result.network_active is True


@pytest.mark.asyncio
async def test_cluster_status_per_node_domain_state(mock_topology):
    """cluster_status sets domain_state on each NodeStatus from libvirt."""
    with (
        patch("vmcluster_mcp.cluster.load_topology", return_value=mock_topology),
        patch("vmcluster_mcp.cluster._client") as mock_client,
        patch("vmcluster_mcp.cluster._wait_for_ssh", new=AsyncMock(return_value=(False, 0.1))),
    ):
        mock_dom = MagicMock()
        mock_client.lookup_domain = AsyncMock(return_value=mock_dom)
        mock_client.get_domain_state = AsyncMock(return_value="running")
        mock_client.lookup_network = AsyncMock(return_value=MagicMock())
        mock_client.is_network_active = AsyncMock(return_value=True)

        from vmcluster_mcp.cluster import cluster_status

        result = await cluster_status(cluster_name="testcluster")

    for node_status in result.result.nodes:
        assert node_status.domain_state == "running"


@pytest.mark.asyncio
async def test_cluster_status_ssh_checked_only_for_running_nodes(mock_topology):
    """cluster_status checks SSH for running nodes but not for shut-off nodes."""
    ssh_wait_mock = AsyncMock(return_value=(True, 0.5))
    with (
        patch("vmcluster_mcp.cluster.load_topology", return_value=mock_topology),
        patch("vmcluster_mcp.cluster._client") as mock_client,
        patch("vmcluster_mcp.cluster._wait_for_ssh", new=ssh_wait_mock),
    ):
        mock_dom = MagicMock()
        mock_client.lookup_domain = AsyncMock(return_value=mock_dom)
        # node1 running, node2 shut off
        mock_client.get_domain_state = AsyncMock(side_effect=["running", "shut off"])
        mock_client.lookup_network = AsyncMock(return_value=MagicMock())
        mock_client.is_network_active = AsyncMock(return_value=True)

        from vmcluster_mcp.cluster import cluster_status

        result = await cluster_status(cluster_name="testcluster")

    # SSH called once (only for running node1)
    assert ssh_wait_mock.call_count == 1
    node1_status = next(n for n in result.result.nodes if n.name == "node1")
    node2_status = next(n for n in result.result.nodes if n.name == "node2")
    assert node1_status.ssh_reachable is True
    assert node2_status.ssh_reachable is False


@pytest.mark.asyncio
async def test_cluster_status_not_running_no_ssh(mock_topology):
    """cluster_status does not attempt SSH for domains that are not running."""
    ssh_wait_mock = AsyncMock(return_value=(True, 0.5))
    with (
        patch("vmcluster_mcp.cluster.load_topology", return_value=mock_topology),
        patch("vmcluster_mcp.cluster._client") as mock_client,
        patch("vmcluster_mcp.cluster._wait_for_ssh", new=ssh_wait_mock),
    ):
        mock_dom = MagicMock()
        mock_client.lookup_domain = AsyncMock(return_value=mock_dom)
        mock_client.get_domain_state = AsyncMock(return_value="shut off")
        mock_client.lookup_network = AsyncMock(return_value=MagicMock())
        mock_client.is_network_active = AsyncMock(return_value=True)

        from vmcluster_mcp.cluster import cluster_status

        result = await cluster_status(cluster_name="testcluster")

    ssh_wait_mock.assert_not_called()
    for ns in result.result.nodes:
        assert ns.ssh_reachable is False


@pytest.mark.asyncio
async def test_cluster_status_unknown_domain(mock_topology):
    """cluster_status returns domain_state='unknown' when the domain is not found."""
    with (
        patch("vmcluster_mcp.cluster.load_topology", return_value=mock_topology),
        patch("vmcluster_mcp.cluster._client") as mock_client,
    ):
        mock_client.lookup_domain = AsyncMock(return_value=None)
        mock_client.lookup_network = AsyncMock(return_value=None)

        from vmcluster_mcp.cluster import cluster_status

        result = await cluster_status(cluster_name="testcluster")

    assert result.success is True
    for ns in result.result.nodes:
        assert ns.domain_state == "unknown"
        assert ns.ssh_reachable is False


@pytest.mark.asyncio
async def test_cluster_status_mixed_states(mock_topology):
    """cluster_status handles a mix of running and stopped nodes."""
    with (
        patch("vmcluster_mcp.cluster.load_topology", return_value=mock_topology),
        patch("vmcluster_mcp.cluster._client") as mock_client,
        patch("vmcluster_mcp.cluster._wait_for_ssh", new=AsyncMock(return_value=(True, 1.0))),
    ):
        mock_dom = MagicMock()
        mock_client.lookup_domain = AsyncMock(return_value=mock_dom)
        mock_client.get_domain_state = AsyncMock(side_effect=["running", "shut off"])
        mock_client.lookup_network = AsyncMock(return_value=MagicMock())
        mock_client.is_network_active = AsyncMock(return_value=True)

        from vmcluster_mcp.cluster import cluster_status

        result = await cluster_status(cluster_name="testcluster")

    assert result.success is True
    states = {ns.name: ns.domain_state for ns in result.result.nodes}
    assert states["node1"] == "running"
    assert states["node2"] == "shut off"


# ---------------------------------------------------------------------------
# cluster_handle tests
# ---------------------------------------------------------------------------


@pytest.mark.asyncio
async def test_cluster_handle_returns_cluster_name(mock_topology):
    """cluster_handle result has the correct cluster_name."""
    mock_conn = AsyncMock()
    mock_conn.__aenter__ = AsyncMock(return_value=mock_conn)
    mock_conn.__aexit__ = AsyncMock(return_value=False)
    mock_run_result = MagicMock()
    mock_run_result.stdout = "5.15.0-100-generic\n"
    mock_conn.run = AsyncMock(return_value=mock_run_result)

    with (
        patch("vmcluster_mcp.cluster.load_topology", return_value=mock_topology),
        patch("vmcluster_mcp.cluster._client") as mock_client,
        patch("vmcluster_mcp.cluster.asyncssh.connect", return_value=mock_conn),
    ):
        mock_dom = MagicMock()
        mock_client.lookup_domain = AsyncMock(return_value=mock_dom)
        mock_client.is_domain_active = AsyncMock(return_value=True)

        from vmcluster_mcp.cluster import cluster_handle

        result = await cluster_handle(cluster_name="testcluster")

    assert result.success is True
    assert result.result.cluster_name == "testcluster"


@pytest.mark.asyncio
async def test_cluster_handle_node_descriptors(mock_topology):
    """cluster_handle returns NodeDescriptor for each node with correct fields."""
    mock_conn = AsyncMock()
    mock_conn.__aenter__ = AsyncMock(return_value=mock_conn)
    mock_conn.__aexit__ = AsyncMock(return_value=False)
    mock_run_result = MagicMock()
    mock_run_result.stdout = "5.15.0-100-generic\n"
    mock_conn.run = AsyncMock(return_value=mock_run_result)

    with (
        patch("vmcluster_mcp.cluster.load_topology", return_value=mock_topology),
        patch("vmcluster_mcp.cluster._client") as mock_client,
        patch("vmcluster_mcp.cluster.asyncssh.connect", return_value=mock_conn),
    ):
        mock_dom = MagicMock()
        mock_client.lookup_domain = AsyncMock(return_value=mock_dom)
        mock_client.is_domain_active = AsyncMock(return_value=True)

        from vmcluster_mcp.cluster import cluster_handle

        result = await cluster_handle(cluster_name="testcluster")

    assert len(result.result.nodes) == 2
    nd = result.result.nodes[0]
    assert nd.name == "node1"
    assert nd.ip == "192.168.100.10"
    assert nd.ssh_port == 22
    assert nd.ssh_user == "root"
    assert nd.ssh_key_path == "/etc/vmcluster/ssh/id_ed25519"


@pytest.mark.asyncio
async def test_cluster_handle_artifact_path(mock_topology):
    """cluster_handle includes artifact_path."""
    mock_conn = AsyncMock()
    mock_conn.__aenter__ = AsyncMock(return_value=mock_conn)
    mock_conn.__aexit__ = AsyncMock(return_value=False)
    mock_run_result = MagicMock()
    mock_run_result.stdout = "5.15.0-100-generic\n"
    mock_conn.run = AsyncMock(return_value=mock_run_result)

    with (
        patch("vmcluster_mcp.cluster.load_topology", return_value=mock_topology),
        patch("vmcluster_mcp.cluster._client") as mock_client,
        patch("vmcluster_mcp.cluster.asyncssh.connect", return_value=mock_conn),
    ):
        mock_dom = MagicMock()
        mock_client.lookup_domain = AsyncMock(return_value=mock_dom)
        mock_client.is_domain_active = AsyncMock(return_value=True)

        from vmcluster_mcp.cluster import cluster_handle

        result = await cluster_handle(cluster_name="testcluster")

    assert result.result.artifact_path == "/opt/vmcluster/artifacts"


@pytest.mark.asyncio
async def test_cluster_handle_kernel_version_from_ssh(mock_topology):
    """cluster_handle fetches kernel_version via 'uname -r' on the first node."""
    mock_conn = AsyncMock()
    mock_conn.__aenter__ = AsyncMock(return_value=mock_conn)
    mock_conn.__aexit__ = AsyncMock(return_value=False)
    mock_run_result = MagicMock()
    mock_run_result.stdout = "5.15.0-100-generic\n"
    mock_conn.run = AsyncMock(return_value=mock_run_result)

    with (
        patch("vmcluster_mcp.cluster.load_topology", return_value=mock_topology),
        patch("vmcluster_mcp.cluster._client") as mock_client,
        patch("vmcluster_mcp.cluster.asyncssh.connect", return_value=mock_conn),
    ):
        mock_dom = MagicMock()
        mock_client.lookup_domain = AsyncMock(return_value=mock_dom)
        mock_client.is_domain_active = AsyncMock(return_value=True)

        from vmcluster_mcp.cluster import cluster_handle

        result = await cluster_handle(cluster_name="testcluster")

    assert result.result.kernel_version == "5.15.0-100-generic"
    mock_conn.run.assert_called_once_with("uname -r", check=True)


@pytest.mark.asyncio
async def test_cluster_handle_not_running_returns_error(mock_topology):
    """cluster_handle returns error when the cluster is not running."""
    with (
        patch("vmcluster_mcp.cluster.load_topology", return_value=mock_topology),
        patch("vmcluster_mcp.cluster._client") as mock_client,
    ):
        mock_client.lookup_domain = AsyncMock(return_value=None)

        from vmcluster_mcp.cluster import cluster_handle

        result = await cluster_handle(cluster_name="testcluster")

    assert result.success is False
    assert result.error.code == "CLUSTER_NOT_RUNNING"


@pytest.mark.asyncio
async def test_cluster_handle_ssh_key_path_from_topology(mock_topology):
    """cluster_handle NodeDescriptor.ssh_key_path comes from topology ssh.key_path."""
    mock_conn = AsyncMock()
    mock_conn.__aenter__ = AsyncMock(return_value=mock_conn)
    mock_conn.__aexit__ = AsyncMock(return_value=False)
    mock_run_result = MagicMock()
    mock_run_result.stdout = "5.15.0\n"
    mock_conn.run = AsyncMock(return_value=mock_run_result)

    with (
        patch("vmcluster_mcp.cluster.load_topology", return_value=mock_topology),
        patch("vmcluster_mcp.cluster._client") as mock_client,
        patch("vmcluster_mcp.cluster.asyncssh.connect", return_value=mock_conn),
    ):
        mock_dom = MagicMock()
        mock_client.lookup_domain = AsyncMock(return_value=mock_dom)
        mock_client.is_domain_active = AsyncMock(return_value=True)

        from vmcluster_mcp.cluster import cluster_handle

        result = await cluster_handle(cluster_name="testcluster")

    for nd in result.result.nodes:
        assert nd.ssh_key_path == mock_topology.ssh.key_path
