#!/usr/bin/env python3
"""Run the Hydrus client (top-level helper)

This standalone helper is intended to live in the project's top-level `scripts/`
folder so it remains available even if the Hydrus repository subfolder is not
present or its copy of this helper gets removed.

Features (subset of the repo helper):
- Locate repository venv (default: <workspace>/hydrusnetwork/.venv)
- Install or reinstall scripts/requirements.txt into the venv
- Verify key imports
- Launch hydrus_client.py (foreground or detached)
- Install/uninstall simple user-level start-on-boot services (schtasks/systemd/crontab)

Usage examples:
  python scripts/run_client.py --verify
  python scripts/run_client.py --detached --headless
  python scripts/run_client.py --install-deps --verify

"""
from __future__ import annotations

import argparse
import os
import shlex
import shutil
import subprocess
import sys
from pathlib import Path
from typing import List, Optional


def get_python_in_venv(venv_dir: Path) -> Optional[Path]:
    try:
        v = Path(venv_dir)
        # Windows
        win_python = v / "Scripts" / "python.exe"
        if win_python.exists():
            return win_python
        # Unix
        unix_python = v / "bin" / "python"
        if unix_python.exists():
            return unix_python
        unix_py3 = v / "bin" / "python3"
        if unix_py3.exists():
            return unix_py3
    except Exception:
        pass
    return None


def find_requirements(root: Path) -> Optional[Path]:
    candidates = [root / "scripts" / "requirements.txt", root / "requirements.txt", root / "client" / "requirements.txt"]
    for c in candidates:
        if c.exists():
            return c
    # shallow two-level search
    try:
        for p in root.iterdir():
            if not p.is_dir():
                continue
            for child in (p,
                          ):
                candidate = child / "requirements.txt"
                if candidate.exists():
                    return candidate
    except Exception:
        pass
    return None


def install_requirements(
    venv_py: Path,
    req_path: Path,
    reinstall: bool = False
) -> bool:
    try:
        print(f"Installing {req_path} into venv ({venv_py})...")
        subprocess.run(
            [str(venv_py),
             "-m",
             "pip",
             "install",
             "--upgrade",
             "pip"],
            check=True
        )
        install_cmd = [str(venv_py), "-m", "pip", "install", "-r", str(req_path)]
        if reinstall:
            install_cmd = [
                str(venv_py),
                "-m",
                "pip",
                "install",
                "--upgrade",
                "--force-reinstall",
                "-r",
                str(req_path),
            ]
        subprocess.run(install_cmd, check=True)
        return True
    except subprocess.CalledProcessError as e:
        print("Failed to install requirements:", e)
        return False


def parse_requirements_file(req_path: Path) -> List[str]:
    names: List[str] = []
    try:
        with req_path.open("r", encoding="utf-8") as fh:
            for raw in fh:
                line = raw.strip()
                if not line or line.startswith("#"):
                    continue
                if line.startswith("-e") or line.startswith("--"):
                    continue
                if "://" in line or line.startswith("file:"):
                    continue
                line = line.split(";")[0].strip()
                line = line.split("[")[0].strip()
                for sep in ("==", ">=", "<=", "~=", "!=", ">", "<", "==="):
                    if sep in line:
                        line = line.split(sep)[0].strip()
                if " @ " in line:
                    line = line.split(" @ ")[0].strip()
                if line:
                    names.append(line.split()[0].strip().lower())
    except Exception:
        pass
    return names


def verify_imports(venv_py: Path, packages: List[str]) -> bool:
    # Map some package names to import names (handle common cases where package name differs from import name)
    import_map = {
        "pyyaml": "yaml",
        "pillow": "PIL",
        "python-dateutil": "dateutil",
        "beautifulsoup4": "bs4",
        "pillow-heif": "pillow_heif",
        "pillow-jxl-plugin": "pillow_jxl_plugin",
        "pyopenssl": "OpenSSL",
        "pysocks": "socks",
        "service-identity": "service_identity",
        "show-in-file-manager": "show_in_file_manager",
        "opencv-python-headless": "cv2",
        "mpv": "mpv",
        "pyside6": "PySide6",
    }
    missing = []
    for pkg in packages:
        try:
            out = subprocess.run(
                [str(venv_py),
                 "-m",
                 "pip",
                 "show",
                 pkg],
                stdout=subprocess.PIPE,
                stderr=subprocess.PIPE,
                text=True,
                timeout=10,
            )
        except subprocess.TimeoutExpired:
            missing.append(pkg)
            continue
        except Exception:
            missing.append(pkg)
            continue

        if out.returncode != 0 or not out.stdout.strip():
            missing.append(pkg)
            continue

        import_name = import_map.get(pkg, pkg)
        try:
            subprocess.run(
                [str(venv_py),
                 "-c",
                 f"import {import_name}"],
                check=True,
                stdout=subprocess.DEVNULL,
                stderr=subprocess.DEVNULL,
                timeout=10,
            )
        except (subprocess.CalledProcessError, subprocess.TimeoutExpired):
            missing.append(pkg)
    if missing:
        print(
            "The following packages were not importable in the venv:",
            ", ".join(missing)
        )
        return False
    return True


def is_first_run(repo_root: Path) -> bool:
    try:
        db_dir = repo_root / "db"
        if db_dir.exists() and any(db_dir.iterdir()):
            return False
        for f in repo_root.glob("*.db"):
            if f.exists():
                return False
    except Exception:
        return False
    return True


# --- Service install/uninstall helpers -----------------------------------


def install_service_windows(
    service_name: str,
    repo_root: Path,
    venv_py: Path,
    headless: bool = True,
    detached: bool = True,
    start_on: str = "logon",
) -> bool:
    try:
        schtasks = shutil.which("schtasks")
        if not schtasks:
            print(
                "schtasks not available on this system; cannot install Windows scheduled task."
            )
            return False

        bat = repo_root / "run-client.bat"
        if not bat.exists():
            # Use escaped backslashes to avoid Python "invalid escape sequence" warnings
            content = '@echo off\n"%~dp0\\.venv\\Scripts\\python.exe" "%~dp0hydrus_client.py" %*\n'
            bat.write_text(content, encoding="utf-8")

        tr = str(bat)
        sc = "ONLOGON" if start_on == "logon" else "ONSTART"
        cmd = [
            schtasks,
            "/Create",
            "/SC",
            sc,
            "/TN",
            service_name,
            "/TR",
            f'"{tr}"',
            "/RL",
            "LIMITED",
            "/F",
        ]
        subprocess.run(cmd, check=True)
        print(f"Scheduled task '{service_name}' created ({sc}).")
        return True
    except subprocess.CalledProcessError as e:
        print("Failed to create scheduled task:", e)
        return False
    except Exception as exc:
        print("Windows install-service error:", exc)
        return False


def uninstall_service_windows(service_name: str) -> bool:
    try:
        schtasks = shutil.which("schtasks")
        if not schtasks:
            print(
                "schtasks not available on this system; cannot remove scheduled task."
            )
            return False
        cmd = [schtasks, "/Delete", "/TN", service_name, "/F"]
        subprocess.run(cmd, check=True)
        print(f"Scheduled task '{service_name}' removed.")
        return True
    except subprocess.CalledProcessError as e:
        print("Failed to delete scheduled task:", e)
        return False
    except Exception as exc:
        print("Windows uninstall-service error:", exc)
        return False


def install_service_systemd(
    service_name: str,
    repo_root: Path,
    venv_py: Path,
    headless: bool = True,
    detached: bool = True
) -> bool:
    try:
        systemctl = shutil.which("systemctl")
        if not systemctl:
            print(
                "systemctl not available; falling back to crontab @reboot (if present)."
            )
            return install_service_cron(
                service_name,
                repo_root,
                venv_py,
                headless,
                detached
            )

        unit_dir = Path.home() / ".config" / "systemd" / "user"
        unit_dir.mkdir(parents=True, exist_ok=True)
        unit_file = unit_dir / f"{service_name}.service"
        exec_args = f'"{venv_py}" "{str(repo_root / "run_client.py")}" --detached '
        exec_args += "--headless " if headless else "--gui "
        content = f"[Unit]\nDescription=Hydrus Client (user)\nAfter=network.target\n\n[Service]\nType=simple\nExecStart={exec_args}\nWorkingDirectory={str(repo_root)}\nRestart=on-failure\nEnvironment=PYTHONUNBUFFERED=1\n\n[Install]\nWantedBy=default.target\n"
        unit_file.write_text(content, encoding="utf-8")
        subprocess.run([systemctl, "--user", "daemon-reload"], check=True)
        subprocess.run(
            [systemctl,
             "--user",
             "enable",
             "--now",
             f"{service_name}.service"],
            check=True
        )
        print(f"systemd user service '{service_name}' installed and started.")
        return True
    except subprocess.CalledProcessError as e:
        print("Failed to create systemd user service:", e)
        return False
    except Exception as exc:
        print("systemd install error:", exc)
        return False


def uninstall_service_systemd(service_name: str) -> bool:
    try:
        systemctl = shutil.which("systemctl")
        if not systemctl:
            print("systemctl not available; cannot uninstall systemd service.")
            return False
        subprocess.run(
            [systemctl,
             "--user",
             "disable",
             "--now",
             f"{service_name}.service"],
            check=False
        )
        unit_file = Path.home(
        ) / ".config" / "systemd" / "user" / f"{service_name}.service"
        if unit_file.exists():
            unit_file.unlink()
        subprocess.run([systemctl, "--user", "daemon-reload"], check=True)
        print(f"systemd user service '{service_name}' removed.")
        return True
    except Exception as exc:
        print("systemd uninstall error:", exc)
        return False


def install_service_cron(
    service_name: str,
    repo_root: Path,
    venv_py: Path,
    headless: bool = True,
    detached: bool = True
) -> bool:
    try:
        crontab = shutil.which("crontab")
        if not crontab:
            print("crontab not available; cannot install reboot cron job.")
            return False
        entry = f"@reboot {venv_py} {str(repo_root / 'run_client.py')} --detached {'--headless' if headless else '--gui'} # {service_name}\n"
        proc = subprocess.run(
            [crontab,
             "-l"],
            stdout=subprocess.PIPE,
            stderr=subprocess.PIPE,
            text=True
        )
        existing = proc.stdout if proc.returncode == 0 else ""
        if entry.strip() in existing:
            print("Crontab entry already present; skipping.")
            return True
        new = existing + "\n" + entry
        subprocess.run([crontab, "-"], input=new, text=True, check=True)
        print(f"Crontab @reboot entry added for '{service_name}'.")
        return True
    except subprocess.CalledProcessError as e:
        print("Failed to install crontab entry:", e)
        return False
    except Exception as exc:
        print("crontab install error:", exc)
        return False


def uninstall_service_cron(service_name: str, repo_root: Path, venv_py: Path) -> bool:
    try:
        crontab = shutil.which("crontab")
        if not crontab:
            print("crontab not available; cannot remove reboot cron job.")
            return False
        proc = subprocess.run(
            [crontab,
             "-l"],
            stdout=subprocess.PIPE,
            stderr=subprocess.PIPE,
            text=True
        )
        if proc.returncode != 0:
            print("No crontab found for user; nothing to remove.")
            return True
        lines = [l for l in proc.stdout.splitlines() if f"# {service_name}" not in l]
        new = "\n".join(lines) + "\n"
        subprocess.run([crontab, "-"], input=new, text=True, check=True)
        print(f"Crontab entry for '{service_name}' removed.")
        return True
    except subprocess.CalledProcessError as e:
        print("Failed to modify crontab:", e)
        return False
    except Exception as exc:
        print("crontab uninstall error:", exc)
        return False


def install_service_auto(
    service_name: str,
    repo_root: Path,
    venv_py: Path,
    headless: bool = True,
    detached: bool = True
) -> bool:
    try:
        if os.name == "nt":
            return install_service_windows(
                service_name,
                repo_root,
                venv_py,
                headless=headless,
                detached=detached
            )
        else:
            if shutil.which("systemctl"):
                return install_service_systemd(
                    service_name,
                    repo_root,
                    venv_py,
                    headless=headless,
                    detached=detached
                )
            else:
                return install_service_cron(
                    service_name,
                    repo_root,
                    venv_py,
                    headless=headless,
                    detached=detached
                )
    except Exception as exc:
        print("install_service_auto error:", exc)
        return False


def uninstall_service_auto(service_name: str, repo_root: Path, venv_py: Path) -> bool:
    try:
        if os.name == "nt":
            return uninstall_service_windows(service_name)
        else:
            if shutil.which("systemctl"):
                return uninstall_service_systemd(service_name)
            else:
                return uninstall_service_cron(service_name, repo_root, venv_py)
    except Exception as exc:
        print("uninstall_service_auto error:", exc)
        return False


def print_activation_instructions(
    repo_root: Path,
    venv_dir: Path,
    venv_py: Path
) -> None:
    print("\nActivation and run examples:")
    # PowerShell
    print(f"  PowerShell:\n    . {shlex.quote(str(venv_dir))}\\Scripts\\Activate.ps1")
    # CMD
    print(f"  CMD:\n    {str(venv_dir)}\\Scripts\\activate.bat")
    # Bash
    print(f"  Bash (Linux/macOS/WSL):\n    source {str(venv_dir)}/bin/activate")
    print(
        f"\nDirect run without activating:\n  {str(venv_py)} {str(repo_root/ 'hydrus_client.py')}"
    )


def detach_kwargs_for_platform():
    kwargs = {}
    if os.name == "nt":
        CREATE_NEW_PROCESS_GROUP = getattr(subprocess, "CREATE_NEW_PROCESS_GROUP", 0)
        DETACHED_PROCESS = getattr(subprocess, "DETACHED_PROCESS", 0)
        flags = CREATE_NEW_PROCESS_GROUP | DETACHED_PROCESS
        if flags:
            kwargs["creationflags"] = flags
    else:
        kwargs["start_new_session"] = True
    return kwargs


def find_venv_python(repo_root: Path,
                     venv_arg: Optional[str],
                     venv_name: str) -> Optional[Path]:
    # venv_arg may be a python executable or a directory
    if venv_arg:
        p = Path(venv_arg)
        if p.exists():
            if p.is_file():
                return p
            else:
                found = get_python_in_venv(p)
                if found:
                    return found
    # Try repo-local venv
    dir_candidate = repo_root / venv_name
    found = get_python_in_venv(dir_candidate)
    if found:
        return found
    # Fallback: if current interpreter is inside repo venv
    try:
        cur = Path(sys.executable).resolve()
        if repo_root in cur.parents:
            return cur
    except Exception:
        pass
    return None


def _python_can_import(python_exe: Path, modules: List[str]) -> bool:
    """Return True if the given python executable can import all modules in the list.

    Uses a subprocess to avoid side-effects in the current interpreter.
    """
    if not python_exe:
        return False
    try:
        # Build a short import test string. Use semicolons to ensure any import error results in non-zero exit.
        imports = ";".join([f"import {m}" for m in modules])
        out = subprocess.run(
            [str(python_exe),
             "-c",
             imports],
            stdout=subprocess.DEVNULL,
            stderr=subprocess.DEVNULL,
            timeout=10,
        )
        return out.returncode == 0
    except (subprocess.TimeoutExpired, Exception):
        return False


def main(argv: Optional[List[str]] = None) -> int:
    p = argparse.ArgumentParser(
        description=
        "Run hydrus_client.py using the repo-local venv Python (top-level helper)"
    )
    p.add_argument(
        "--venv",
        help="Path to venv dir or python executable (overrides default .venv)"
    )
    p.add_argument(
        "--venv-name",
        default=".venv",
        help="Name of the venv folder to look for (default: .venv)"
    )
    p.add_argument(
        "--client",
        default="hydrus_client.py",
        help="Path to hydrus_client.py relative to repo root",
    )
    p.add_argument(
        "--repo-root",
        default=None,
        help="Path to the hydrus repository root (overrides auto-detection)",
    )
    p.add_argument(
        "--install-deps",
        action="store_true",
        help="Install requirements.txt into the venv before running",
    )
    p.add_argument(
        "--reinstall",
        action="store_true",
        help=
        "Force re-install dependencies from requirements.txt into the venv (uses --force-reinstall)",
    )
    p.add_argument(
        "--verify",
        action="store_true",
        help=
        "Verify that packages from requirements.txt are importable in the venv (after install)",
    )
    p.add_argument(
        "--no-verify",
        action="store_true",
        help=
        "Skip verification and do not prompt to install missing dependencies; proceed to run with the chosen Python",
    )
    p.add_argument(
        "--headless",
        action="store_true",
        help=
        "Attempt to launch the client without showing the Qt GUI (best-effort). Default for subsequent runs; first run will show GUI unless --headless is supplied",
    )
    p.add_argument(
        "--gui",
        action="store_true",
        help="Start the client with the GUI visible (overrides headless/default) ",
    )
    p.add_argument(
        "--detached",
        action="store_true",
        help="Start the client and do not wait (detached)"
    )
    p.add_argument(
        "--install-service",
        action="store_true",
        help=
        "Install a user-level start-on-boot service/scheduled task for the hydrus client",
    )
    p.add_argument(
        "--uninstall-service",
        action="store_true",
        help="Remove an installed start-on-boot service/scheduled task",
    )
    p.add_argument(
        "--service-name",
        default="hydrus-client",
        help="Name of the service / scheduled task to install (default: hydrus-client)",
    )
    p.add_argument(
        "--cwd",
        default=None,
        help="Working directory to start the client in (default: repo root)"
    )
    p.add_argument("--quiet", action="store_true", help="Reduce output")
    p.add_argument(
        "client_args",
        nargs=argparse.REMAINDER,
        help="Arguments to pass to hydrus_client.py (prefix with --)",
    )

    args = p.parse_args(argv)

    workspace_root = Path(__file__).resolve().parent.parent
    # Determine default repo root: prefer <workspace>/hydrusnetwork when present
    if args.repo_root:
        repo_root = Path(args.repo_root).expanduser().resolve()
    else:
        candidate = workspace_root / "hydrusnetwork"
        if candidate.exists():
            repo_root = candidate
        else:
            repo_root = workspace_root

    venv_py = find_venv_python(repo_root, args.venv, args.venv_name)

    def _is_running_in_virtualenv() -> bool:
        try:
            return hasattr(sys,
                           "real_prefix") or getattr(sys,
                                                     "base_prefix",
                                                     None
                                                     ) != getattr(sys,
                                                                  "prefix",
                                                                  None)
        except Exception:
            return False

    # Prefer the current interpreter if the helper was invoked from a virtualenv
    # and the user did not explicitly pass --venv. This matches the user's likely
    # intent when they called: <venv_python> scripts/run_client.py ...
    cur_py = Path(sys.executable)
    if args.venv is None and _is_running_in_virtualenv() and cur_py:
        # If current interpreter looks like a venv and can import required modules,
        # prefer it immediately rather than forcing the repo venv.
        req = find_requirements(repo_root)
        pkgs = parse_requirements_file(req) if req else []
        check_pkgs = pkgs if pkgs else ["pyyaml"]
        try:
            ok_cur = verify_imports(cur_py, check_pkgs)
        except Exception:
            ok_cur = _python_can_import(cur_py, ["yaml"])
        if ok_cur:
            venv_py = cur_py
            if not args.quiet:
                print(f"Using current Python interpreter as venv: {cur_py}")

    # If we found a repo-local venv, verify it has at least the core imports (or the
    # packages listed in requirements.txt). If not, prefer the current Python
    # interpreter when that interpreter looks more suitable (e.g. has deps installed).
    if venv_py and venv_py != cur_py:
        if not args.quiet:
            print(f"Found venv python: {venv_py}")
        req = find_requirements(repo_root)
        pkgs = parse_requirements_file(req) if req else []
        check_pkgs = pkgs if pkgs else ["pyyaml"]
        try:
            ok_venv = verify_imports(venv_py, check_pkgs)
        except Exception:
            ok_venv = _python_can_import(venv_py, ["yaml"])

        if not ok_venv:
            try:
                ok_cur = verify_imports(cur_py, check_pkgs)
            except Exception:
                ok_cur = _python_can_import(cur_py, ["yaml"])

            if ok_cur:
                if not args.quiet:
                    print(
                        f"Repository venv ({venv_py}) is missing required packages; using current Python at {cur_py} instead."
                    )
                venv_py = cur_py
            else:
                print(
                    "Warning: repository venv appears to be missing required packages. If the client fails to start, run this helper with --install-deps to install requirements into the repo venv, or use --venv to point to a Python that has the deps."
                )

    if not venv_py:
        print("Could not locate a repository venv.")
        print(
            "Create one with: python -m venv .venv (inside your hydrus repo) and then re-run this helper, or use the installer to create it for you."
        )
        print_activation_instructions(
            repo_root,
            repo_root / args.venv_name,
            repo_root / args.venv_name
        )
        return 2

    client_path = (repo_root / args.client).resolve()
    if not client_path.exists():
        print(f"Client file not found: {client_path}")
        return 3

    cwd = Path(args.cwd).resolve() if args.cwd else repo_root

    # Optionally install dependencies
    if args.install_deps or args.reinstall:
        req = find_requirements(repo_root)
        if not req:
            print("No requirements.txt found; skipping install")
        else:
            ok = install_requirements(venv_py, req, reinstall=args.reinstall)
            if not ok:
                print("Dependency installation failed; aborting")
                return 4
            if args.verify:
                pkgs = parse_requirements_file(req)
                if pkgs:
                    okv = verify_imports(venv_py, pkgs)
                    if not okv:
                        print(
                            "Verification failed; see instructions above to re-run installation."
                        )

    # If not installing but user asked to verify, do verification only
    if args.verify and not (args.install_deps or args.reinstall):
        req = find_requirements(repo_root)
        if req:
            pkgs = parse_requirements_file(req)
            if pkgs and not verify_imports(venv_py, pkgs):
                print(
                    "Verification found missing packages. Use --install-deps to install into the venv."
                )

    # If the venv appears to be missing required packages, offer to install them interactively
    req = find_requirements(repo_root)
    pkgs = parse_requirements_file(req) if req else []
    check_pkgs = pkgs if pkgs else ["pyyaml"]
    try:
        venv_ok = verify_imports(venv_py, check_pkgs)
    except Exception:
        venv_ok = _python_can_import(venv_py, ["yaml"])  # fallback

    if not venv_ok:
        # If user explicitly requested install, we've already attempted it above; otherwise, do not block.
        if args.install_deps or args.reinstall:
            # if we already did an install attempt and it still fails, bail
            print("Dependency verification failed after install; aborting.")
            return 4

        # Default: print a clear warning and proceed to launch with the repository venv
        if args.no_verify:
            print(
                "Repository venv is missing required packages; proceeding without verification as requested ( --no-verify ). Client may fail to start."
            )
        else:
            print(
                "Warning: repository venv appears to be missing required packages. Proceeding to launch with repository venv; the client may fail to start. Use --install-deps to install requirements into the repo venv."
            )

        # Do not prompt to switch to another interpreter automatically; the user can
        # re-run with --venv to select a different python if desired.

    # Service install/uninstall requests
    if args.install_service or args.uninstall_service:
        first_run = is_first_run(repo_root)
        if args.gui:
            use_headless = False
        elif args.headless:
            use_headless = True
        else:
            use_headless = not first_run

        if args.install_service:
            ok = install_service_auto(
                args.service_name,
                repo_root,
                venv_py,
                headless=use_headless,
                detached=True
            )
            return 0 if ok else 6
        if args.uninstall_service:
            ok = uninstall_service_auto(args.service_name, repo_root, venv_py)
            return 0 if ok else 7

    # Prepare the command
    client_args = args.client_args or []
    cmd = [str(venv_py), str(client_path)] + client_args

    # Determine headless vs GUI
    first_run = is_first_run(repo_root)
    if args.gui:
        headless = False
    elif args.headless:
        headless = True
    else:
        headless = not first_run
    if not args.quiet and first_run:
        print("First run detected: defaulting to GUI unless --headless is specified.")

    env = os.environ.copy()
    if headless:
        if os.name == "posix" and shutil.which("xvfb-run"):
            xvfb_cmd = [
                "xvfb-run",
                "--auto-servernum",
                "--server-args=-screen 0 1024x768x24"
            ]
            cmd = xvfb_cmd + cmd
            if not args.quiet:
                print("Headless: using xvfb-run to provide a virtual X server")
        else:
            env["QT_QPA_PLATFORM"] = "offscreen"
            if not args.quiet:
                print("Headless: setting QT_QPA_PLATFORM=offscreen (best-effort)")

    # Inform which Python will be used
    if not args.quiet:
        try:
            print(f"Launching Hydrus client with Python: {venv_py}")
            print(f"Command: {' '.join(shlex.quote(str(c)) for c in cmd)}")
        except Exception:
            pass

    # Launch
    if args.detached:
        try:
            kwargs = detach_kwargs_for_platform()
            kwargs.update({
                "cwd": str(cwd),
                "env": env
            })
            subprocess.Popen(cmd, **kwargs)
            print("Hydrus client launched (detached).")
            return 0
        except Exception as exc:
            print("Failed to launch client detached:", exc)
            return 5
    else:
        try:
            subprocess.run(cmd, cwd=str(cwd), env=env)
            return 0
        except subprocess.CalledProcessError as e:
            print("hydrus client exited non-zero:", e)
            return 5


if __name__ == "__main__":
    raise SystemExit(main())
