# Copyright (c) Microsoft. All rights reserved.

from __future__ import annotations

from types import SimpleNamespace
from typing import Any, Callable, Optional

import pytest

from agentlightning.utils import system_snapshot

pytestmark = pytest.mark.utils


def _patch_system_snapshot(
    monkeypatch: pytest.MonkeyPatch,
    include_gpu: bool = False,
    cpu_percent_fn: Optional[Callable[[float], float]] = None,
) -> Optional[SimpleNamespace]:
    monkeypatch.setattr(system_snapshot.platform, "processor", lambda: "test-cpu")
    monkeypatch.setattr(system_snapshot.platform, "platform", lambda: "test-platform")
    monkeypatch.setattr(system_snapshot.socket, "gethostname", lambda: "test-host")

    def fake_cpu_count(logical: bool = True) -> int:
        return 4 if logical else 2

    monkeypatch.setattr(system_snapshot.psutil, "cpu_count", fake_cpu_count)

    def default_cpu_percent(interval: float) -> float:
        return 33.3

    monkeypatch.setattr(
        system_snapshot.psutil,
        "cpu_percent",
        cpu_percent_fn or default_cpu_percent,
    )

    vm = SimpleNamespace(used=5 * (2**30), total=10 * (2**30), percent=50.0)
    monkeypatch.setattr(system_snapshot.psutil, "virtual_memory", lambda: vm)

    du = SimpleNamespace(used=2 * (2**30), total=8 * (2**30), percent=25.0)
    monkeypatch.setattr(system_snapshot.psutil, "disk_usage", lambda _: du)  # type: ignore

    net = SimpleNamespace(bytes_sent=4 * (2**20), bytes_recv=6 * (2**20))
    monkeypatch.setattr(system_snapshot.psutil, "net_io_counters", lambda: net)

    if include_gpu:
        dummy_gpu = SimpleNamespace(
            name="Test GPU",
            utilization=37.5,
            memory_used=1024,
            memory_total=4096,
            temperature=68,
        )
        monkeypatch.setattr(
            system_snapshot.GPUStatCollection,
            "new_query",
            lambda *args, **kwargs: SimpleNamespace(gpus=[dummy_gpu]),  # type: ignore
        )

        return dummy_gpu
    return None


def test_system_snapshot_excludes_gpu_by_default(monkeypatch: pytest.MonkeyPatch) -> None:
    def fail_gpu_query(*args: Any, **kwargs: Any) -> Any:
        raise AssertionError("GPU stats should not be queried when include_gpu is False")

    monkeypatch.setattr(system_snapshot.GPUStatCollection, "new_query", fail_gpu_query)

    _patch_system_snapshot(monkeypatch)

    snapshot = system_snapshot.system_snapshot()

    assert snapshot["cpu_name"] == "test-cpu"
    assert snapshot["cpu_cores"] == 2
    assert snapshot["cpu_threads"] == 4
    assert snapshot["cpu_usage_pct"] == 33.3
    assert snapshot["mem_used_gb"] == 5.0
    assert snapshot["mem_total_gb"] == 10.0
    assert snapshot["mem_pct"] == 50.0
    assert snapshot["disk_used_gb"] == 2.0
    assert snapshot["disk_total_gb"] == 8.0
    assert snapshot["disk_pct"] == 25.0
    assert snapshot["bytes_sent_mb"] == 4.0
    assert snapshot["bytes_recv_mb"] == 6.0
    assert snapshot["host"] == "test-host"
    assert snapshot["os"] == "test-platform"
    assert "gpus" not in snapshot


def test_system_snapshot_includes_gpus_when_requested(monkeypatch: pytest.MonkeyPatch) -> None:
    dummy_gpu = _patch_system_snapshot(monkeypatch, include_gpu=True)
    assert dummy_gpu is not None

    snapshot = system_snapshot.system_snapshot(include_gpu=True)

    assert snapshot["gpus"] == [
        {
            "gpu": dummy_gpu.name,
            "util_pct": dummy_gpu.utilization,
            "mem_used_mb": dummy_gpu.memory_used,
            "mem_total_mb": dummy_gpu.memory_total,
            "temp_c": dummy_gpu.temperature,
        }
    ]


def test_sanity_check() -> None:
    snapshot = system_snapshot.system_snapshot()
    assert snapshot is not None

    snapshot = system_snapshot.system_snapshot(include_gpu=True)
    assert snapshot is not None

    try:
        import torch  # type: ignore

        GPU_AVAILABLE = torch.cuda.is_available()
    except Exception:
        GPU_AVAILABLE = False  # type: ignore

    if GPU_AVAILABLE:
        assert snapshot["gpus"] is not None
        assert len(snapshot["gpus"]) > 0


def test_system_snapshot_cpu_percent_samples_immediately(monkeypatch: pytest.MonkeyPatch) -> None:
    called_intervals: list[float] = []

    def record_cpu_percent(interval: float) -> float:
        called_intervals.append(interval)
        return 77.7

    _patch_system_snapshot(monkeypatch, cpu_percent_fn=record_cpu_percent)

    snapshot = system_snapshot.system_snapshot()

    assert snapshot["cpu_usage_pct"] == 77.7
    assert called_intervals == [0.0]
