# -*- coding: utf-8 -*-
# © 2025 Emergent Physics Lab. All rights reserved.
# Licensed under CC BY-NC-ND 4.0 (Creative Commons Attribution-NonCommercial-NoDerivatives 4.0 International).
# SPDX-License-Identifier: CC-BY-NC-ND-4.0
# Contact: latticefieldmediumresearch@gmail.com
"""
Legacy / Convenience Entry Point for Full or Fast LFM Suite
==========================================================
Purpose:
    Simplified launcher wrapping the canonical parallel harness `run_parallel_suite.py`.
    Handles venv creation, dependency install, optional CuPy GPU enablement, and then
    runs either the full tier suite (tiers 1–7) or the fast validation subset.

Fast Mode ("--fast"):
    Now runs 14 tests (2 per tier for tiers 1–7) instead of the older 4-test smoke.
    Tests: REL-01, REL-02, GRAV-12, GRAV-23, ENER-01, ENER-03, QUAN-01, QUAN-03,
                 EM-01, EM-03, COUP-01, COUP-02, THERM-01, THERM-02.

Full Mode (default with NO flags):
    Runs all registered tests across tiers 1–7 using adaptive parallel scheduling.

Usage (Windows PowerShell):
        python QuickStart\run_lfm_suite.py          # full suite (tiers 1–7)
        python QuickStart\run_lfm_suite.py --fast   # 12-test fast validation subset

Notes:
    - ALWAYS GPU-first: attempts CuPy install; falls back to NumPy only if GPU unavailable.
    - Ensures outputs land under QuickStart/results/<Category>.
    - This wrapper is legacy; direct use of `runtime/src/run_parallel_suite.py` is recommended
        for advanced options (--tests, --tiers, --backend).
"""

from __future__ import annotations
import argparse
import os
import platform
import shutil
import subprocess
import sys
from pathlib import Path

QS_DIR = Path(__file__).resolve().parent
REPO_ROOT = QS_DIR.parent
# For standalone QuickStart zip: default venv lives inside QuickStart/.venv
# For full repo usage: prefer root .venv if it already has CuPy (GPU mandatory)
_QS_DEFAULT_VENV = QS_DIR / ".venv"
_GLOBAL_VENV = REPO_ROOT / ".venv"

def _test_cupy(python_exe: Path) -> bool:
    """Return True if importing CuPy and querying device count succeeds.
    Adds a very small smoke test to ensure NVRTC actually loads (kernel compile).
    """
    if not python_exe.exists():
        return False
    try:
        code = (
            "import cupy as cp; import sys; "
            "a=cp.arange(16); b=(a+1)**2; "
            "sys.exit(0 if cp.cuda.runtime.getDeviceCount()>0 and int(cp.asnumpy(b)[3])==16 else 2)"
        )
        res = subprocess.run([str(python_exe), "-c", code], capture_output=True, text=True, timeout=18)
        return res.returncode == 0
    except Exception:
        return False

# Decide active venv directory: prefer global if it has working CuPy
# Selection policy:
# 1. If a global repo venv exists AND already has working CuPy, use it (avoids duplicate heavy install).
# 2. Otherwise always use the QuickStart-local venv for isolation & reproducibility.
_GLOBAL_PY = _GLOBAL_VENV / ("Scripts/python.exe" if os.name == "nt" else "bin/python")
# Revised policy (2025-11-20): Prefer existing global venv if present even if CuPy is not yet installed.
# Rationale: Global venv likely pinned to supported Python version for CuPy wheels; creating a fresh QuickStart venv
# under a newer system interpreter (observed cp314) prevents locating cupy-cuda12x wheels causing CPU fallback.
if _GLOBAL_PY.exists():
    VENV_DIR = _GLOBAL_VENV
else:
    VENV_DIR = _QS_DEFAULT_VENV

# Prefer self-contained runtime if present, else fall back to repo workspace
PKG_SRC_DIR = QS_DIR / "runtime" / "src"
PKG_REQ = QS_DIR / "requirements.txt"

WS_REQ_FILE = REPO_ROOT / "workspace" / "requirements.txt"
WS_SRC_DIR = REPO_ROOT / "workspace" / "src"

def _resolve_src_dir() -> Path:
    if PKG_SRC_DIR.exists():
        return PKG_SRC_DIR
    return WS_SRC_DIR

def _resolve_requirements() -> Path:
    if PKG_REQ.exists():
        return PKG_REQ
    return WS_REQ_FILE

def _resolve_parallel_suite(src_dir: Path) -> Path:
    return src_dir / "run_parallel_suite.py"


def _venv_python() -> Path:
    if platform.system().lower().startswith("win"):
        return VENV_DIR / "Scripts" / "python.exe"
    return VENV_DIR / "bin" / "python"


def ensure_python_version(min_major: int = 3, min_minor: int = 10) -> None:
    if sys.version_info < (min_major, min_minor):
        print(f"ERROR: Python>={min_major}.{min_minor} required. Found {sys.version.split()[0]}")
        sys.exit(1)
    # Warn if interpreter is newer than commonly supported CuPy wheels (e.g., Python 3.14 at time of writing)
    if sys.version_info >= (3, 14):
        print("WARNING: Python >=3.14 detected; CuPy wheels may be unavailable. Prefer using repository global .venv with supported Python (<=3.13).")


def create_venv_if_needed() -> None:
    # If we selected the global venv (already exists & has CuPy), skip creation
    if VENV_DIR == _GLOBAL_VENV:
        if VENV_DIR.exists():
            print(f"Using existing global venv: {VENV_DIR}")
            return
    if VENV_DIR.exists():
        return
    print(f"Creating virtual environment: {VENV_DIR}")
    subprocess.run([sys.executable, "-m", "venv", str(VENV_DIR)], check=True)


def relaunch_in_venv_if_needed(argv: list[str]) -> None:
    vpy = _venv_python()
    if Path(sys.executable) != vpy:
        # Relaunch this script under the venv python
        cmd = [str(vpy), str(Path(__file__).resolve())] + argv[1:]
        result = subprocess.run(cmd)
        sys.exit(result.returncode)


def pip_install(requirements: Path) -> None:
    """Install baseline dependencies.
    Always ensure pip is upgraded. If using global venv we still verify baseline packages
    but skip install if a sentinel indicates prior completion.
    """
    sentinel = VENV_DIR / ".lfm_baseline_deps_installed"
    if VENV_DIR == _GLOBAL_VENV and sentinel.exists():
        print("Using global venv; baseline dependencies previously satisfied (sentinel present).")
        return
    print("Upgrading pip and installing baseline dependencies...")
    subprocess.run([str(_venv_python()), "-m", "pip", "install", "-U", "pip"], check=True)
    subprocess.run([str(_venv_python()), "-m", "pip", "install", "-r", str(requirements)], check=True)
    try:
        sentinel.write_text("ok", encoding="utf-8")
    except Exception as e:
        print(f"WARNING: Could not write sentinel file: {e}")


def detect_cuda_version() -> tuple[int, int] | None:
    """Detect installed CUDA version by checking nvidia-smi. Returns (major, minor) tuple."""
    try:
        result = subprocess.run(["nvidia-smi"], capture_output=True, text=True, timeout=5)
        if result.returncode != 0:
            return None
        # Parse CUDA version from nvidia-smi output (e.g., "CUDA Version: 12.2")
        for line in result.stdout.split('\n'):
            if 'CUDA Version:' in line:
                version_str = line.split('CUDA Version:')[1].strip().split()[0]
                parts = version_str.split('.')
                if len(parts) >= 2:
                    return (int(parts[0]), int(parts[1]))
    except (subprocess.TimeoutExpired, FileNotFoundError, IndexError, ValueError):
        pass
    return None


def detect_and_set_cuda_path() -> None:
    """Detect CUDA installation using nvcc --version (most reliable method)."""
    if os.environ.get('CUDA_PATH'):
        return  # Already set
    
    try:
        # Use nvcc to find CUDA installation (most reliable)
        result = subprocess.run(["nvcc", "--version"], capture_output=True, text=True, timeout=5)
        if result.returncode == 0:
            # nvcc found - get its directory and derive CUDA_PATH
            nvcc_result = subprocess.run(["where" if platform.system().startswith("Win") else "which", "nvcc"], 
                                       capture_output=True, text=True, timeout=5)
            if nvcc_result.returncode == 0:
                nvcc_path = Path(nvcc_result.stdout.strip())
                # CUDA_PATH is typically nvcc's grandparent (nvcc is in bin/ subdirectory)
                cuda_path = nvcc_path.parent.parent
                os.environ['CUDA_PATH'] = str(cuda_path)
                print(f"+ Set CUDA_PATH={cuda_path} (detected via nvcc)")
                return
    except (subprocess.TimeoutExpired, FileNotFoundError):
        pass
    
    # Fallback: Check nvidia-ml-py for CUDA library path
    try:
        result = subprocess.run([str(_venv_python()), "-c", 
                               "import ctypes.util; import sys; "
                               "lib = ctypes.util.find_library('cudart'); "
                               "print(lib) if lib else sys.exit(1)"], 
                              capture_output=True, text=True, timeout=5)
        if result.returncode == 0:
            cudart_path = Path(result.stdout.strip())
            # Derive CUDA_PATH from cudart library location
            cuda_path = cudart_path.parent.parent  # lib/../ or bin/../
            os.environ['CUDA_PATH'] = str(cuda_path)
            print(f"+ Set CUDA_PATH={cuda_path} (detected via cudart)")
            return
    except (subprocess.TimeoutExpired, FileNotFoundError):
        pass
    
    print("⚠ Could not auto-detect CUDA_PATH (this is usually OK)")


def _get_site_packages_dir() -> str:
    """Return the active venv's site-packages path using the venv python."""
    code = (
        "import site, sys;"
        "paths = [p for p in site.getsitepackages() if 'site-packages' in p];"
        "print(paths[0] if paths else '')"
    )
    res = subprocess.run([str(_venv_python()), "-c", code], capture_output=True, text=True)
    return res.stdout.strip()


def add_nvidia_bins_to_path() -> None:
    """If NVIDIA CUDA runtime/nvrtc pip packages are present, add their bin/lib to PATH and DLL directories."""
    # Get site-packages directory from venv
    site_packages_path = _get_site_packages_dir()
    if not site_packages_path:
        print("⚠ Could not locate site-packages directory")
        return
    
    # Find NVIDIA CUDA package directories
    nvidia_base = Path(site_packages_path) / "nvidia"
    if not nvidia_base.exists():
        print(f"⚠ NVIDIA packages not found in: {nvidia_base}")
        return
    
    bins = []
    for module in ["cuda_runtime", "cuda_nvrtc", "cublas"]:
        module_path = nvidia_base / module
        if module_path.exists():
            for subdir in ["bin", "lib"]:
                dll_dir = module_path / subdir
                if dll_dir.exists():
                    bins.append(str(dll_dir))
                    print(f"  Found: {dll_dir}")
    
    if not bins:
        print("⚠ No NVIDIA CUDA bin/lib directories found")
        return
    
    # On Windows, add as DLL directories (works in same process)
    if os.name == "nt" and hasattr(os, "add_dll_directory"):
        for b in bins:
            try:
                os.add_dll_directory(b)
                print(f"  + Added DLL directory: {b}")
            except Exception as e:
                print(f"  X Could not add DLL directory {b}: {e}")
    
    # Also prepend to PATH for child processes
    current = os.environ.get("PATH", "")
    new_path = os.pathsep.join(bins + [current])
    os.environ["PATH"] = new_path
    
    # Set environment variable so child processes can add DLL directories too
    os.environ["LFM_NVIDIA_DLLS"] = os.pathsep.join(bins)
    
    print(f"+ Set LFM_NVIDIA_DLLS with {len(bins)} paths")
    print(f"  Paths: {os.environ['LFM_NVIDIA_DLLS'][:200]}...")


def _parse_nvrtc_missing(msg: str) -> tuple[int, int] | None:
    """Parse messages like 'nvrtc64_120_0.dll' -> (12, 0)."""
    import re
    m = re.search(r"nvrtc64_(\d{3})_", msg)
    if not m:
        return None
    code = m.group(1)
    try:
        maj = int(code[:2])
        minor = int(code[2])
        return maj, minor
    except Exception:
        return None


def ensure_cupy(allow_cpu: bool) -> None:
    # First, try to detect and set CUDA_PATH to avoid CuPy warnings
    detect_and_set_cuda_path()
    
    # Check if CuPy already works (must compile a kernel to verify NVRTC works)
    try:
        subprocess.run([str(_venv_python()), "-c", 
                       "import cupy as cp; a = cp.arange(100); b = a * 2; cp.asnumpy(b); import sys; sys.exit(0)"], 
                       check=True, capture_output=True, timeout=10)
        print("+ CuPy already installed and working")
        return
    except (subprocess.CalledProcessError, subprocess.TimeoutExpired):
        pass
    
    plat = platform.system().lower()
    
    # macOS typically lacks CUDA
    if not plat.startswith("win") and plat != "linux":
        print("INFO: CUDA GPU not supported on this platform by default. Installing NumPy for CPU.")
        if allow_cpu:
            subprocess.run([str(_venv_python()), "-m", "pip", "install", "numpy>=1.24.0"], check=True)
            return
        sys.exit(1)
    
    # Detect CUDA version to install matching CuPy
    print("Detecting CUDA version...")
    cuda_version = detect_cuda_version()
    
    if cuda_version:
        cuda_major, cuda_minor = cuda_version
        print(f"+ Detected CUDA {cuda_major}.{cuda_minor}")
        # Map CUDA version to CuPy package
        if cuda_major == 12:
            candidates = ["cupy-cuda12x"]
        elif cuda_major == 11:
            candidates = ["cupy-cuda11x"]
        else:
            candidates = ["cupy-cuda12x", "cupy-cuda11x"]  # Try both
    else:
        print("⚠ Could not detect CUDA version (nvidia-smi not found or failed)")
        cuda_major, cuda_minor = 12, 0  # Safe fallback
        candidates = ["cupy-cuda12x", "cupy-cuda11x"]
    
    # Sentinel files to avoid repeated heavy installs across invocations
    cupy_attempt = VENV_DIR / ".lfm_cupy_attempted"
    cupy_success = VENV_DIR / ".lfm_cupy_success"
    if cupy_success.exists():
        print("+ CuPy previously installed (sentinel detected) - skipping reinstall")
        return
    if cupy_attempt.exists():
        print("⚠ Prior CuPy attempt detected without success; retrying (may take time)...")
    else:
        try:
            cupy_attempt.write_text("attempt", encoding="utf-8")
        except Exception:
            pass

    for pkg in candidates:
        print(f"Installing {pkg} (pinned >=13.3,<14.0)...")
        try:
            subprocess.run([str(_venv_python()), "-m", "pip", "install", f"{pkg}>=13.3,<14.0"], check=True, capture_output=True)
            # Try to actually compile and run a kernel (forces NVRTC DLL loading)
            print(f"  Testing {pkg} with kernel compilation...")
            result = subprocess.run([str(_venv_python()), "-c", 
                                   "import cupy as cp; a = cp.arange(100); b = a * 2; cp.asnumpy(b); n = cp.cuda.runtime.getDeviceCount(); print(f'GPU devices: {n}')"], 
                                   capture_output=True, text=True, timeout=15)
            print(f"  Test result: returncode={result.returncode}")
            if result.returncode != 0:
                print(f"  Test failed - stdout: {result.stdout[:200]}")
                print(f"  Test failed - stderr: {result.stderr[:200]}")
            if result.returncode == 0:
                add_nvidia_bins_to_path()
                print("+ CuPy installed successfully - GPU acceleration enabled")
                try:
                    cupy_success.write_text("ok", encoding="utf-8")
                except Exception:
                    pass
                return

            # If import failed, attempt to deduce required NVRTC version and install exact minor
            stderr_text = (result.stderr or "") + (result.stdout or "")
            need = _parse_nvrtc_missing(stderr_text)
            if need is None:
                # Use detected CUDA version from nvidia-smi (already have cuda_major, cuda_minor in scope)
                need = (cuda_major, cuda_minor)
            
            if need is not None:
                maj, minor = need
                print(f"  Installing CUDA {maj}.{minor} Python libs...")
                
                # Install packages one at a time to catch failures
                packages = [
                    f"nvidia-cuda-nvrtc-cu{maj}=={maj}.{minor}.*",
                    f"nvidia-cuda-runtime-cu{maj}=={maj}.{minor}.*",
                    f"nvidia-cublas-cu{maj}"
                ]
                
                installed_any = False
                for package in packages:
                    try:
                        print(f"    Installing {package.split('==')[0]}...")
                        result = subprocess.run([str(_venv_python()), "-m", "pip", "install", package], 
                                              capture_output=True, text=True, timeout=120, check=True)
                        print(f"    + {package.split('==')[0]} installed")
                        installed_any = True
                    except subprocess.CalledProcessError as e:
                        print(f"    ⚠ Failed to install {package.split('==')[0]}: {e.stderr[:150] if e.stderr else 'unknown error'}")
                    except Exception as e:
                        print(f"    ⚠ Error installing {package.split('==')[0]}: {str(e)[:150]}")
                
                if installed_any:
                    add_nvidia_bins_to_path()
                    # Retry import
                    verify = subprocess.run([str(_venv_python()), "-c", "import cupy; import cupy.cuda.runtime as rt; print(rt.getDeviceCount())"], capture_output=True, text=True, timeout=15)
                    if verify.returncode == 0:
                        print("+ CuPy installed successfully after CUDA DLL wiring - GPU acceleration enabled")
                        try:
                            cupy_success.write_text("ok", encoding="utf-8")
                        except Exception:
                            pass
                        return
                    else:
                        print(f"  ⚠ CuPy still failing: {(verify.stderr or verify.stdout)[:200]}")
            else:
                print(f"  {pkg} installed but import failed; NVRTC version not deduced; trying next...")
        except (subprocess.CalledProcessError, subprocess.TimeoutExpired) as e:
            print(f"  {pkg} failed: {getattr(e, 'stderr', '') or ''}\n  Trying next...")
            continue
    
    # CuPy failed - fall back to CPU with NumPy
    if allow_cpu:
        print("⚠ CuPy installation failed for all CUDA versions")
        print("  Installing NumPy for CPU fallback...")
        subprocess.run([str(_venv_python()), "-m", "pip", "install", "numpy>=1.24.0"], check=True)
        print("+ NumPy installed - running on CPU")
        return
    
    print("ERROR: Failed to install CuPy and CPU fallback disabled.")
    print("  Either install CUDA toolkit or allow CPU mode.")
    sys.exit(1)


def check_gpu() -> bool:
    try:
        code = (
            "import sys, os;\n"
            "print('GPU Check: LFM_NVIDIA_DLLS =', os.environ.get('LFM_NVIDIA_DLLS', '(not set)'));\n"
            "# Add NVIDIA DLL dirs if env var is set\n"
            "if os.environ.get('LFM_NVIDIA_DLLS'):\n"
            "  for p in os.environ['LFM_NVIDIA_DLLS'].split(os.pathsep):\n"
            "    if p:\n"
            "      print(f'  Adding DLL dir: {p}');\n"
            "      try:\n"
            "        if os.name == 'nt' and hasattr(os, 'add_dll_directory'):\n"
            "          os.add_dll_directory(p);\n"
            "          print(f'    + Added');\n"
            "      except Exception as ex:\n"
            "        print(f'    X Failed: {ex}');\n"
            "try:\n"
            " print('Importing CuPy...');\n"
            " import cupy as cp;\n"
            " print('+ CuPy imported');\n"
            " n=cp.cuda.runtime.getDeviceCount();\n"
            " print(f'GPU devices: {n}');\n"
            " sys.exit(0 if n>0 else 2)\n"
            "except Exception as e:\n"
            " print(f'X GPU check failed: {type(e).__name__}: {e}'); sys.exit(3)\n"
        )
        result = subprocess.run([str(_venv_python()), "-c", code], capture_output=True, text=True)
        print(result.stdout.strip())
        if result.stderr:
            print("stderr:", result.stderr[:500])
        return result.returncode == 0
    except FileNotFoundError:
        print("WARNING: Could not invoke venv python for GPU check; assuming CPU.")
        return False


def parse_args(argv: list[str]) -> tuple[argparse.Namespace, list[str]]:
    """Parse user-facing flags.

    Supported flags:
      --fast        Run 12-test fast validation subset.
      --cpu         Force CPU mode (skip CuPy install attempt). Still runs tests using NumPy.
      --force-gpu   Require GPU; abort if CuPy cannot be installed/initialized.

    Mutual exclusion: --cpu cannot be combined with --force-gpu.
    If neither --cpu nor --force-gpu specified: attempt GPU first, fallback to CPU.
    """
    p = argparse.ArgumentParser(description="LFM one-stop parallel suite runner (runs ALL tiers by default)")
    p.add_argument("--fast", action="store_true", help="Run fast 12-test validation subset (2 per tier) instead of full suite")
    p.add_argument("--cpu", action="store_true", help="Force CPU mode (skip GPU attempt; install NumPy explicitly)")
    p.add_argument("--force-gpu", action="store_true", help="Hard-require GPU (fail if CuPy unavailable)")
    args, extra = p.parse_known_args(argv[1:])
    if args.cpu and args.force_gpu:
        print("ERROR: --cpu and --force-gpu cannot be used together.")
        sys.exit(2)
    return args, []


def build_suite_cmd(args: argparse.Namespace, parallel_suite_path: Path, extra: list[str]) -> list[str]:
    # Internal policy: default to running ALL tiers (1..7) with reasonable parallelism.
    cmd = [str(_venv_python()), str(parallel_suite_path)]
    if args.fast:
        cmd += ["--fast"]
    else:
        cmd += ["--tiers", "1,2,3,4,5,6,7", "--max-concurrent", "8"]
    return cmd


def main(argv: list[str]) -> int:
    ensure_python_version()
    # 1) Ensure venv exists and relaunch inside it
    create_venv_if_needed()
    relaunch_in_venv_if_needed(argv)

    # 2) Install dependencies if needed (skipped if leveraging global venv)
    req_file = _resolve_requirements()
    if not req_file.exists():
        print(f"ERROR: Requirements file not found: {req_file}")
        return 1
    # Always ensure baseline deps; then ensure CuPy for GPU
    pip_install(req_file)

    # 3) Parse args
    args, extra = parse_args(argv)

    # 3.1) GPU / CPU mode selection
    if args.cpu:
        # Force CPU environment variable so harness picks NumPy
        os.environ["LFM_FORCE_CPU"] = "1"
        print("[mode] CPU-only mode requested (--cpu). Skipping CuPy installation attempt.")
        subprocess.run([str(_venv_python()), "-m", "pip", "install", "numpy>=1.24.0"], check=True)
        has_gpu = False
    else:
        # Attempt GPU; allow CPU fallback unless --force-gpu
        allow_cpu_fallback = not args.force_gpu
        if args.force_gpu:
            os.environ["LFM_FORCE_GPU"] = "1"
            print("[mode] GPU required (--force-gpu). Will abort if unavailable.")
        else:
            print("[mode] Attempting GPU first; will fallback to CPU if unavailable.")
        ensure_cupy(allow_cpu=allow_cpu_fallback)
        print("\nSetting up NVIDIA CUDA DLL paths...")
        add_nvidia_bins_to_path()
        has_gpu = check_gpu()
        if args.force_gpu and not has_gpu:
            print("ERROR: GPU not available after CuPy install attempts (--force-gpu). Aborting.")
            return 3
        if not has_gpu and not args.cpu:
            print("[mode] Falling back to CPU (GPU unavailable).")
            os.environ["LFM_FORCE_CPU"] = "1"

    # 4) Change to src dir and run parallel suite
    src_dir = _resolve_src_dir()
    if not src_dir.exists():
        print(f"ERROR: Source directory not found: {src_dir}")
        return 1
    # Set results root to QuickStart so outputs land in QuickStart/results
    try:
        os.environ["LFM_RESULTS_ROOT"] = str(QS_DIR)
        # Also export workspace root hint for caching/diagnostics
        os.environ.setdefault("LFM_WORKSPACE_ROOT", str(QS_DIR))
    except Exception:
        pass
    # Ensure QuickStart/results exists (target for outputs)
    try:
        (QS_DIR / "results").mkdir(parents=True, exist_ok=True)
    except Exception:
        pass
    os.chdir(str(src_dir))

    # Resolve parallel suite path for command
    parallel_suite = _resolve_parallel_suite(src_dir)
    if not parallel_suite.exists():
        print(f"ERROR: Suite entry not found: {parallel_suite}")
        return 1
    cmd = build_suite_cmd(args, parallel_suite, extra=extra)
    print("GPU available:" , "yes" if has_gpu else "no")
    print("Running:", " ".join(cmd))
    proc = subprocess.run(cmd)

    # 5) Write environment summary
    try:
        env_summary = {
            "gpu_available": has_gpu,
            "forced_cpu": bool(os.environ.get("LFM_FORCE_CPU") == "1"),
            "forced_gpu": bool(os.environ.get("LFM_FORCE_GPU") == "1"),
            "python": str(_venv_python()),
            "platform": platform.platform(),
            "fast_mode": bool(args.fast),
            "exit_code": proc.returncode,
            "cupy_attempted": (VENV_DIR / ".lfm_cupy_attempted").exists(),
            "cupy_success": (VENV_DIR / ".lfm_cupy_success").exists(),
            "venv_dir": str(VENV_DIR),
        }
        (QS_DIR / "results").mkdir(parents=True, exist_ok=True)
        import json
        with open(QS_DIR / "results" / "environment.json", "w", encoding="utf-8") as f:
            json.dump(env_summary, f, indent=2)
        print("[env] Wrote environment summary to results/environment.json")
    except Exception as e:
        print(f"[env] WARNING: Failed to write environment summary: {e}")
    return proc.returncode


if __name__ == "__main__":
    sys.exit(main(sys.argv))
