"""Async Subprocess-Runner fürs GTK-Frontend.

Das GTK-Pendant zu `taninux.core.runner` / `streaming` (die an die Textual-App
hängen). Hier läuft alles non-blocking über `Gio.Subprocess`: stdout wird
zeilenweise im GLib-Mainloop gelesen, ohne den UI-Thread zu blockieren.

Privilegierte Befehle (pacman -S/-R/-Syu, bootctl set-default) laufen NICHT mehr
direkt als `pkexec pacman …` (= beliebiges root), sondern werden durch den
dedizierten `taninux-helper` geschleust: `pkexec <helper> <argv…>`. Der Helper
prüft das argv gegen eine feste Allowlist und lehnt alles andere ab.

Damit das ohne Terminal-Passwortprompt funktioniert, braucht es einen laufenden
polkit-Auth-Agent **und** idealerweise die mitgelieferte Policy (siehe
gui/polkit/). Ohne Policy fragt pkexec generisch nach dem Passwort eines Admins.
"""
from __future__ import annotations

import os

import gi

gi.require_version("Gtk", "4.0")
from gi.repository import Gio, GLib, GObject  # noqa: E402

# Pfad zum root-Helper. Bevorzugt der Systempfad (so zeigt auch die
# polkit-Policy via exec.path dorthin). Fallback: der Helper im Repo, damit
# das Frontend ohne Installation (Dev / uneditierte Quelle) funktioniert.
_SYSTEM_HELPER = "/usr/lib/taninux/taninux-helper"
_REPO_HELPER = os.path.join(os.path.dirname(__file__), "helper", "taninux-helper")


def _helper_path() -> str:
    return _SYSTEM_HELPER if os.path.exists(_SYSTEM_HELPER) else _REPO_HELPER


class ProcessRunner(GObject.Object):
    """Führt einen Befehl async aus und emittiert Zeilen + Exit-Code.

    Signals:
        line(str): eine Zeile stdout/stderr (ohne Newline)
        done(int): Exit-Code, wenn der Prozess endet (-1 = Start fehlgeschlagen)
    """

    __gsignals__ = {
        "line": (GObject.SignalFlags.RUN_FIRST, None, (str,)),
        "done": (GObject.SignalFlags.RUN_FIRST, None, (int,)),
    }

    def __init__(self) -> None:
        super().__init__()
        self._proc: Gio.Subprocess | None = None

    @property
    def running(self) -> bool:
        return self._proc is not None

    def run(self, argv: list[str], *, privileged: bool = False) -> None:
        if self.running:
            self.emit("line", "⚠ a process is already running")
            return

        # privileged: argv durch pkexec + Helper schleusen (Helper validiert
        # gegen Allowlist). Sonst direkt ausführen.
        if privileged:
            real_argv = ["pkexec", _helper_path(), *argv]
        else:
            real_argv = list(argv)
        self.emit("line", f"$ {' '.join(real_argv)}")

        flags = Gio.SubprocessFlags.STDOUT_PIPE | Gio.SubprocessFlags.STDERR_MERGE
        try:
            self._proc = Gio.Subprocess.new(real_argv, flags)
        except GLib.Error as err:
            self.emit("line", f"✘ failed to start process: {err.message}")
            self.emit("done", -1)
            return

        stdout = self._proc.get_stdout_pipe()
        reader = Gio.DataInputStream.new(stdout)
        self._read_next(reader)
        self._proc.wait_async(None, self._on_wait)

    # -- intern -----------------------------------------------------------

    def _read_next(self, reader: Gio.DataInputStream) -> None:
        reader.read_line_async(GLib.PRIORITY_DEFAULT, None, self._on_line, None)

    def _on_line(self, reader: Gio.DataInputStream, result, _user) -> None:
        try:
            raw, _length = reader.read_line_finish_utf8(result)
        except GLib.Error:
            return
        if raw is None:  # EOF
            return
        self.emit("line", raw)
        self._read_next(reader)

    def _on_wait(self, proc: Gio.Subprocess, result) -> None:
        try:
            proc.wait_finish(result)
            code = proc.get_exit_status()
        except GLib.Error:
            code = -1
        self._proc = None
        self.emit("done", code)
