import os
from pathlib import Path

import importlib.machinery
import importlib.util


BIN = Path(__file__).resolve().parents[1] / "lib" / "stall_detect.py"
spec = importlib.util.spec_from_loader(
    "stall_detect", importlib.machinery.SourceFileLoader("stall_detect", str(BIN))
)
stall_detect = importlib.util.module_from_spec(spec)
spec.loader.exec_module(stall_detect)


accumulate = stall_detect.accumulate
classify_cgroup = stall_detect.classify_cgroup


def sample(points):
    return [{"t": t, "cpu": c, "io": i, "pids": p} for t, c, i, p in points]


def test_cgroup_accumulate_tracks_only_increasing_deltas():
    raw = sample([(0, 10, 8, 1), (10, 8, 8, 1), (20, 15, 6, 2)])
    got = accumulate(raw, ("cpu", "io", "pids"))
    assert got == {
        "cpu": [0.0, 0.0, 7.0],
        "io": [0.0, 0.0, 0.0],
        "pids": [0.0, 0.0, 1.0],
    }


def test_cgroup_classifier_requires_full_window():
    raw = sample([(0, 1, 1, 2), (60, 1, 1, 2)])
    assert classify_cgroup(raw, 120) is None


def test_cgroup_classifier_fires_only_when_all_three_flat():
    raw = sample([(0, 1, 1, 2), (60, 1, 1, 2), (120, 1, 1, 2)])
    reason = classify_cgroup(raw, 120)
    assert reason == "no progress on cpu+io+pids for 120s"

    raw = sample([(0, 1, 1, 2), (60, 1, 1, 3), (120, 1, 1, 3)])
    assert classify_cgroup(raw, 120) is None


def test_cgroup_classifier_ignores_negative_deltas_as_progress():
    raw = sample([(0, 1, 1, 1), (60, 2, 1, 1), (120, 1, 1, 1)])
    assert classify_cgroup(raw, 120) is None


def test_cgroup_classifier_refuses_when_a_signal_is_absent():
    raw = sample([(0, 1, 1, 2), (60, 1, 1, 2), (120, 1, 1, 2)])
    assert classify_cgroup(raw, 120) is not None
    raw[1]["io"] = None
    assert classify_cgroup(raw, 120) is None
    assert stall_detect.unavailable_signals(raw) == ("io",)


def test_proc_io_absent_when_no_task_is_readable(tmp_path, monkeypatch):
    monkeypatch.chdir(tmp_path)
    assert stall_detect._read_proc_io([]) == 0
    # pid 0 is filtered; a pid that cannot be opened leaves nothing readable
    assert stall_detect._read_proc_io(["999999999"]) is None
    assert stall_detect._read_proc_io([str(os.getpid())]) is not None
