"""Parser engine changes invalidate native runtime proofs before optimization."""
from __future__ import annotations

import importlib
import importlib.metadata
import os
from pathlib import Path
import subprocess
import sys
from types import SimpleNamespace

import pytest

from hessian_recipes import gym_training_long as long
from hessian_recipes.training import sha256
from test_gym_training_long import setup_run  # noqa: F401 -- scripted scalar/RNG trainer


MODULES = ("regex", "regex._main", "regex._regex_core", "regex._regex")


@pytest.fixture
def engine_files(tmp_path, monkeypatch):
    files = {}
    for index, name in enumerate(MODULES):
        path = tmp_path / f"engine-{index}"
        path.write_bytes(f"qualified {name}".encode())
        files[name] = path
    original_import = importlib.import_module
    original_version = importlib.metadata.version
    monkeypatch.setattr(importlib, "import_module", lambda name, *args, **kwargs:
                        SimpleNamespace(__file__=str(files[name])) if name in files
                        else original_import(name, *args, **kwargs))
    monkeypatch.setattr(importlib.metadata, "version", lambda name:
                        "2026.9.29" if name == "regex" else original_version(name))
    return files


def test_same_version_native_engine_change_changes_identity(engine_files):
    before = long._response_parser_identity()
    assert before["version"] == "2026.9.29"
    assert before["source_sha256"] == {name: sha256(path) for name, path in engine_files.items()}
    engine_files["regex._regex"].write_bytes(b"changed compiled engine, same package version")
    after = long._response_parser_identity()
    assert before["version"] == after["version"]
    assert before["source_sha256"]["regex._regex"] != after["source_sha256"]["regex._regex"]
    assert all(before["source_sha256"][name] == after["source_sha256"][name]
               for name in MODULES if name != "regex._regex")


def test_unknown_engine_version_rejected_before_loading_extension(engine_files, monkeypatch):
    monkeypatch.setattr(importlib.metadata, "version", lambda name: "different")
    calls = []
    monkeypatch.setattr(importlib, "import_module", lambda name: calls.append(name))
    with pytest.raises(RuntimeError, match="pinned regex"):
        long._response_parser_identity()
    assert calls == []


def test_missing_engine_file_is_rejected(engine_files):
    engine_files["regex._regex"].unlink()
    with pytest.raises(RuntimeError, match="inspectable"):
        long._response_parser_identity()


def test_resume_rejects_changed_same_version_parser_engine_before_model_load(
        setup_run, monkeypatch):  # noqa: F811 -- imported fixture
    setup = setup_run
    config = setup.config()
    first = long.run(config, "http://127.0.0.1:9999", checkpoint_interval=2, stop_after_step=2)
    output = setup.root / "run"
    original_files = {name: (output / name).read_bytes()
                      for name in ("runtime-binding.json", "rollout-evidence.jsonl",
                                   "checkpoint-2/checkpoint-complete.json")}
    before = setup.state.heavy_calls
    monkeypatch.setattr(long, "_response_parser_identity", lambda: {
        "version": "2026.9.29", "source_sha256": {"fixture-engine": "1" * 64}})
    with pytest.raises(ValueError, match="upstream trainer source or dependency"):
        long.run(config, "http://127.0.0.1:9999", checkpoint_interval=2,
                 resume_from_checkpoint=first["checkpoint"])
    assert setup.state.heavy_calls == before
    assert not (output / "recovery").exists()
    assert all((output / name).read_bytes() == data for name, data in original_files.items())


def test_import_for_planning_does_not_load_training_or_parser_stack():
    source = Path(long.__file__).resolve().parents[1]
    env = os.environ | {"PYTHONPATH": str(source)}
    code = ("import sys; import hessian_recipes.gym_training_long; "
            "assert 'torch' not in sys.modules; assert 'regex' not in sys.modules")
    result = subprocess.run([sys.executable, "-c", code], env=env, capture_output=True,
                            text=True, timeout=10)
    assert result.returncode == 0, result.stderr


def test_actual_pinned_python_and_native_engine_sources_are_bound():
    pytest.importorskip("regex")
    identity = long._response_parser_identity()
    assert identity["version"] == "2026.9.29"
    assert set(identity["source_sha256"]) == set(MODULES)
    for name in MODULES:
        path = Path(importlib.import_module(name).__file__)
        assert identity["source_sha256"][name] == sha256(path)
    assert Path(importlib.import_module("regex._regex").__file__).suffix in {".so", ".pyd"}
