"""Unit tests for vmcluster_mcp/cluster/libvirt_client.py.

All libvirt calls are mocked — no real hypervisor required.
"""

from __future__ import annotations

import re
from unittest.mock import MagicMock, patch

import pytest

from vmcluster_mcp.cluster.libvirt_client import (
    LibvirtClient,
    domain_name,
    generate_mac,
    map_domain_state,
)


# ---------------------------------------------------------------------------
# Utility function tests
# ---------------------------------------------------------------------------


def test_domain_name_basic():
    assert domain_name("mycluster", "node1") == "mycluster_node1"


def test_domain_name_preserves_case():
    assert domain_name("Cluster", "NodeA") == "Cluster_NodeA"


def test_generate_mac_format():
    mac = generate_mac("mycluster", "node1")
    assert re.match(r"52:54:00:[0-9a-f]{2}:[0-9a-f]{2}:[0-9a-f]{2}", mac), mac


def test_generate_mac_deterministic():
    assert generate_mac("mycluster", "node1") == generate_mac("mycluster", "node1")


def test_generate_mac_different_inputs():
    assert generate_mac("mycluster", "node1") != generate_mac("mycluster", "node2")
    assert generate_mac("clusterA", "node1") != generate_mac("clusterB", "node1")


def test_map_domain_state_running():
    assert map_domain_state(1) == "running"


def test_map_domain_state_paused():
    assert map_domain_state(3) == "paused"


def test_map_domain_state_shutoff():
    assert map_domain_state(5) == "shut off"


def test_map_domain_state_crashed():
    assert map_domain_state(6) == "crashed"


def test_map_domain_state_unknown():
    assert map_domain_state(99) == "unknown"
    assert map_domain_state(0) == "unknown"


# ---------------------------------------------------------------------------
# LibvirtClient tests
# ---------------------------------------------------------------------------


@pytest.fixture
def mock_libvirt():
    """Patch libvirt module inside libvirt_client."""
    with patch("vmcluster_mcp.cluster.libvirt_client.libvirt") as mock_lv:
        mock_conn = MagicMock()
        mock_lv.open.return_value = mock_conn
        yield mock_lv, mock_conn


@pytest.fixture
def client():
    return LibvirtClient("qemu:///system")


def test_client_does_not_open_connection_on_init(client):
    """Lazy connection: libvirt.open must NOT be called during __init__."""
    with patch("vmcluster_mcp.cluster.libvirt_client.libvirt") as mock_lv:
        _ = LibvirtClient("qemu:///system")
        mock_lv.open.assert_not_called()


# --- Domain operations ---


@pytest.mark.asyncio
async def test_define_domain(mock_libvirt):
    mock_lv, mock_conn = mock_libvirt
    c = LibvirtClient("qemu:///system")
    xml = "<domain/>"
    await c.define_domain(xml)
    mock_conn.defineXML.assert_called_once_with(xml)


@pytest.mark.asyncio
async def test_lookup_domain_found(mock_libvirt):
    mock_lv, mock_conn = mock_libvirt
    mock_dom = MagicMock()
    mock_conn.lookupByName.return_value = mock_dom
    c = LibvirtClient("qemu:///system")
    result = await c.lookup_domain("mycluster_node1")
    assert result is mock_dom
    mock_conn.lookupByName.assert_called_once_with("mycluster_node1")


@pytest.mark.asyncio
async def test_lookup_domain_not_found_returns_none(mock_libvirt):
    mock_lv, mock_conn = mock_libvirt
    mock_lv.libvirtError = Exception
    mock_conn.lookupByName.side_effect = Exception("domain not found")
    c = LibvirtClient("qemu:///system")
    result = await c.lookup_domain("nonexistent")
    assert result is None


@pytest.mark.asyncio
async def test_start_domain(mock_libvirt):
    mock_lv, mock_conn = mock_libvirt
    mock_dom = MagicMock()
    c = LibvirtClient("qemu:///system")
    await c.start_domain(mock_dom)
    mock_dom.create.assert_called_once()


@pytest.mark.asyncio
async def test_shutdown_domain(mock_libvirt):
    mock_lv, mock_conn = mock_libvirt
    mock_dom = MagicMock()
    c = LibvirtClient("qemu:///system")
    await c.shutdown_domain(mock_dom)
    mock_dom.shutdown.assert_called_once()


@pytest.mark.asyncio
async def test_destroy_domain(mock_libvirt):
    mock_lv, mock_conn = mock_libvirt
    mock_dom = MagicMock()
    c = LibvirtClient("qemu:///system")
    await c.destroy_domain(mock_dom)
    mock_dom.destroy.assert_called_once()


@pytest.mark.asyncio
async def test_undefine_domain(mock_libvirt):
    mock_lv, mock_conn = mock_libvirt
    mock_dom = MagicMock()
    c = LibvirtClient("qemu:///system")
    await c.undefine_domain(mock_dom)
    mock_dom.undefine.assert_called_once()


@pytest.mark.asyncio
async def test_get_domain_state(mock_libvirt):
    mock_lv, mock_conn = mock_libvirt
    mock_dom = MagicMock()
    mock_dom.state.return_value = [1, 0]  # running
    c = LibvirtClient("qemu:///system")
    state = await c.get_domain_state(mock_dom)
    assert state == "running"


@pytest.mark.asyncio
async def test_is_domain_active_true(mock_libvirt):
    mock_lv, mock_conn = mock_libvirt
    mock_dom = MagicMock()
    mock_dom.isActive.return_value = 1
    c = LibvirtClient("qemu:///system")
    result = await c.is_domain_active(mock_dom)
    assert result is True


@pytest.mark.asyncio
async def test_is_domain_active_false(mock_libvirt):
    mock_lv, mock_conn = mock_libvirt
    mock_dom = MagicMock()
    mock_dom.isActive.return_value = 0
    c = LibvirtClient("qemu:///system")
    result = await c.is_domain_active(mock_dom)
    assert result is False


# --- Network operations ---


@pytest.mark.asyncio
async def test_define_network(mock_libvirt):
    mock_lv, mock_conn = mock_libvirt
    c = LibvirtClient("qemu:///system")
    xml = "<network/>"
    await c.define_network(xml)
    mock_conn.networkDefineXML.assert_called_once_with(xml)


@pytest.mark.asyncio
async def test_lookup_network_found(mock_libvirt):
    mock_lv, mock_conn = mock_libvirt
    mock_net = MagicMock()
    mock_conn.networkLookupByName.return_value = mock_net
    c = LibvirtClient("qemu:///system")
    result = await c.lookup_network("mycluster")
    assert result is mock_net


@pytest.mark.asyncio
async def test_lookup_network_not_found_returns_none(mock_libvirt):
    mock_lv, mock_conn = mock_libvirt
    mock_lv.libvirtError = Exception
    mock_conn.networkLookupByName.side_effect = Exception("network not found")
    c = LibvirtClient("qemu:///system")
    result = await c.lookup_network("nonexistent")
    assert result is None


@pytest.mark.asyncio
async def test_start_network(mock_libvirt):
    mock_lv, mock_conn = mock_libvirt
    mock_net = MagicMock()
    c = LibvirtClient("qemu:///system")
    await c.start_network(mock_net)
    mock_net.create.assert_called_once()


@pytest.mark.asyncio
async def test_destroy_network(mock_libvirt):
    mock_lv, mock_conn = mock_libvirt
    mock_net = MagicMock()
    c = LibvirtClient("qemu:///system")
    await c.destroy_network(mock_net)
    mock_net.destroy.assert_called_once()


@pytest.mark.asyncio
async def test_undefine_network(mock_libvirt):
    mock_lv, mock_conn = mock_libvirt
    mock_net = MagicMock()
    c = LibvirtClient("qemu:///system")
    await c.undefine_network(mock_net)
    mock_net.undefine.assert_called_once()


@pytest.mark.asyncio
async def test_is_network_active_true(mock_libvirt):
    mock_lv, mock_conn = mock_libvirt
    mock_net = MagicMock()
    mock_net.isActive.return_value = 1
    c = LibvirtClient("qemu:///system")
    result = await c.is_network_active(mock_net)
    assert result is True


@pytest.mark.asyncio
async def test_is_network_active_false(mock_libvirt):
    mock_lv, mock_conn = mock_libvirt
    mock_net = MagicMock()
    mock_net.isActive.return_value = 0
    c = LibvirtClient("qemu:///system")
    result = await c.is_network_active(mock_net)
    assert result is False


# --- Connection lifecycle ---


@pytest.mark.asyncio
async def test_close_connection(mock_libvirt):
    mock_lv, mock_conn = mock_libvirt
    c = LibvirtClient("qemu:///system")
    # Trigger connection open
    await c.define_domain("<domain/>")
    await c.close()
    mock_conn.close.assert_called_once()


@pytest.mark.asyncio
async def test_close_noop_when_not_connected():
    """close() must not raise if connection was never opened."""
    c = LibvirtClient("qemu:///system")
    await c.close()  # should not raise
