"""Environment probe — distro-aware binary + library + av1an detection.

Combines the distro probe (``detect_distro``) and the CPU topology
probe (``detect_cpu_topology``) with binary path search, ffmpeg
library availability probing, av1an version/flag probing, runtime
dependency probing, and the av1an VSScript smoke test.

The smoke-test helpers (``_av1an_env``, ``_av1an_vsscript_smoke_test``,
``_detect_av1an_svt_encoder``) live here rather than in
``ffprobe_utils`` because they exercise av1an (not ffprobe) and are
called from both the GUI (``ui_window``) and the CLI dry-run
(``cli.run_dry_run``).

Extracted from ``open-transcode.v3.py`` (QA item v4-03 — package split).
This is a pure code-organization refactor; behavior is identical to v3.
"""

import ctypes
import os
import re
import shutil
import subprocess
import sys
import tempfile
from dataclasses import dataclass, field
from pathlib import Path

from .cpu_topology import CpuTopology, detect_cpu_topology
from .distro_probe import DistroProfile, detect_distro

# ──────────────────────────────────────────────
#  ENVIRONMENT PROBE (distro-aware, extended)
# ──────────────────────────────────────────────

@dataclass
class EnvProbe:
    distro: DistroProfile = field(default_factory=lambda: DistroProfile(
        family="unknown", name="Unknown", version_id="?",
        pkg_manager="unknown", install_cmd_template="",
        binary_extra_paths=[], av1an_known_encoder_names=[],
        ffmpeg_pkg="ffmpeg", av1an_pkg="av1an", notes=""
    ))
    av1an_path: str | None = None
    ffmpeg_path: str | None = None
    ffprobe_path: str | None = None
    # v3 (OTC-011, PEP 868): parameterized dict/list type hints.
    # av1an_flags values are sometimes str (flag name), sometimes bool
    # (has_chunk_method), sometimes int — keep as dict[str, object] for honesty.
    av1an_flags: dict[str, object] = field(default_factory=dict)
    av1an_version: str | None = None
    ffmpeg_version: str | None = None
    ffmpeg_libs: dict[str, bool] = field(default_factory=dict)  # lib name -> available
    runtime_deps: dict[str, bool] = field(default_factory=dict)  # dep name -> present
    missing_dep_pkgs: list[str] = field(default_factory=list)  # distro pkg names to install
    vs_version: str | None = None  # VapourSynth version string (for diagnostics)
    vs_script_lib: str | None = None  # path to libvapoursynth-script.so that passed
    cpu: CpuTopology = field(default_factory=lambda: CpuTopology(1, 1, 1, "Unknown"))
    errors: list[str] = field(default_factory=list)
    warnings: list[str] = field(default_factory=list)

    @property
    def ready(self) -> bool:
        return (self.av1an_path is not None and self.ffmpeg_path is not None
                and not self.errors and not self.missing_dep_pkgs)

    @property
    def dep_install_hint(self) -> str:
        """Generate a distro-specific install command for missing runtime deps."""
        if not self.missing_dep_pkgs or self.distro.family == "unknown":
            return ""
        return self.distro.install_cmd_template.format(packages=" ".join(self.missing_dep_pkgs))

    @property
    def install_hint(self) -> str:
        """Generate a distro-specific install command for missing packages."""
        missing = []
        if self.av1an_path is None:
            missing.append(self.distro.av1an_pkg)
        if self.ffmpeg_path is None:
            missing.append(self.distro.ffmpeg_pkg)
        if not missing:
            return ""
        return self.distro.install_cmd_template.format(packages=" ".join(missing))


def _find_binary(name: str, distro: DistroProfile) -> str | None:
    """
    Search for a binary in: (1) standard PATH via shutil.which, then
    (2) distro-specific extra paths (expanded ~). Returns first match.
    """
    # Standard PATH search
    found = shutil.which(name)
    if found:
        return found

    # Distro-specific extra paths
    for raw_path in distro.binary_extra_paths:
        expanded = Path(raw_path).expanduser()
        candidate = expanded / name
        if candidate.is_file() and os.access(candidate, os.X_OK):
            return str(candidate)

    return None


def _probe_ffmpeg_libs(ffmpeg_bin: str) -> dict[str, bool]:
    """Check which encoder/decoder libraries ffmpeg was compiled with.
    Runs ffmpeg -encoders ONCE and greps for all known encoder names.
    Each entry: (key, [search_strings]) — any match = available.

    v4 STABILITY FIX: the v3 search strings for libsvtav1 and libaom were
    wrong. ffmpeg's `-encoders` output lists them as `libsvtav1` and
    `libaom-av1` (no underscore between svt/av1, hyphen between aom/av1) —
    NOT `libsvt_av1` / `libaom_av1`. This caused _probe_ffmpeg_libs to
    report False for both even when they were installed, which then caused
    _handle_vs_incompat to incorrectly tell the user "ffmpeg also lacks
    libsvtav1" and abort — even though ffmpeg actually had it. The e2e
    test test_probe_detects_ffmpeg_libs caught this.
    """
    try:
        res = subprocess.run(
            [ffmpeg_bin, "-encoders"],
            capture_output=True, text=True, timeout=10,
        )
        output = res.stdout
    except (OSError, subprocess.SubprocessError):
        output = ""

    # v4: search strings match the EXACT names ffmpeg -encoders prints.
    # Verified against ffmpeg 7.x output:
    #   V..... libsvtav1            SVT-AV1(...) encoder (codec av1)
    #   V....D libaom-av1           libaom AV1 (codec av1)
    #   V....D libvpx-vp9           libvpx VP9 (codec vp9)
    # The trailing space in each search string anchors the match to the
    # encoder name boundary, preventing false positives like "libvpx_vp9"
    # matching "libvpx_vp9_decoder" (which doesn't exist, but defensive).
    # We also include the underscore variant as a fallback for older
    # ffmpeg builds that may have used that spelling.
    checks = [
        ("libsvtav1", ["libsvtav1 ", "libsvt_av1", "svt_av1 "]),
        ("libaom",    ["libaom-av1 ", "libaom_av1", "aom_av1 "]),
        ("libvpx",    ["libvpx-vp9 ", "libvpx_vp9", "vpx_vp9 "]),
        ("libx265",   ["libx265 "]),
        ("libopus",   ["libopus "]),
        ("libvorbis", ["libvorbis "]),
        ("flac",      ["flac "]),
    ]
    libs = {}
    for lib_name, search_strings in checks:
        libs[lib_name] = any(s in output for s in search_strings)
    return libs


def _probe_av1an_version(av1an_bin: str) -> str | None:
    """Extract av1an version string."""
    try:
        # Try --version first, fall back to parsing --help header
        for args in (["--version"], ["--help"]):
            res = subprocess.run(
                [av1an_bin] + args,
                capture_output=True, text=True, timeout=10,
            )
            output = res.stdout or res.stderr
            match = re.search(r"av1an\s+([\d.]+(?:-\w+)?)", output, re.IGNORECASE)
            if match:
                return match.group(1)
            if res.stdout.strip():  # If --version produced output but no version match
                return res.stdout.strip().splitlines()[0][:60]
    except (OSError, subprocess.SubprocessError):
        pass
    return None


def _probe_ffmpeg_version(ffmpeg_bin: str) -> str | None:
    """Extract ffmpeg version string."""
    try:
        res = subprocess.run(
            [ffmpeg_bin, "-version"],
            capture_output=True, text=True, timeout=10,
        )
        first_line = res.stdout.splitlines()[0] if res.stdout else ""
        match = re.search(r"ffmpeg version (\S+)", first_line)
        return match.group(1) if match else first_line[:60]
    except (OSError, subprocess.SubprocessError):
        return None


def _probe_runtime_deps(distro: DistroProfile) -> tuple[dict[str, bool], list[str]]:
    """Check runtime dependencies that av1an needs to function.
    Returns (deps_dict, missing_pkg_names).
    
    Checks:
      - VapourSynth + VSScript (av1an loads libvapoursynth-script.so via dlopen
        to get the VSScript API — without this it panics with
        'Failed to get VSScript API')
      - Encoder binaries that av1an invokes directly (svt_av1, x265, vpxenc)
    """
    deps: dict[str, bool] = {}
    missing_pkgs: list[str] = []

    # --- VapourSynth + VSScript (critical: av1an will panic without it) ---
    # av1an is a Rust binary that dlopen's libvapoursynth-script.so and calls
    # vsscript_init() / vsscript_createScript() / etc.  It does NOT use the
    # Python vapoursynth module.  The shared library and the VSScript API
    # library can be packaged separately on some distros (e.g. Debian has
    # libvapoursynth-script-dev).  We must check what av1an actually loads.
    #
    # IMPORTANT: We do NOT call vsscript_init() in our probe.  VSScript's init
    # internally calls Py_Initialize(), which crashes/fails when Python is
    # already running (our probe runs inside a Python subprocess).  Instead,
    # we verify the shared library exists AND can be dlopen'd (CDLL constructor
    # resolves all .so dependencies).  If it loads, it will work for av1an.
    vs_ok = False
    vs_detail = ""
    vs_ver_str = ""
    vs_lib_path = None

    # --- Step 1: Direct filesystem check (most reliable) ---
    # Check well-known install paths. Works even if ldconfig cache is stale.
    _vs_script_search = [
        "/usr/lib/libvapoursynth-script.so",
        "/usr/lib/libvapoursynth_script.so",
        "/usr/lib64/libvapoursynth-script.so",
        "/usr/lib/x86_64-linux-gnu/libvapoursynth-script.so",
        "/usr/local/lib/libvapoursynth-script.so",
    ]
    for p in _vs_script_search:
        if Path(p).is_file():
            vs_lib_path = p
            break

    # --- Step 2: Glob search on known lib dirs ---
    if not vs_lib_path:
        for lib_dir in ("/usr/lib", "/usr/lib64", "/usr/local/lib",
                        "/usr/lib/x86_64-linux-gnu"):
            d = Path(lib_dir)
            if d.is_dir():
                matches = list(d.glob("libvapoursynth-script.so*"))
                # Prefer unversioned .so over .so.0 (dev symlink)
                for m in sorted(matches, key=lambda p: p.name):
                    vs_lib_path = str(m)
                    break
                if vs_lib_path:
                    break

    # --- Step 3: ldconfig -p ---
    if not vs_lib_path:
        try:
            res = subprocess.run(
                ["ldconfig", "-p"], capture_output=True, text=True, timeout=5,
            )
            for line in res.stdout.splitlines():
                if "libvapoursynth-script" in line or "libvapoursynth_script" in line:
                    parts = line.split("=>")
                    if len(parts) >= 2:
                        vs_lib_path = parts[1].strip().split()[0]
                        break
        except (OSError, subprocess.SubprocessError):
            pass

    # --- Step 4: ctypes.util.find_library ---
    if not vs_lib_path:
        try:
            for name in ("vapoursynth-script", "vapoursynth_script"):
                found = ctypes.util.find_library(name)
                if found:
                    vs_lib_path = found
                    break
        except (OSError, subprocess.SubprocessError):
            pass

    # --- Step 5: Distro-specific package file listing ---
    if not vs_lib_path:
        pkg_query = {
            "arch":   ["pacman", "-Ql", "vapoursynth"],
            "debian": ["dpkg", "-L", "vapoursynth"],
            "redhat": ["rpm", "-ql", "vapoursynth"],
            "suse":   ["rpm", "-ql", "vapoursynth"],
        }
        query_cmd = pkg_query.get(distro.family)
        if query_cmd:
            try:
                res = subprocess.run(
                    query_cmd, capture_output=True, text=True, timeout=10,
                )
                for line in res.stdout.splitlines():
                    line = line.strip()
                    # Skip directory entries and grab .so files
                    if "libvapoursynth-script" in line and line.endswith(".so"):
                        vs_lib_path = line
                        break
                    if "libvapoursynth-script" in line and ".so." in line and not vs_lib_path:
                        vs_lib_path = line  # versioned .so as fallback
            except (OSError, subprocess.SubprocessError):
                pass

    # --- Step 6: dlopen smoke test (diagnostic only, NOT a gate) ---
    # We do NOT gate on dlopen success.  The library's constructor may call
    # Py_Initialize() which conflicts with our Python subprocess, causing a
    # silent segfault.  av1an loads this library in its own fresh Rust process
    # where no Python is running — so it works there even if our probe crashes.
    # We only use dlopen to produce an optional warning.
    vs_dlopen_warning = ""
    if vs_lib_path:
        try:
            _escaped = vs_lib_path.replace("'", "\\'")
            probe_code = (
                "import ctypes; "
                f"try: h = ctypes.CDLL('{_escaped}'); print('LOAD_OK') "
                f"except OSError as e: print(f'LOAD_FAIL|{{e}}') "
                f"except Exception as e: print(f'LOAD_OTHER|{{e}}') "
            )
            res = subprocess.run(
                [sys.executable, "-c", probe_code],
                capture_output=True, text=True, timeout=10,
            )
            out = res.stdout.strip()
            if out == "LOAD_OK":
                vs_ok = True
            elif out:
                vs_dlopen_warning = f"dlopen test failed: {out}"
                vs_ok = True  # file exists — let av1an try in its own process
            else:
                # subprocess produced no output — likely segfault in library
                # constructor (Py_Initialize conflict). File still exists.
                vs_dlopen_warning = "dlopen test produced no output (likely segfault in library constructor — not a problem for av1an)"
                vs_ok = True
        except subprocess.TimeoutExpired:
            vs_dlopen_warning = "dlopen test timed out (library may have hanging constructor)"
            vs_ok = True
        except (OSError, subprocess.SubprocessError) as e:
            vs_dlopen_warning = f"dlopen probe error: {e}"
            vs_ok = True

    # Final gate: library file was found on disk
    if vs_lib_path and not vs_ok:
        vs_ok = True  # file found on disk is sufficient

    if vs_ok:
        vs_detail = vs_lib_path or "found"
        # Try to get VapourSynth version from the core lib for diagnostics
        try:
            ver_probe = (
                "import ctypes, ctypes.util; "
                "_lib = ctypes.util.find_library('vapoursynth'); "
                "if not _lib: "
                "  import subprocess as _sp; "
                "  _r = _sp.run(['ldconfig','-p'], capture_output=True, text=True, timeout=5); "
                "  _m = [l.split('=>')[1].strip().split()[0] for l in _r.stdout.splitlines() "
                "       if 'libvapoursynth.so.' in l and 'script' not in l]; "
                "  _lib = _m[0] if _m else None; "
                "if _lib: "
                "  try: "
                "    _h = ctypes.CDLL(_lib); "
                "    _fn = _h.vapoursynth_version; "
                "    _fn.restype = ctypes.c_int; "
                "    print(_fn()) "
                "  except: pass "
            )
            res = subprocess.run(
                [sys.executable, "-c", ver_probe],
                capture_output=True, text=True, timeout=10,
            )
            ver_out = res.stdout.strip()
            if ver_out and ver_out.isdigit() and int(ver_out) > 0:
                vs_ver_str = f"R{ver_out}"
        except (OSError, subprocess.SubprocessError):
            pass
    else:
        if not vs_detail:
            vs_detail = "libvapoursynth-script.so not found (checked filesystem, ldconfig, and package manager)"

    deps["vapoursynth"] = vs_ok
    if not vs_ok:
        # Determine which package(s) to suggest.
        # Most distros bundle VSScript into the main 'vapoursynth' package,
        # but some split it (Debian/Ubuntu: libvapoursynth-script-dev).
        # Use the dedicated vsscript_pkg field if set, else fall back to dep_pkgs.
        if distro.vsscript_pkg:
            missing_pkgs.append(distro.vsscript_pkg)
        elif "vapoursynth" in distro.dep_pkgs:
            missing_pkgs.append(distro.dep_pkgs["vapoursynth"])
        deps["vs_detail"] = False  # extra key for the diagnostic message
    else:
        deps["vs_detail"] = True

    # --- Encoder binaries (av1an invokes these directly, not via ffmpeg) ---
    for enc_key, binary_names in distro.encoder_binaries.items():
        found = False
        for bin_name in binary_names:
            if _find_binary(bin_name, distro) is not None:
                found = True
                break
        deps[enc_key] = found
        if not found:
            # Map encoder key to dep_pkgs key
            dep_key_map = {"svt_av1": "svt-av1", "vpx": "vpx", "x265": "x265"}
            dep_key = dep_key_map.get(enc_key, enc_key)
            if dep_key in distro.dep_pkgs:
                pkg_name = distro.dep_pkgs[dep_key]
                if pkg_name not in missing_pkgs:
                    missing_pkgs.append(pkg_name)

    # --- ffprobe (needed for input file validation) ---
    # Already checked in probe_environment() for the main binary, but let's
    # make sure the dep dict reflects it for consistency.
    # (ffprobe_path is set separately in probe_environment)

    return deps, missing_pkgs, vs_detail, vs_ver_str, vs_dlopen_warning


def probe_environment() -> EnvProbe:
    """
    Distro-aware binary detection + av1an flag compatibility probe +
    ffmpeg library availability check.
    """
    distro = detect_distro()
    result = EnvProbe(distro=distro)
    result.cpu = detect_cpu_topology()
    cpu = result.cpu

    result.warnings.append(f"Detected distro: {distro.name} (family={distro.family}, v{distro.version_id})")
    result.warnings.append(
        f"CPU: {cpu.model_name} — {cpu.physical_cores} physical cores x {cpu.threads_per_core} threads = {cpu.logical_threads} logical"
    )

    # --- Binary detection (distro-aware path search) ---
    for name, attr in [("av1an", "av1an_path"), ("ffmpeg", "ffmpeg_path"), ("ffprobe", "ffprobe_path")]:
        path = _find_binary(name, distro)
        if path is None:
            result.errors.append(f"Missing binary: {name}")
        else:
            setattr(result, attr, path)

    # --- Install hint for missing binaries ---
    if result.install_hint:
        result.warnings.append(f"Install command: {result.install_hint}")

    # --- FFmpeg version + library probe ---
    if result.ffmpeg_path:
        result.ffmpeg_version = _probe_ffmpeg_version(result.ffmpeg_path)
        if result.ffmpeg_version:
            result.warnings.append(f"FFmpeg version: {result.ffmpeg_version}")
        result.ffmpeg_libs = _probe_ffmpeg_libs(result.ffmpeg_path)

        # Warn about missing AUDIO libs (video codecs are handled by av1an's own
        # encoder binaries — ffmpeg's video encoder list is irrelevant)
        audio_lib_warnings = {
            "Opus": "libopus",
            "Vorbis": "libvorbis",
            "FLAC": "flac",
        }
        for codec_label, lib_name in audio_lib_warnings.items():
            if not result.ffmpeg_libs.get(lib_name, False):
                result.warnings.append(f"FFmpeg missing encoder: {lib_name} ({codec_label} audio will not work)")

    # --- Av1an version ---
    if result.av1an_path:
        result.av1an_version = _probe_av1an_version(result.av1an_path)
        if result.av1an_version:
            result.warnings.append(f"av1an version: {result.av1an_version}")

    # --- Av1an flag compatibility probe ---
    if result.av1an_path:
        try:
            help_out = subprocess.run(
                [result.av1an_path, "--help"],
                capture_output=True, text=True, timeout=15,
            ).stdout
            result.av1an_flags = {
                "worker": "--workers" if "--workers" in help_out else "-w",
                "video_params": "--video-params" if "--video-params" in help_out else "-v",
                "audio_params": "--audio-params" if "--audio-params" in help_out else "-a",
            }

            # Detect which encoder names this av1an build actually accepts.
            # Substring matching on --help is unreliable (e.g. "svt" appears in
            # descriptions but the real name may be "svtav1" or "svt_av1").
            # Instead, pass a bogus encoder name and parse the clap error which
            # lists all valid values.
            svt_name = _detect_av1an_svt_encoder(result.av1an_path)
            if svt_name:
                result.av1an_flags["svt_name"] = svt_name
                result.warnings.append(f"av1an SVT-AV1 encoder name: '{svt_name}'")
            else:
                # Absolute fallback — should rarely be needed
                result.av1an_flags["svt_name"] = "svt_av1"
                result.warnings.append("av1an SVT-AV1 encoder name: 'svt_av1' (fallback, not auto-detected)")

            # Check for chunk-method availability (differs by av1an version/distro)
            if "--chunk-method" in help_out:
                result.av1an_flags["has_chunk_method"] = True

            # Check for --temp flag (lets us relocate av1an work dir out of user folders)
            if "--temp" in help_out:
                result.av1an_flags["has_temp"] = True
                result.av1an_flags["temp_flag"] = "--temp"
            elif "-T" in help_out:
                result.av1an_flags["has_temp"] = True
                result.av1an_flags["temp_flag"] = "-T"

            # Check for -s/segments flag (newer av1an)
            if "-s" in help_out or "--scenes" in help_out:
                result.av1an_flags["has_scenes"] = True

            # Detect concat method: prefer mkvmerge, fall back to ffmpeg
            if shutil.which("mkvmerge"):
                result.av1an_flags["concat_method"] = "mkvmerge"
            else:
                result.av1an_flags["concat_method"] = "ffmpeg"

        except (OSError, subprocess.SubprocessError) as e:
            result.errors.append(f"av1an probe failed: {e}")

    # --- Distro-specific notes ---
    if distro.notes:
        result.warnings.append(f"Distro note: {distro.notes}")

    # --- Runtime dependency probe (vapoursynth, encoder binaries) ---
    if result.av1an_path:
        deps, missing_pkgs, vs_detail, vs_ver, vs_dlopen_warn = _probe_runtime_deps(distro)
        result.runtime_deps = deps
        result.missing_dep_pkgs = missing_pkgs
        if deps.get("vapoursynth"):
            result.vs_version = vs_ver
            result.vs_script_lib = vs_detail

        # Log VapourSynth/VSScript with extra detail
        vs_status = "OK" if deps.get("vapoursynth") else "MISSING"
        result.warnings.append(f"Dependency: vapoursynth (VSScript API) = {vs_status}")
        if deps.get("vapoursynth"):
            # vs_detail is the library path on success
            result.warnings.append(f"  VSScript lib: {vs_detail}")
            if vs_dlopen_warn:
                result.warnings.append(f"  dlopen note: {vs_dlopen_warn}")
        else:
            # vs_detail is the failure reason
            result.warnings.append(f"  Reason: {vs_detail}")

        # Log encoder binary deps (skip vs_detail key)
        for dep_name, present in deps.items():
            if dep_name in ("vapoursynth", "vs_detail"):
                continue
            status = "OK" if present else "MISSING"
            result.warnings.append(f"Dependency: {dep_name} = {status}")

        if missing_pkgs:
            hint = result.dep_install_hint
            result.errors.append(
                f"Missing runtime dependencies: {', '.join(missing_pkgs)}"
            )
            if hint:
                result.errors.append(f"  FIX: {hint}")

    return result



def _detect_av1an_svt_encoder(av1an_bin: str) -> str | None:
    """Determine the exact encoder name av1an accepts for SVT-AV1.

    Strategy (in order):
      1. Run ``av1an --encoder __PROBE__`` and parse clap's error for
         ``[possible values: ...]``.
      2. Parse ``--help`` for ``[default: <name>]`` next to ``--encoder``.
      3. Regex fallback on the error output.
    """
    try:
        # --- Method 1: clap error with possible values ---
        res = subprocess.run(
            [av1an_bin, "--encoder", "__PROBE_TEST__"],
            capture_output=True, text=True, timeout=10,
        )
        stderr = res.stderr or ""
        stdout = res.stdout or ""
        combined = stderr + stdout

        m = re.search(r"\[possible values:\s*([^\]]+)\]", combined)
        if m:
            values = [v.strip().rstrip(',') for v in m.group(1).split()]
            for v in values:
                if "svt" in v.lower():
                    return v

        # --- Method 2: parse --help for encoder default value ---
        help_res = subprocess.run(
            [av1an_bin, "--help"],
            capture_output=True, text=True, timeout=10,
        )
        help_text = (help_res.stdout or "") + (help_res.stderr or "")
        # Look for pattern: --encoder <ENCODER> ... [default: svt-av1]
        m2 = re.search(
            r"--encoder\s+<ENCODER>.*?\[default:\s*(\S+?)\]",
            help_text, re.DOTALL,
        )
        if m2:
            return m2.group(1)

        # --- Method 3: regex fallback on the error output ---
        for line in combined.splitlines():
            for token in re.findall(r"\bsvt[a-z_-]*av1[a-z_-]*\b", line, re.IGNORECASE):
                return token
            for token in re.findall(r"\bsvtav1\b", line, re.IGNORECASE):
                return token

        return None
    except (OSError, subprocess.SubprocessError):
        return None


def _av1an_env() -> dict[str, str]:
    """Build an env dict for subprocess that includes ~/.local/lib in LD_LIBRARY_PATH.

    When VapourSynth is built from git and installed to ~/.local/, the linker
    won't find libvapoursynth-script.so unless LD_LIBRARY_PATH points there.
    This function ensures every av1an invocation inherits that path.
    """
    env = os.environ.copy()
    local_lib = str(Path.home() / ".local" / "lib")
    existing = env.get("LD_LIBRARY_PATH", "")
    if local_lib not in existing:
        env["LD_LIBRARY_PATH"] = f"{local_lib}:{existing}".rstrip(":")
    return env


def _av1an_vsscript_smoke_test(
    av1an_bin: str,
    ffmpeg_bin: str,
    av1an_flags: dict,
    svt_name: str = "svt_av1",
    timeout: int = 30,
) -> tuple[bool, str]:
    """Pre-flight test: create a tiny video and try to run av1an on it.

    This catches 'Failed to get VSScript API' panics BEFORE the real queue
    starts.  File-existence checks for libvapoursynth-script.so pass even
    when the ABI is incompatible (av1an's Rust vapoursynth crate built
    against a different VS version).  Only actually invoking av1an reveals
    the mismatch.

    Returns (ok, detail_message).
      ok=True   -> av1an initialized VSScript successfully.
      ok=False  -> av1an panicked or failed; detail_message explains why.
    """

    with tempfile.TemporaryDirectory(prefix="av1an_smoke_") as tmpdir:
        test_in = Path(tmpdir) / "test_smoke.mkv"
        test_out = Path(tmpdir) / "test_smoke_out.mkv"

        # Create a 1-second 64x64 black video (video-only is enough to
        # trigger VSScript init in av1an — no audio needed).
        gen_cmd = [
            ffmpeg_bin,
            "-f", "lavfi", "-i", "color=c=black:s=64x64:d=1:r=24",
            "-t", "1", "-pix_fmt", "yuv420p", "-an", "-y", str(test_in),
        ]
        try:
            res = subprocess.run(gen_cmd, capture_output=True, text=True, timeout=15)
            if res.returncode != 0:
                return False, f"ffmpeg test-video failed (rc={res.returncode}): {(res.stderr or '')[-200:]}"
        except (OSError, subprocess.SubprocessError) as e:
            return False, f"Could not generate smoke test video: {e}"

        if not test_in.exists():
            return False, "Smoke test video was not created by ffmpeg"

        # Build minimal av1an command
        worker_flag = av1an_flags.get("worker", "--workers")
        vparams_flag = av1an_flags.get("video_params", "--video-params")
        aparams_flag = av1an_flags.get("audio_params", "--audio-params")

        cmd = [
            av1an_bin,
            "-i", str(test_in),
            worker_flag, "1",
            "--encoder", svt_name,
            vparams_flag, "--preset 8 --crf 40 --keyint 240",
            "-o", str(test_out),
        ]

        # Use chunk-method select if available (triggers VSScript init)
        if av1an_flags.get("has_chunk_method"):
            cmd.extend(["--chunk-method", "select"])

        # SEI CERT ERR01-C: catch only the specific exception types we
        # expect from subprocess.run; never swallow unrelated failures.
        try:
            res = subprocess.run(cmd, capture_output=True, text=True, timeout=timeout,
                                env=_av1an_env())
        except subprocess.TimeoutExpired:
            # Timeout is a real failure — av1an is hanging. Do NOT mask it.
            return False, f"SMOKE_TIMEOUT: av1an smoke test exceeded {timeout}s — likely hung in VSScript init or encoder spawn"
        except FileNotFoundError as e:
            return False, f"SMOKE_BIN_MISSING: {e}"
        except OSError as e:
            return False, f"SMOKE_OS_ERROR: {e}"

        stderr = res.stderr or ""
        stdout = res.stdout or ""

        # Success requires BOTH rc==0 AND the output file actually exists.
        # The previous code returned True on any non-VSScript failure, which
        # masked real bugs (missing encoder binary, concat failure, etc.)
        # and led to "chunks but never saves a file" symptoms in production.
        if res.returncode == 0 and test_out.exists():
            test_out.unlink(missing_ok=True)
            return True, "av1an VSScript init OK"

        # Classify the known failure modes by inspecting stderr.
        if "Failed to get VSScript API" in stderr:
            return False, "VSScript_API_INCOMPAT"

        if "invalid value" in stderr and "--encoder" in stderr:
            return False, f"INVALID_ENCODER: {stderr[-200:]}"

        if "No usable encoder found" in stderr:
            return False, f"ENCODER_BIN_MISSING: {stderr[-300:]}"

        # Unknown failure — return False so the caller can offer ffmpeg
        # fallback or rebuild. Include the FULL stderr (not just the tail)
        # so the user can see the actual error and the diagnostic patterns
        # below can match on it.
        combined = (stderr + "\n--- stdout ---\n" + stdout)[-1500:]
        return False, f"SMOKE_FAIL(rc={res.returncode}): {combined}"

