"""Tests for net_fault_list tool — returns active fault entries from the registry."""

from __future__ import annotations

from unittest.mock import AsyncMock, MagicMock, patch

import pytest

from vmcluster_mcp.models import NetFaultInfo


# ---------------------------------------------------------------------------
# Helpers
# ---------------------------------------------------------------------------


def _make_fault_info(
    handle_id: str = "h-01",
    cluster_name: str = "cluster1",
    fault_type: str = "partition",
    affected_nodes: list[str] | None = None,
    parameters: dict | None = None,
) -> NetFaultInfo:
    return NetFaultInfo(
        handle_id=handle_id,
        fault_type=fault_type,  # type: ignore[arg-type]
        affected_nodes=affected_nodes or ["node1", "node2"],
        created_at="2026-01-01T00:00:00+00:00",
        parameters=parameters or {"partition_a": ["node1"], "partition_b": ["node2"], "bridge": "virbr0"},
    )


# ---------------------------------------------------------------------------
# net_fault_list — returns faults for cluster
# ---------------------------------------------------------------------------


@pytest.mark.asyncio
async def test_net_fault_list_returns_both_fault_types():
    """net_fault_list returns both partition and impairment faults for a cluster."""
    from vmcluster_mcp.net import net_fault_list

    partition_fault = _make_fault_info("h-part-01", "cluster1", "partition")
    impairment_fault = _make_fault_info(
        "h-imp-01", "cluster1", "impairment",
        affected_nodes=["node1"],
        parameters={"source_node": "node1", "latency_ms": 100},
    )
    faults = [partition_fault, impairment_fault]

    with patch("vmcluster_mcp.net._registry.list_faults", return_value=faults):
        result = await net_fault_list(cluster_name="cluster1")

    assert result.success is True
    assert result.result is not None
    assert len(result.result) == 2
    fault_types = {f.fault_type for f in result.result}
    assert "partition" in fault_types
    assert "impairment" in fault_types


@pytest.mark.asyncio
async def test_net_fault_list_returns_empty_list_when_no_active_faults():
    """net_fault_list returns empty list when no active faults exist for the cluster."""
    from vmcluster_mcp.net import net_fault_list

    with patch("vmcluster_mcp.net._registry.list_faults", return_value=[]):
        result = await net_fault_list(cluster_name="cluster1")

    assert result.success is True
    assert result.result == []


@pytest.mark.asyncio
async def test_net_fault_list_filters_by_cluster_name():
    """net_fault_list only returns faults for the requested cluster_name."""
    from vmcluster_mcp.net import net_fault_list

    # list_faults already filters by cluster_name in the registry — we verify
    # the tool passes the correct cluster_name to list_faults
    captured_calls: list[str] = []

    def mock_list_faults(cluster_name: str) -> list[NetFaultInfo]:
        captured_calls.append(cluster_name)
        return []

    with patch("vmcluster_mcp.net._registry.list_faults", side_effect=mock_list_faults):
        await net_fault_list(cluster_name="target-cluster")

    assert captured_calls == ["target-cluster"]


@pytest.mark.asyncio
async def test_net_fault_list_includes_parameters_dict():
    """net_fault_list returns NetFaultInfo entries with populated parameters dict."""
    from vmcluster_mcp.net import net_fault_list

    params = {"partition_a": ["node1"], "partition_b": ["node2"], "bridge": "virbr0"}
    fault = _make_fault_info("h-01", "cluster1", "partition", parameters=params)

    with patch("vmcluster_mcp.net._registry.list_faults", return_value=[fault]):
        result = await net_fault_list(cluster_name="cluster1")

    assert result.success is True
    assert result.result is not None
    assert len(result.result) == 1
    returned_fault = result.result[0]
    assert returned_fault.parameters == params
    assert returned_fault.handle_id == "h-01"


@pytest.mark.asyncio
async def test_net_fault_list_reflects_reconciled_state():
    """net_fault_list returns only faults present after registry reconciliation.

    Stale entries pruned by reconcile() are not returned.
    """
    from vmcluster_mcp.net import net_fault_list

    # After reconcile, only the active fault remains
    active_fault = _make_fault_info("h-active", "cluster1", "partition")

    # Simulate scenario: reconcile was called externally and pruned stale entries.
    # list_faults returns only the live entry, confirming stale entries are absent.
    with patch("vmcluster_mcp.net._registry.list_faults", return_value=[active_fault]):
        result = await net_fault_list(cluster_name="cluster1")

    assert result.success is True
    assert len(result.result) == 1
    assert result.result[0].handle_id == "h-active"


@pytest.mark.asyncio
async def test_net_fault_list_returns_netfaultinfo_instances():
    """net_fault_list result entries are NetFaultInfo model instances."""
    from vmcluster_mcp.net import net_fault_list

    fault = _make_fault_info("h-01", "cluster1", "impairment")

    with patch("vmcluster_mcp.net._registry.list_faults", return_value=[fault]):
        result = await net_fault_list(cluster_name="cluster1")

    assert result.success is True
    for item in result.result:
        assert isinstance(item, NetFaultInfo)


# ---------------------------------------------------------------------------
# Lazy startup reconciliation — _ensure_reconciled() and _reconciled flag
# ---------------------------------------------------------------------------


@pytest.mark.asyncio
async def test_net_fault_list_calls_reconcile_on_first_call():
    """net_fault_list calls _registry.reconcile() on first invocation."""
    import vmcluster_mcp.net
    from vmcluster_mcp.net import net_fault_list

    vmcluster_mcp.net._reconciled = False
    with patch("vmcluster_mcp.net._registry.reconcile", new_callable=AsyncMock) as mock_reconcile:
        with patch("vmcluster_mcp.net._registry.list_faults", return_value=[]):
            await net_fault_list(cluster_name="cluster1")

    assert mock_reconcile.called


@pytest.mark.asyncio
async def test_net_fault_list_skips_reconcile_on_subsequent_call():
    """net_fault_list does NOT call reconcile() when already reconciled."""
    import vmcluster_mcp.net
    from vmcluster_mcp.net import net_fault_list

    vmcluster_mcp.net._reconciled = True
    with patch("vmcluster_mcp.net._registry.reconcile", new_callable=AsyncMock) as mock_reconcile:
        with patch("vmcluster_mcp.net._registry.list_faults", return_value=[]):
            await net_fault_list(cluster_name="cluster1")

    assert not mock_reconcile.called


@pytest.mark.asyncio
async def test_ensure_reconciled_sets_flag():
    """_ensure_reconciled() sets _reconciled flag to True after call."""
    import vmcluster_mcp.net
    from vmcluster_mcp.net import _ensure_reconciled

    vmcluster_mcp.net._reconciled = False
    with patch("vmcluster_mcp.net._registry.reconcile", new_callable=AsyncMock):
        await _ensure_reconciled()

    assert vmcluster_mcp.net._reconciled is True

