import importlib.util
from pathlib import Path
import sys
import types

import pytest


ROOT = Path(__file__).resolve().parents[2]
EXPORTER_PATH = ROOT / "services" / "monitoring" / "scripts" / "nvidia_process_exporter.py"


def load_exporter(monkeypatch):
    """Load the exporter without requiring an NVIDIA driver on the test host."""

    pynvml = types.ModuleType("pynvml")

    class NVMLError(Exception):
        pass

    class NVMLErrorNotFound(NVMLError):
        pass

    class NVMLErrorNotSupported(NVMLError):
        pass

    pynvml.NVMLError = NVMLError
    pynvml.NVMLError_NotFound = NVMLErrorNotFound
    pynvml.NVMLError_NotSupported = NVMLErrorNotSupported
    for name in (
        "nvmlDeviceGetComputeRunningProcesses_v3",
        "nvmlDeviceGetCount",
        "nvmlDeviceGetGraphicsRunningProcesses_v3",
        "nvmlDeviceGetHandleByIndex",
        "nvmlDeviceGetName",
        "nvmlDeviceGetProcessUtilization",
        "nvmlDeviceGetUUID",
        "nvmlDeviceGetUtilizationRates",
        "nvmlInit",
    ):
        setattr(pynvml, name, lambda *args, **kwargs: None)

    monkeypatch.setitem(sys.modules, "pynvml", pynvml)
    spec = importlib.util.spec_from_file_location("nvidia_process_exporter_test", EXPORTER_PATH)
    module = importlib.util.module_from_spec(spec)
    spec.loader.exec_module(module)
    return module


def test_process_sample_window_uses_nvml_microseconds(monkeypatch):
    exporter = load_exporter(monkeypatch)
    observed = []
    monkeypatch.setattr(exporter.time, "time", lambda: 1_700_000_000.0)
    monkeypatch.setattr(
        exporter,
        "nvmlDeviceGetProcessUtilization",
        lambda handle, since: observed.append(since) or [],
    )

    samples, supported = exporter.process_utilization_samples(object())

    assert samples == {}
    assert supported == 1
    assert observed == [1_700_000_000_000_000 - 30_000_000]


def test_namespace_attribution_scales_to_current_device_total(monkeypatch):
    exporter = load_exporter(monkeypatch)

    result = exporter.reconcile_namespace_utilization(
        {"game-stream": 20, "hermes": 10},
        device_util=3,
    )

    assert sum(result.values()) == pytest.approx(3)
    assert result["game-stream"] == pytest.approx(2)
    assert result["hermes"] == pytest.approx(1)


def test_namespace_attribution_assigns_unexplained_compute_to_host(monkeypatch):
    exporter = load_exporter(monkeypatch)

    result = exporter.reconcile_namespace_utilization(
        {"hermes": 1},
        device_util=3,
    )

    assert result == {"hermes": 1, "host": 2}


def test_zero_device_utilization_clears_stale_process_samples(monkeypatch):
    exporter = load_exporter(monkeypatch)

    result = exporter.reconcile_namespace_utilization(
        {"hermes": 40, "game-stream": 5},
        device_util=0,
    )

    assert result == {"hermes": 0, "game-stream": 0}
