"""Unit tests for cluster_start, cluster_stop, and cluster_destroy lifecycle 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_start tests
# ---------------------------------------------------------------------------


@pytest.mark.asyncio
async def test_cluster_start_boots_all_domains(mock_topology):
    """cluster_start calls start_domain for all 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.is_domain_active = AsyncMock(return_value=False)
        mock_client.start_domain = AsyncMock()
        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_start

        result = await cluster_start(cluster_name="testcluster", wait_for_ssh=True)

    assert result.success is True
    assert mock_client.start_domain.call_count == 2


@pytest.mark.asyncio
async def test_cluster_start_skips_already_active_domain(mock_topology):
    """cluster_start does not call start_domain on domains that are already running."""
    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, 0.5))),
    ):
        mock_dom = MagicMock()
        mock_client.lookup_domain = AsyncMock(return_value=mock_dom)
        mock_client.is_domain_active = AsyncMock(return_value=True)  # already running
        mock_client.start_domain = AsyncMock()
        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_start

        result = await cluster_start(cluster_name="testcluster", wait_for_ssh=False)

    assert result.success is True
    mock_client.start_domain.assert_not_called()


@pytest.mark.asyncio
async def test_cluster_start_no_wait_returns_immediately(mock_topology):
    """cluster_start with wait_for_ssh=False does not call _wait_for_ssh."""
    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()) as mock_ssh_wait,
    ):
        mock_dom = MagicMock()
        mock_client.lookup_domain = AsyncMock(return_value=mock_dom)
        mock_client.is_domain_active = AsyncMock(return_value=False)
        mock_client.start_domain = AsyncMock()
        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_start

        result = await cluster_start(cluster_name="testcluster", wait_for_ssh=False)

    assert result.success is True
    mock_ssh_wait.assert_not_called()


@pytest.mark.asyncio
async def test_cluster_start_ssh_success_sets_reachable(mock_topology):
    """cluster_start sets ssh_reachable=True for nodes that respond to SSH."""
    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, 2.0))),
    ):
        mock_dom = MagicMock()
        mock_client.lookup_domain = AsyncMock(return_value=mock_dom)
        mock_client.is_domain_active = AsyncMock(return_value=False)
        mock_client.start_domain = AsyncMock()
        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_start

        result = await cluster_start(cluster_name="testcluster", wait_for_ssh=True)

    assert result.success is True
    for node_status in result.result.nodes:
        assert node_status.ssh_reachable is True


@pytest.mark.asyncio
async def test_cluster_start_ssh_timeout_returns_failure(mock_topology):
    """cluster_start returns success=False if any node fails SSH (D-04)."""
    call_count = [0]

    async def mock_wait(ip, port, user, key_path, timeout_s):
        call_count[0] += 1
        if ip == "192.168.100.10":
            return (False, float(timeout_s))  # node1 times out
        return (True, 2.0)

    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=mock_wait),
    ):
        mock_dom = MagicMock()
        mock_client.lookup_domain = AsyncMock(return_value=mock_dom)
        mock_client.is_domain_active = AsyncMock(return_value=False)
        mock_client.start_domain = AsyncMock()
        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_start

        result = await cluster_start(cluster_name="testcluster", wait_for_ssh=True)

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


# ---------------------------------------------------------------------------
# cluster_stop tests
# ---------------------------------------------------------------------------


@pytest.mark.asyncio
async def test_cluster_stop_shutdown_mode(mock_topology):
    """cluster_stop mode='shutdown' calls shutdown_domain."""
    with (
        patch("vmcluster_mcp.cluster.load_topology", return_value=mock_topology),
        patch("vmcluster_mcp.cluster._client") as mock_client,
    ):
        mock_dom = MagicMock()
        mock_client.lookup_domain = AsyncMock(return_value=mock_dom)
        mock_client.is_domain_active = AsyncMock(return_value=True)
        mock_client.shutdown_domain = AsyncMock()
        mock_client.destroy_domain = AsyncMock()
        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_stop

        result = await cluster_stop(cluster_name="testcluster", mode="shutdown")

    assert result.success is True
    assert mock_client.shutdown_domain.call_count == 2
    mock_client.destroy_domain.assert_not_called()


@pytest.mark.asyncio
async def test_cluster_stop_destroy_mode(mock_topology):
    """cluster_stop mode='destroy' calls destroy_domain."""
    with (
        patch("vmcluster_mcp.cluster.load_topology", return_value=mock_topology),
        patch("vmcluster_mcp.cluster._client") as mock_client,
    ):
        mock_dom = MagicMock()
        mock_client.lookup_domain = AsyncMock(return_value=mock_dom)
        mock_client.is_domain_active = AsyncMock(return_value=True)
        mock_client.shutdown_domain = AsyncMock()
        mock_client.destroy_domain = AsyncMock()
        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_stop

        result = await cluster_stop(cluster_name="testcluster", mode="destroy")

    assert result.success is True
    assert mock_client.destroy_domain.call_count == 2
    mock_client.shutdown_domain.assert_not_called()


@pytest.mark.asyncio
async def test_cluster_stop_skips_inactive_domains(mock_topology):
    """cluster_stop does not call shutdown/destroy on domains that are already stopped."""
    with (
        patch("vmcluster_mcp.cluster.load_topology", return_value=mock_topology),
        patch("vmcluster_mcp.cluster._client") as mock_client,
    ):
        mock_dom = MagicMock()
        mock_client.lookup_domain = AsyncMock(return_value=mock_dom)
        mock_client.is_domain_active = AsyncMock(return_value=False)  # already stopped
        mock_client.shutdown_domain = AsyncMock()
        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_stop

        result = await cluster_stop(cluster_name="testcluster", mode="shutdown")

    assert result.success is True
    mock_client.shutdown_domain.assert_not_called()


# ---------------------------------------------------------------------------
# cluster_destroy tests
# ---------------------------------------------------------------------------


@pytest.mark.asyncio
async def test_cluster_destroy_undefines_domains(mock_topology):
    """cluster_destroy undefines all domains."""
    with (
        patch("vmcluster_mcp.cluster.load_topology", return_value=mock_topology),
        patch("vmcluster_mcp.cluster._client") as mock_client,
        patch("shutil.rmtree"),
    ):
        mock_dom = MagicMock()
        mock_client.lookup_domain = AsyncMock(return_value=mock_dom)
        mock_client.is_domain_active = AsyncMock(return_value=False)
        mock_client.destroy_domain = AsyncMock()
        mock_client.undefine_domain = AsyncMock()
        mock_net = MagicMock()
        mock_client.lookup_network = AsyncMock(return_value=mock_net)
        mock_client.is_network_active = AsyncMock(return_value=False)
        mock_client.destroy_network = AsyncMock()
        mock_client.undefine_network = AsyncMock()

        from vmcluster_mcp.cluster import cluster_destroy

        result = await cluster_destroy(cluster_name="testcluster", remove_overlays=True)

    assert result.success is True
    assert mock_client.undefine_domain.call_count == 2


@pytest.mark.asyncio
async def test_cluster_destroy_force_stops_active_domains(mock_topology):
    """cluster_destroy force-stops active domains before undefining."""
    with (
        patch("vmcluster_mcp.cluster.load_topology", return_value=mock_topology),
        patch("vmcluster_mcp.cluster._client") as mock_client,
        patch("shutil.rmtree"),
    ):
        mock_dom = MagicMock()
        mock_client.lookup_domain = AsyncMock(return_value=mock_dom)
        mock_client.is_domain_active = AsyncMock(return_value=True)  # still running
        mock_client.destroy_domain = AsyncMock()
        mock_client.undefine_domain = AsyncMock()
        mock_net = MagicMock()
        mock_client.lookup_network = AsyncMock(return_value=mock_net)
        mock_client.is_network_active = AsyncMock(return_value=False)
        mock_client.destroy_network = AsyncMock()
        mock_client.undefine_network = AsyncMock()

        from vmcluster_mcp.cluster import cluster_destroy

        result = await cluster_destroy(cluster_name="testcluster", remove_overlays=False)

    assert result.success is True
    assert mock_client.destroy_domain.call_count == 2


@pytest.mark.asyncio
async def test_cluster_destroy_always_removes_network(mock_topology):
    """cluster_destroy always removes the network (D-01), regardless of remove_overlays."""
    with (
        patch("vmcluster_mcp.cluster.load_topology", return_value=mock_topology),
        patch("vmcluster_mcp.cluster._client") as mock_client,
        patch("shutil.rmtree"),
    ):
        mock_client.lookup_domain = AsyncMock(return_value=None)  # no domains
        mock_net = MagicMock()
        mock_client.lookup_network = AsyncMock(return_value=mock_net)
        mock_client.is_network_active = AsyncMock(return_value=True)
        mock_client.destroy_network = AsyncMock()
        mock_client.undefine_network = AsyncMock()

        from vmcluster_mcp.cluster import cluster_destroy

        result = await cluster_destroy(cluster_name="testcluster", remove_overlays=False)

    assert result.success is True
    mock_client.destroy_network.assert_called_once()
    mock_client.undefine_network.assert_called_once()


@pytest.mark.asyncio
async def test_cluster_destroy_remove_overlays_true(mock_topology, tmp_path):
    """cluster_destroy with remove_overlays=True deletes the overlay directory."""
    with (
        patch("vmcluster_mcp.cluster.load_topology", return_value=mock_topology),
        patch("vmcluster_mcp.cluster._client") as mock_client,
        patch("vmcluster_mcp.cluster.config") as mock_config,
        patch("shutil.rmtree") as mock_rmtree,
    ):
        mock_config.topology_dir = "/etc/vmcluster/topologies"
        mock_config.overlay_dir = str(tmp_path)
        mock_client.lookup_domain = AsyncMock(return_value=None)
        mock_client.lookup_network = AsyncMock(return_value=None)

        from vmcluster_mcp.cluster import cluster_destroy

        result = await cluster_destroy(cluster_name="testcluster", remove_overlays=True)

    assert result.success is True
    mock_rmtree.assert_called_once()
    args = mock_rmtree.call_args[0][0]
    assert "testcluster" in args


@pytest.mark.asyncio
async def test_cluster_destroy_remove_overlays_false(mock_topology, tmp_path):
    """cluster_destroy with remove_overlays=False leaves overlay files intact."""
    with (
        patch("vmcluster_mcp.cluster.load_topology", return_value=mock_topology),
        patch("vmcluster_mcp.cluster._client") as mock_client,
        patch("shutil.rmtree") as mock_rmtree,
    ):
        mock_client.lookup_domain = AsyncMock(return_value=None)
        mock_client.lookup_network = AsyncMock(return_value=None)

        from vmcluster_mcp.cluster import cluster_destroy

        result = await cluster_destroy(cluster_name="testcluster", remove_overlays=False)

    assert result.success is True
    mock_rmtree.assert_not_called()
