"""Gemeinsame Hilfen fuer die Isolationstests. Startet iso_exec.py als Subprozess mit
python3 / /bin/true INNERHALB des Kaefigs - NIE codex."""
from __future__ import annotations

import ctypes
import json
import os
import subprocess
import sys
import tempfile

from aria_router.isolation import ISO_EXEC_PATH
from aria_router.isolation.caged_runner import default_bus_env

PYTHON = sys.executable
FIXTURES = os.path.join(os.path.dirname(os.path.abspath(__file__)), "fixtures")
CAGE_PROBE = os.path.join(FIXTURES, "cage_probe.py")
FAKE_CODEX_CAGE = os.path.join(FIXTURES, "fake_codex_cage.py")


REAL_HOME = "/home/aria"


def home_probe_dir() -> str:
    """Wegwerf-Verzeichnis direkt unter dem echten Home (NICHT in einem Worktree). Wird VOR dem
    Profilbau angelegt, damit es als bestehender Teilbaum eine Regel bekommt (seit E44 Option 2 nur
    noch LESEN; schreibbar nur, wenn es ein Allowlist-Ordner ist). Der Aufrufer loescht es mit
    shutil.rmtree."""
    return tempfile.mkdtemp(prefix=".aria-v3-probe-", dir=REAL_HOME)


def home_project_probe_dir() -> str:
    """Wie home_probe_dir, aber NICHT versteckt: ein Allowlist-Eintrag (codex.tools_write_allowlist)
    darf keine Komponente mit fuehrendem '.' haben. Name ohne 'aria-v3-'-Stamm (sonst Geschwister-
    Sperre 1g). Der Aufrufer loescht es mit shutil.rmtree."""
    return tempfile.mkdtemp(prefix="aria-e44-probe-", dir=REAL_HOME)


def landlock_abi() -> int:
    libc = ctypes.CDLL(None, use_errno=True)
    libc.syscall.restype = ctypes.c_long
    return int(libc.syscall(ctypes.c_long(444), None, ctypes.c_long(0), ctypes.c_long(1)))


def user_bus_reachable() -> bool:
    env = default_bus_env()
    try:
        p = subprocess.run(["/usr/bin/systemctl", "--user", "show", "-p", "Version"],
                           env=env, capture_output=True, timeout=5, check=False)
    except (OSError, subprocess.TimeoutExpired):
        return False
    return p.returncode == 0 and b"Version=" in p.stdout


def run_in_cage(cfg: dict, argv, *, stdin: bytes = b"", timeout: float = 30, pass_fds=()):
    """Fuehrt `python3 -I iso_exec.py <cfg> -- argv...` aus und liefert die CompletedProcess."""
    fd, cfg_path = tempfile.mkstemp(suffix=".json", prefix="isocfg-")
    with os.fdopen(fd, "w", encoding="utf-8") as fh:
        json.dump(cfg, fh)
    try:
        return subprocess.run([PYTHON, "-I", ISO_EXEC_PATH, cfg_path, "--", *argv],
                              input=stdin, capture_output=True, timeout=timeout, check=False,
                              pass_fds=tuple(pass_fds))
    finally:
        os.unlink(cfg_path)


def probe_cfg(scratch: str, *, extra: dict | None = None, rlimits: dict | None = None,
              min_abi: int = 4) -> dict:
    """Basis-cfg, damit python3 die cage_probe INNERHALB des Kaefigs ausfuehren kann."""
    cfg = {
        "min_abi": min_abi,
        "cwd": scratch,
        "rx": ["/usr"],
        "file_ro": ["/etc/ld.so.cache", CAGE_PROBE],
        "rw": [scratch],
        "env": {"PATH": "/usr/bin:/bin"},
        "rlimits": rlimits if rlimits is not None else {"NOFILE": 1024},
    }
    for key, vals in (extra or {}).items():
        if key in ("env", "rlimits"):
            cfg[key] = {**cfg.get(key, {}), **vals}
        else:
            cfg.setdefault(key, [])
            cfg[key] = list(cfg[key]) + list(vals)
    return cfg


def run_probe(scratch: str, check: str, *args, extra: dict | None = None, rlimits: dict | None = None,
              stdin: bytes = b"", timeout: float = 30):
    cfg = probe_cfg(scratch, extra=extra, rlimits=rlimits)
    return run_in_cage(cfg, [PYTHON, "-I", CAGE_PROBE, check, *args], stdin=stdin, timeout=timeout)
