"""
Mini Job-System: Hintergrund-Prozesse mit Live-Log.

Bewusst KISS: ein In-Memory-Dict, ein Daemon-Thread je Job, Subprocess mit
zeilenweisem Log-Capture. Keine Persistenz, kein Broker. Genutzt von allen
Routern, die laenger laufende Shell-Befehle anstossen (Download, Update, ...).
"""

import os
import shlex
import subprocess
import threading
import time
import uuid

JOBS: dict[str, dict] = {}
_LOG_CAP = 400


def _run_job(job_id: str, args: list[str], env: dict | None = None, stdin_data: str | None = None):
    job = JOBS[job_id]
    job["state"] = "running"
    try:
        proc = subprocess.Popen(
            args,
            stdin=subprocess.PIPE if stdin_data is not None else None,
            stdout=subprocess.PIPE,
            stderr=subprocess.STDOUT,
            text=True,
            bufsize=1,
            env={**os.environ, **(env or {})},
        )
        if stdin_data is not None:
            # Secret (z.B. sudo-Passwort) ueber stdin fuettern — landet NICHT im Log/ps.
            try:
                proc.stdin.write(stdin_data)  # type: ignore[union-attr]
                proc.stdin.close()  # type: ignore[union-attr]
            except Exception:  # noqa: BLE001
                pass
        for line in proc.stdout:  # type: ignore[union-attr]
            job["log"].append(line.rstrip("\n"))
            if len(job["log"]) > _LOG_CAP:
                del job["log"][0]
        proc.wait()
        job["returncode"] = proc.returncode
        job["state"] = "done" if proc.returncode == 0 else "failed"
    except Exception as exc:  # noqa: BLE001
        job["log"].append(f"[mission-control] Fehler: {exc}")
        job["state"] = "failed"
        job["returncode"] = -1
    job["finished_at"] = time.time()


def start_job(args: list[str], label: str, env: dict | None = None,
              stdin_data: str | None = None, log_cmd: str | None = None) -> str:
    job_id = uuid.uuid4().hex[:12]
    # log_cmd erlaubt eine sanitisierte Befehlszeile (kein Secret im Log), wenn stdin_data ein
    # Geheimnis (sudo-Passwort) traegt. Sonst die echten Argumente.
    first_line = log_cmd if log_cmd is not None else "$ " + " ".join(shlex.quote(a) for a in args)
    JOBS[job_id] = {
        "id": job_id,
        "label": label,
        "state": "queued",
        "log": [first_line],
        "returncode": None,
        "started_at": time.time(),
        "finished_at": None,
    }
    threading.Thread(target=_run_job, args=(job_id, args, env, stdin_data), daemon=True).start()
    return job_id
