"""Make the helper start on its own, on whichever OS this is.

Each platform has exactly one sane per-user mechanism that needs no
administrator rights, and this uses that one:

  Windows  Scheduled Task, at logon
  macOS    LaunchAgent, at login
  Linux    systemd --user service, at login (lingering not required)

No installer, no admin prompt, no PATH changes. The binary stays wherever the
user put it and is registered from there, because a non-technical person will
run it out of Downloads and never move it.
"""
import os
import platform
import shlex
import shutil
import subprocess
import sys
from pathlib import Path

APP = "ams-print-helper"
LABEL = "Astitvaams Print Helper"

# The Scheduled Task used to be registered under this name. Renaming the task
# does not remove the old one, so without this an upgraded machine would start
# TWO copies at logon — the second one failing to bind the port and dying, which
# looks exactly like a broken install. Cleared on every install and uninstall.
LEGACY_LABELS = ["AAMS Print Helper"]


def _launch_argv() -> list[str]:
    r"""The command that starts this program, as separate arguments.

    A LIST, not a joined string. It used to return "<python> <script>" as one
    string, and every caller then wrapped that in one pair of quotes:

        schtasks /TR ""C:\...\pythonw.exe C:\...\ams_print_helper.py" --serve"

    Windows read the whole thing as the name of a single file, schtasks exited
    1, and "Finish & start automatically" failed with nothing but "returned
    non-zero exit status 1". macOS had the same fault — both paths went into one
    <string> in ProgramArguments. Linux only worked because systemd splits
    ExecStart on whitespace, so the bug was invisible on the one platform that
    was easiest to test.
    """
    if getattr(sys, "frozen", False):
        return [os.path.abspath(sys.executable)]

    python = sys.executable
    if os.name == "nt":
        # Run the background copy under pythonw.exe so no console window opens
        # at every logon. sys.executable is python.exe when setup was started
        # from the installer's visible window.
        windowless = Path(python).with_name("pythonw.exe")
        if windowless.is_file():
            python = str(windowless)
    return [python, os.path.abspath(sys.argv[0])]


def install_autostart(config_dir: Path) -> dict:
    system = platform.system()
    argv = _launch_argv() + ["--serve"]
    try:
        if system == "Windows":
            return _windows(argv, config_dir)
        if system == "Darwin":
            return _macos(argv, config_dir)
        return _linux(argv, config_dir)
    except Exception as e:  # surfaced to the setup page, never a stack trace
        return {"ok": False, "error": str(e)}


def _windows(argv: list[str], config_dir: Path) -> dict:
    r"""Scheduled Task first, Startup folder if that is refused.

    schtasks needs no admin for a per-user task and /F makes a re-run an update,
    so it is the right default. But it is also refused outright on machines
    where an IT policy disables the Task Scheduler service or blocks task
    creation — and on those machines the helper would simply never start again
    after a reboot, with no warning. The Startup folder needs no service and no
    permission at all, so it is the fallback.
    """
    # list2cmdline quotes each path on its own, which is what schtasks wants:
    #   "C:\...\pythonw.exe" "C:\...\ams_print_helper.py" --serve
    # A username with a space in it ("C:\Users\John Smith\...") makes this
    # mandatory, not cosmetic.
    task_run = subprocess.list2cmdline(argv)
    for old_name in LEGACY_LABELS:
        subprocess.run(["schtasks", "/Delete", "/TN", old_name, "/F"],
                       capture_output=True,
                       creationflags=getattr(subprocess, "CREATE_NO_WINDOW", 0))
    cmd = ["schtasks", "/Create", "/TN", LABEL, "/SC", "ONLOGON",
           "/TR", task_run, "/F", "/RL", "LIMITED"]
    try:
        subprocess.run(cmd, check=True, capture_output=True,
                       creationflags=getattr(subprocess, "CREATE_NO_WINDOW", 0))
        return {"ok": True, "method": "Scheduled Task"}
    except subprocess.CalledProcessError as e:
        # schtasks writes the actual reason to its own output. Reporting only
        # "returned non-zero exit status 1" leaves nobody anything to act on.
        reason = (e.stderr or e.stdout or b"").decode("utf-8", "replace").strip()
        try:
            return _windows_startup_folder(argv, reason)
        except Exception as e2:
            return {"ok": False, "error": f"{reason or e}. Startup folder also failed: {e2}"}


def _windows_startup_folder(argv: list[str], reason: str) -> dict:
    """A .vbs in the Startup folder — runs at logon, no service, no admin.

    A .bat here would flash a console window at every logon; WScript.Shell.Run
    with the window style set to 0 starts it hidden.
    """
    startup = (Path(os.environ.get("APPDATA", Path.home() / "AppData" / "Roaming"))
               / "Microsoft" / "Windows" / "Start Menu" / "Programs" / "Startup")
    startup.mkdir(parents=True, exist_ok=True)
    command = subprocess.list2cmdline(argv).replace('"', '""')
    (startup / f"{APP}.vbs").write_text(
        'CreateObject("WScript.Shell").Run "' + command + '", 0, False\n',
        encoding="utf-8")
    return {"ok": True, "method": "Startup folder",
            "note": f"The Task Scheduler refused this ({reason or 'no reason given'}), "
                    f"so the helper starts from your Startup folder instead."}


def _macos(argv: list[str], config_dir: Path) -> dict:
    agents = Path.home() / "Library" / "LaunchAgents"
    agents.mkdir(parents=True, exist_ok=True)
    plist = agents / f"com.astitva.{APP}.plist"
    plist.write_text(f"""<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN"
  "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0"><dict>
  <key>Label</key><string>com.astitva.{APP}</string>
  <key>ProgramArguments</key><array>
    {"".join(f"<string>{a}</string>" for a in argv)}
  </array>
  <key>RunAtLoad</key><true/>
  <key>KeepAlive</key><true/>
  <key>StandardErrorPath</key><string>{config_dir / "helper.log"}</string>
</dict></plist>
""", encoding="utf-8")
    # bootout first so a re-run reloads rather than erroring "already loaded".
    subprocess.run(["launchctl", "bootout", f"gui/{os.getuid()}/com.astitva.{APP}"],
                   capture_output=True)
    subprocess.run(["launchctl", "bootstrap", f"gui/{os.getuid()}", str(plist)],
                   check=True, capture_output=True)
    return {"ok": True, "method": "LaunchAgent"}


def _linux(argv: list[str], config_dir: Path) -> dict:
    unit_dir = Path.home() / ".config" / "systemd" / "user"
    unit_dir.mkdir(parents=True, exist_ok=True)
    (unit_dir / f"{APP}.service").write_text(f"""[Unit]
Description={LABEL}
After=network-online.target

[Service]
ExecStart={" ".join(shlex.quote(a) for a in argv)}
Restart=always
RestartSec=5

[Install]
WantedBy=default.target
""", encoding="utf-8")
    if not shutil.which("systemctl"):
        return {"ok": False, "error": "systemd not available on this machine"}
    subprocess.run(["systemctl", "--user", "daemon-reload"], check=True, capture_output=True)
    subprocess.run(["systemctl", "--user", "enable", "--now", f"{APP}.service"],
                   check=True, capture_output=True)
    return {"ok": True, "method": "systemd user service"}


def remove_autostart() -> dict:
    system = platform.system()
    try:
        if system == "Windows":
            for name in [LABEL] + LEGACY_LABELS:
                subprocess.run(["schtasks", "/Delete", "/TN", name, "/F"], capture_output=True)
            startup = (Path(os.environ.get("APPDATA", Path.home() / "AppData" / "Roaming"))
                       / "Microsoft" / "Windows" / "Start Menu" / "Programs" / "Startup")
            (startup / f"{APP}.vbs").unlink(missing_ok=True)
        elif system == "Darwin":
            subprocess.run(["launchctl", "bootout", f"gui/{os.getuid()}/com.astitva.{APP}"],
                           capture_output=True)
            p = Path.home() / "Library" / "LaunchAgents" / f"com.astitva.{APP}.plist"
            p.unlink(missing_ok=True)
        else:
            subprocess.run(["systemctl", "--user", "disable", "--now", f"{APP}.service"],
                           capture_output=True)
            (Path.home() / ".config" / "systemd" / "user" / f"{APP}.service").unlink(missing_ok=True)
        return {"ok": True}
    except Exception as e:
        return {"ok": False, "error": str(e)}
