"""Updates — Zähler (pacman/AUR/flatpak) + eine gemeinsame Paketliste.

Alles auf einmal oder einzeln aktualisieren. pacman läuft privilegiert über
den Helper, flatpak/AUR als User. Quellen werden async geladen.
"""
from __future__ import annotations

import threading

import gi

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

from taninux.core import panel
from taninux.gui.console import LogConsole, log_expander
from taninux.gui.process import ProcessRunner
from taninux.update.sources import aur, flatpak, pacman
from taninux.update.types import UpdateItem


def _one_argv(item: UpdateItem):
    if item.source == "pacman":
        return ["pacman", "-S", "--noconfirm", item.name], True
    if item.source == "flatpak":
        return ["flatpak", "update", "--assumeyes", item.name], False
    if item.source == "aur":
        return [item.extra.get("helper", "paru"), "-S", "--noconfirm", item.name], False
    return None


def _all_queue(items: list[UpdateItem]):
    """(argv, privileged)-Queue für „Update all"."""
    out = []
    if any(i.source == "pacman" for i in items):
        out.append((["pacman", "-Syu", "--noconfirm"], True))
    if any(i.source == "flatpak" for i in items):
        out.append((["flatpak", "update", "--assumeyes"], False))
    aur_items = [i for i in items if i.source == "aur"]
    if aur_items:
        helper = aur_items[0].extra.get("helper", "paru")
        out.append(([helper, "-Sua", "--noconfirm"], False))
    return out


def build() -> Gtk.Widget:
    page = Adw.PreferencesPage()

    console = LogConsole()
    runner = ProcessRunner()
    runner.connect("line", lambda _r, line: console.append_line(line))

    items: list[UpdateItem] = []
    pending: list = []

    g_sum = Adw.PreferencesGroup(title="Updates")
    refresh = Gtk.Button(icon_name="view-refresh-symbolic")
    refresh.add_css_class("flat")
    refresh.set_valign(Gtk.Align.CENTER)
    g_sum.set_header_suffix(refresh)

    def _count_row(name: str):
        row = Adw.ActionRow(title=name)
        lbl = Gtk.Label(label="…")
        lbl.add_css_class("numeric")
        lbl.add_css_class("dim-label")
        lbl.set_valign(Gtk.Align.CENTER)
        row.add_suffix(lbl)
        return row, lbl

    pac_row, pac_lbl = _count_row("Pacman")
    aur_row, aur_lbl = _count_row("AUR")
    fp_row, fp_lbl = _count_row("Flatpak")
    for r in (pac_row, aur_row, fp_row):
        g_sum.add(r)

    all_row = Adw.ActionRow(title="Update all")
    all_btn = Gtk.Button(label="Update")
    all_btn.add_css_class("suggested-action")
    all_btn.add_css_class("pill")
    all_btn.set_valign(Gtk.Align.CENTER)
    all_btn.set_sensitive(False)
    all_row.add_suffix(all_btn)
    g_sum.add(all_row)
    page.add(g_sum)

    g_list = Adw.PreferencesGroup(title="Packages")
    page.add(g_list)
    list_rows: list[Gtk.Widget] = []

    page.add(log_expander(console))

    def _start_next() -> None:
        if pending:
            argv, priv = pending.pop(0)
            runner.run(argv, privileged=priv)

    def _update_one(item: UpdateItem) -> None:
        a = _one_argv(item)
        if not a:
            return
        console.clear()
        runner.run(a[0], privileged=a[1])

    def _update_all(_b: Gtk.Button) -> None:
        pending.clear()
        pending.extend(_all_queue(items))
        if not pending:
            return
        console.clear()
        _start_next()

    all_btn.connect("clicked", _update_all)

    def _clear_list() -> None:
        for r in list_rows:
            g_list.remove(r)
        list_rows.clear()

    def _render() -> None:
        counts = {"pacman": 0, "aur": 0, "flatpak": 0}
        for i in items:
            if i.source in counts:
                counts[i.source] += 1
        pac_lbl.set_label(str(counts["pacman"]))
        aur_lbl.set_label(str(counts["aur"]) if panel.include_aur() else "off")
        fp_lbl.set_label(str(counts["flatpak"]))
        total = sum(counts.values())
        all_btn.set_sensitive(total > 0)
        all_row.set_title(f"Update all ({total})" if total else "Update all")

        _clear_list()
        if not items:
            row = Adw.ActionRow(title="Everything up to date")
            g_list.add(row)
            list_rows.append(row)
            return
        order = {"pacman": 0, "aur": 1, "flatpak": 2}
        for item in sorted(items, key=lambda i: (order.get(i.source, 9), i.name)):
            row = Adw.ActionRow(
                title=item.name,
                subtitle=f"{item.current_version}  →  {item.new_version}",
            )
            tag = Gtk.Label(label=item.source)
            tag.add_css_class("dim-label")
            tag.add_css_class("caption")
            tag.set_valign(Gtk.Align.CENTER)
            row.add_prefix(tag)
            btn = Gtk.Button(label="Update")
            btn.add_css_class("flat")
            btn.set_valign(Gtk.Align.CENTER)
            btn.connect("clicked", lambda _b, it=item: _update_one(it))
            row.add_suffix(btn)
            g_list.add(row)
            list_rows.append(row)

    def _load() -> None:
        pac_lbl.set_label("…")
        aur_lbl.set_label("…")
        fp_lbl.set_label("…")

        fetchers = [pacman.fetch, flatpak.fetch]
        if panel.include_aur():
            fetchers.insert(1, aur.fetch)

        def worker() -> None:
            found: list[UpdateItem] = []
            for fetch in fetchers:
                try:
                    found += fetch()
                except Exception:  # noqa: BLE001
                    pass

            def deliver() -> bool:
                items.clear()
                items.extend(found)
                _render()
                return False

            GLib.idle_add(deliver)

        threading.Thread(target=worker, daemon=True).start()

    def on_done(_r: ProcessRunner, code: int) -> None:
        console.append_line("✔ done" if code == 0 else f"✘ exit {code}")
        if pending:
            _start_next()
        else:
            _load()

    runner.connect("done", on_done)
    refresh.connect("clicked", lambda _b: _load())
    GLib.idle_add(lambda: (_load(), False)[1])
    return page
