from __future__ import annotations

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

import pytest


def _worker_module() -> ModuleType:
    worker_root = Path(__file__).parents[1] / "experiments" / "perception" / "worker"
    path = worker_root / "run_e9_multirate_perception.py"
    sys.path.insert(0, str(worker_root))
    try:
        spec = importlib.util.spec_from_file_location("e9_multirate_worker", path)
        assert spec is not None and spec.loader is not None
        module = importlib.util.module_from_spec(spec)
        sys.modules[spec.name] = module
        spec.loader.exec_module(module)
        return module
    finally:
        sys.path.pop(0)


def _profile_path() -> Path:
    return (
        Path(__file__).parents[1]
        / "experiments"
        / "perception"
        / "worker"
        / "e9_multirate_perception_profile.json"
    )


def _semantic(worker: ModuleType, *, index: int, session_seconds: float) -> object:
    return worker.SemanticResult(
        frame_index=index,
        source_frame_index=1000 + index,
        session_seconds=session_seconds,
        completed_monotonic=10.0,
        completion_age_ms=180.0,
        mask_sha256="a" * 64,
        class_pixels={"road": 100},
        class_fractions={"road": 0.5},
    )


def test_e9_profile_pins_independent_bounded_rates() -> None:
    worker = _worker_module()

    profile, digest = worker._read_profile(_profile_path())

    assert len(digest) == 64
    assert profile["replay"]["detector_queue_capacity"] == 2
    assert profile["replay"]["semantic_queue_capacity"] == 1
    assert profile["replay"]["semantic_sample_every_frames"] == 5
    assert profile["replay"]["semantic_ttl_ms"] == 750.0


def test_e9_semantic_binding_exposes_fresh_stale_and_unavailable() -> None:
    worker = _worker_module()
    semantic = _semantic(worker, index=5, session_seconds=20.0)

    assert (
        worker._semantic_binding(
            None,
            frame_session_seconds=20.1,
            ttl_ms=750.0,
        )["status"]
        == "unavailable"
    )
    assert (
        worker._semantic_binding(
            semantic,
            frame_session_seconds=20.7,
            ttl_ms=750.0,
        )["status"]
        == "fresh"
    )
    assert (
        worker._semantic_binding(
            semantic,
            frame_session_seconds=20.8,
            ttl_ms=750.0,
        )["status"]
        == "stale"
    )


def test_e9_latest_semantic_rejects_time_reversal() -> None:
    worker = _worker_module()
    latest = worker.LatestSemantic()
    latest.publish(_semantic(worker, index=5, session_seconds=20.0))

    with pytest.raises(RuntimeError, match="not monotonic"):
        latest.publish(_semantic(worker, index=4, session_seconds=19.9))
