#!/usr/bin/env python3
"""Install and manage the Hydrus web GUI companion app.

This helper keeps the Medios bootstrap as the single user-facing installer entry
point while isolating the Node/npm-specific logic required by
`api-HydrusNetwork`.
"""

from __future__ import annotations

import argparse
import os
import platform
import shlex
import shutil
import subprocess
import sys
from pathlib import Path


DEFAULT_REPO_URL = "https://code.glowers.club/goyimnose/api-HydrusNetwork.git"
DEFAULT_DEST_NAME = "api-HydrusNetwork"
DEFAULT_SERVICE_NAME = "hydrus-web-gui"
DEFAULT_PORT = 4173


def run(
    cmd: list[str],
    *,
    cwd: Path | None = None,
    check: bool = True,
    capture_output: bool = False,
    debug: bool = False,
) -> subprocess.CompletedProcess:
    if debug:
        location = f" (cwd={cwd})" if cwd else ""
        print(f"> {' '.join(cmd)}{location}")
    return subprocess.run(
        cmd,
        cwd=str(cwd) if cwd else None,
        check=check,
        text=capture_output,
        capture_output=capture_output,
    )


def find_executable(*names: str) -> str | None:
    for name in names:
        found = shutil.which(name)
        if found:
            return found
    return None


def detect_npm() -> str | None:
    if os.name == "nt":
        return find_executable("npm.cmd", "npm")
    return find_executable("npm")


def parse_node_major_version(node_exe: str, debug: bool = False) -> int | None:
    try:
        result = run([node_exe, "--version"], capture_output=True, debug=debug)
        version_text = (result.stdout or "").strip()
        if version_text.startswith("v"):
            version_text = version_text[1:]
        major = version_text.split(".", 1)[0]
        return int(major)
    except Exception:
        return None


def with_privilege(cmd: list[str]) -> list[str]:
    if os.name == "nt":
        return cmd
    if hasattr(os, "geteuid") and os.geteuid() == 0:
        return cmd
    sudo = shutil.which("sudo")
    if sudo:
        return [sudo, *cmd]
    return cmd


def ensure_node_runtime(debug: bool = False) -> tuple[str, str]:
    node_exe = find_executable("node")
    npm_exe = detect_npm()
    major = parse_node_major_version(node_exe, debug=debug) if node_exe else None

    if node_exe and npm_exe and major is not None and major >= 20:
        return node_exe, npm_exe

    system = platform.system().lower()
    print("Node.js 20+ and npm are required for the Hydrus web GUI. Attempting installation...")

    if system == "windows":
        winget = find_executable("winget")
        if not winget:
            raise RuntimeError(
                "Node.js 20+ is required. Install it manually or ensure winget is available."
            )
        run(
            [
                winget,
                "install",
                "--id",
                "OpenJS.NodeJS.LTS",
                "-e",
                "--accept-package-agreements",
                "--accept-source-agreements",
            ],
            debug=debug,
        )
    elif system == "linux":
        if find_executable("apt"):
            run(with_privilege(["apt", "update"]), debug=debug)
            run(with_privilege(["apt", "install", "-y", "nodejs", "npm"]), debug=debug)
        elif find_executable("dnf"):
            run(with_privilege(["dnf", "install", "-y", "nodejs", "npm"]), debug=debug)
        elif find_executable("pacman"):
            run(with_privilege(["pacman", "-Sy", "--noconfirm", "nodejs", "npm"]), debug=debug)
        else:
            raise RuntimeError(
                "Node.js 20+ is required. Install nodejs/npm manually for this distribution."
            )
    else:
        raise RuntimeError("Automatic Hydrus web GUI setup is currently supported on Windows and Linux.")

    node_exe = find_executable("node")
    npm_exe = detect_npm()
    major = parse_node_major_version(node_exe, debug=debug) if node_exe else None
    if not node_exe or not npm_exe or major is None or major < 20:
        raise RuntimeError(
            "Node.js installation completed, but Node.js 20+ with npm is still not available on PATH."
        )
    return node_exe, npm_exe


def clone_or_update_repo(repo_url: str, dest: Path, force: bool = False, debug: bool = False) -> None:
    git = find_executable("git")
    if not git:
        raise RuntimeError("git is required to install the Hydrus web GUI.")

    if dest.exists() and force:
        shutil.rmtree(dest)

    if dest.exists():
        if (dest / ".git").exists():
            run([git, "-C", str(dest), "pull", "--ff-only"], debug=debug)
            return
        if any(dest.iterdir()):
            raise RuntimeError(
                f"Destination already exists and is not a git repository: {dest}. Use --force to replace it."
            )

    dest.parent.mkdir(parents=True, exist_ok=True)
    run([git, "clone", "--depth", "1", repo_url, str(dest)], debug=debug)


def install_web_gui_dependencies(app_root: Path, npm_exe: str, debug: bool = False) -> None:
    run([npm_exe, "install"], cwd=app_root, debug=debug)
    run([npm_exe, "run", "build"], cwd=app_root, debug=debug)


def write_service_launcher(app_root: Path, npm_exe: str, port: int) -> Path:
    if os.name == "nt":
        launcher = app_root / "run-hydrus-web-gui.cmd"
        launcher.write_text(
            "@echo off\n"
            "setlocal\n"
            "cd /d \"%~dp0\"\n"
            f"call \"{npm_exe}\" run build\n"
            "if errorlevel 1 exit /b %errorlevel%\n"
            f"call \"{npm_exe}\" run preview -- --host 0.0.0.0 --port {port}\n",
            encoding="utf-8",
        )
        return launcher

    launcher = app_root / "run-hydrus-web-gui.sh"
    launcher.write_text(
        "#!/usr/bin/env bash\n"
        "set -e\n"
        "cd \"$(dirname \"$0\")\"\n"
        f"\"{npm_exe}\" run build\n"
        f"exec \"{npm_exe}\" run preview -- --host 0.0.0.0 --port {port}\n",
        encoding="utf-8",
    )
    launcher.chmod(launcher.stat().st_mode | 0o111)
    return launcher


def install_service_windows(service_name: str, launcher_path: Path, debug: bool = False) -> None:
    schtasks = find_executable("schtasks")
    if not schtasks:
        raise RuntimeError("schtasks is required to register the Hydrus web GUI on Windows.")

    tr_command = f'cmd.exe /c ""{launcher_path}""'
    run(
        [
            schtasks,
            "/Create",
            "/SC",
            "ONLOGON",
            "/TN",
            service_name,
            "/TR",
            tr_command,
            "/RL",
            "LIMITED",
            "/F",
        ],
        debug=debug,
    )


def install_service_linux(service_name: str, app_root: Path, launcher_path: Path, debug: bool = False) -> None:
    systemctl = find_executable("systemctl")
    if not systemctl:
        raise RuntimeError("systemctl is required to register the Hydrus web GUI on Linux.")

    if hasattr(os, "geteuid") and os.geteuid() == 0:
        unit_dir = Path("/etc/systemd/system")
        unit_name = f"{service_name}.service"
        wanted_by = "multi-user.target"
        systemctl_cmd = [systemctl]
        service_user = os.environ.get("SUDO_USER") or os.environ.get("USER")
    else:
        unit_dir = Path.home() / ".config" / "systemd" / "user"
        unit_name = f"{service_name}.service"
        wanted_by = "default.target"
        systemctl_cmd = [systemctl, "--user"]
        service_user = None

    unit_dir.mkdir(parents=True, exist_ok=True)
    unit_file = unit_dir / unit_name
    exec_start = f"/bin/bash -lc {shlex.quote(str(launcher_path))}"

    unit_lines = [
        "[Unit]",
        "Description=Hydrus Web GUI",
        "After=network-online.target",
        "Wants=network-online.target",
        "",
        "[Service]",
        "Type=simple",
        f"WorkingDirectory={app_root}",
        f"ExecStart={exec_start}",
        "Restart=on-failure",
        "Environment=NODE_ENV=production",
    ]
    if service_user and hasattr(os, "geteuid") and os.geteuid() == 0:
        unit_lines.append(f"User={service_user}")
        unit_lines.append(f"Group={service_user}")
    unit_lines.extend([
        "",
        "[Install]",
        f"WantedBy={wanted_by}",
        "",
    ])
    unit_file.write_text("\n".join(unit_lines), encoding="utf-8")

    run([*systemctl_cmd, "daemon-reload"], debug=debug)
    run([*systemctl_cmd, "enable", "--now", unit_name], debug=debug)


def install_service(service_name: str, app_root: Path, launcher_path: Path, debug: bool = False) -> None:
    system = platform.system().lower()
    if system == "windows":
        install_service_windows(service_name, launcher_path, debug=debug)
        return
    if system == "linux":
        install_service_linux(service_name, app_root, launcher_path, debug=debug)
        return
    raise RuntimeError("Service installation for the Hydrus web GUI is only supported on Windows and Linux.")


def setup_mpv_handler(app_root: Path, npm_exe: str, debug: bool = False) -> None:
    system = platform.system().lower()
    if system not in {"windows", "linux"}:
        print("Skipping mpv-handler setup: this helper is only automated on Windows and Linux.")
        return
    run([npm_exe, "run", "setup:mpv-handler"], cwd=app_root, debug=debug)


def parse_args(argv: list[str] | None = None) -> argparse.Namespace:
    parser = argparse.ArgumentParser(description="Install the Hydrus web GUI companion app")
    parser.add_argument("--root", default=".", help="Root directory that will contain the checkout")
    parser.add_argument("--dest-name", default=DEFAULT_DEST_NAME, help="Folder name for the checkout")
    parser.add_argument("--repo", default=DEFAULT_REPO_URL, help="Repository URL for the Hydrus web GUI")
    parser.add_argument("--force", action="store_true", help="Replace an existing non-empty destination")
    parser.add_argument(
        "--install-service",
        action="store_true",
        help="Register the web GUI to start automatically using Task Scheduler or systemd",
    )
    parser.add_argument(
        "--service-name",
        default=DEFAULT_SERVICE_NAME,
        help=f"Service name to register (default: {DEFAULT_SERVICE_NAME})",
    )
    parser.add_argument(
        "--setup-mpv-handler",
        action="store_true",
        help="Run the web GUI's mpv-handler setup helper after installation",
    )
    parser.add_argument(
        "--port",
        type=int,
        default=DEFAULT_PORT,
        help=f"Preferred preview port for the generated service launcher (default: {DEFAULT_PORT})",
    )
    parser.add_argument("--debug", action="store_true", help="Show installer debug output")
    return parser.parse_args(argv)


def main(argv: list[str] | None = None) -> int:
    args = parse_args(argv)
    root = Path(os.path.expandvars(os.path.expanduser(args.root))).resolve()
    dest = root / os.path.expandvars(args.dest_name)

    try:
        _, npm_exe = ensure_node_runtime(debug=args.debug)
        clone_or_update_repo(args.repo, dest, force=args.force, debug=args.debug)
        install_web_gui_dependencies(dest, npm_exe, debug=args.debug)
        launcher_path = write_service_launcher(dest, npm_exe, args.port)

        if args.setup_mpv_handler:
            setup_mpv_handler(dest, npm_exe, debug=args.debug)

        if args.install_service:
            install_service(args.service_name, dest, launcher_path, debug=args.debug)

        print(f"Hydrus web GUI ready at: {dest}")
        print(f"Launcher: {launcher_path}")
        print(f"URL: http://localhost:{args.port}")
        return 0
    except subprocess.CalledProcessError as exc:
        print(f"Hydrus web GUI install failed: {exc}", file=sys.stderr)
        return int(exc.returncode or 1)
    except Exception as exc:
        print(f"Hydrus web GUI install error: {exc}", file=sys.stderr)
        return 1


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