from __future__ import annotations

import hashlib
import importlib.util
import os
import subprocess
from pathlib import Path
from types import ModuleType
from typing import Protocol, runtime_checkable

import pytest


@runtime_checkable
class Support(Protocol):
    def load_launcher(self, path: Path) -> object: ...
    def sealed_memfd(self, launcher: object, name: str, content: bytes) -> int: ...
    def run_snapshot_child(self, launcher: object, entries: dict[str, list[object]], cli_descriptor: int, cli_digest: str, after_fork: object | None = None) -> int: ...
    def json_object(self, content: str | bytes, label: str) -> dict[str, object]: ...


ROOT = Path(__file__).resolve().parent
LAUNCHER = ROOT / "task22-trainer-authority-removal-canonical.py"
STRICT_ENV = {"HOME": "/home/cube", "LANG": "C.UTF-8", "LC_ALL": "C.UTF-8", "PATH": "/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin"}


def load_support() -> Support:
    spec = importlib.util.spec_from_file_location("task22_fork_support", ROOT / "task22_launcher_test_support.py")
    if spec is None or spec.loader is None:
        raise AssertionError("support loader missing")
    module: ModuleType = importlib.util.module_from_spec(spec)
    spec.loader.exec_module(module)
    if not isinstance(module, Support):
        raise AssertionError("support surface missing")
    return module


def test_exact_venv_runtime_is_preserved_in_forked_snapshot(tmp_path: Path) -> None:
    support = load_support()
    launcher = support.load_launcher(LAUNCHER)
    output = tmp_path / "runtime.json"
    cli = ("import importlib.metadata as m,json,pathlib,sys\n" + f"pathlib.Path({str(output)!r}).write_text(json.dumps({{'prefix':sys.prefix,'base_prefix':sys.base_prefix,'pydantic':m.version('pydantic'),'pyyaml':m.version('PyYAML'),'telegram':m.version('python-telegram-bot')}},sort_keys=True))\n").encode()
    descriptor = support.sealed_memfd(launcher, "runtime-cli", cli)
    try:
        assert support.run_snapshot_child(launcher, {}, descriptor, hashlib.sha256(cli).hexdigest()) == 0
    finally:
        os.close(descriptor)
    assert support.json_object(output.read_text(), "runtime") == {"base_prefix": "/home/cube/miniconda3", "prefix": "/home/cube/projects/richard/hermes-agent/.venv", "pydantic": "2.13.4", "pyyaml": "6.0.3", "telegram": "22.6"}


def test_miniconda_reports_every_dependency_mismatch() -> None:
    result = subprocess.run(["/home/cube/miniconda3/bin/python", "-I", "-B", str(LAUNCHER)], cwd=ROOT, env=STRICT_ENV, text=True, capture_output=True, check=False)
    assert result.returncode == 1
    for detail in ("sys.prefix=/home/cube/miniconda3", "pydantic=2.12.5 (required 2.13.4)", "PyYAML=6.0.2 (required 6.0.3)", "python-telegram-bot=missing (required 22.6)"):
        assert detail in result.stderr


@pytest.mark.parametrize(("statement", "status"), (("raise SystemExit(23)", 23), ("import os,signal; os.kill(os.getpid(),signal.SIGTERM)", 143)))
def test_forked_snapshot_propagates_status(statement: str, status: int) -> None:
    support = load_support()
    launcher = support.load_launcher(LAUNCHER)
    cli = (statement + "\n").encode()
    descriptor = support.sealed_memfd(launcher, "status-cli", cli)
    try:
        assert support.run_snapshot_child(launcher, {}, descriptor, hashlib.sha256(cli).hexdigest()) == status
    finally:
        os.close(descriptor)
