"""Update-Pillar View — listet pending updates aus allen Quellen."""
from __future__ import annotations

from collections import Counter

from textual import work
from textual.app import ComposeResult
from textual.binding import Binding
from textual.containers import Vertical
from textual.widgets import DataTable, Static

from taninux.core import sudo, theme as T
from taninux.core.confirm import ConfirmModal
from taninux.core.runner import Command, run_interactive
from taninux.core.streaming import RunnerScreen, StreamCommand
from taninux.core.sudo_modal import SudoModal
from taninux.update.sources import fetch_all
from taninux.update.types import UpdateItem


SOURCE_COLOR = {
    "pacman":  T.UME,        # violet — system core
    "aur":     T.CHIKYU,     # yellow — community
    "flatpak": T.TAKE,       # green — sandboxed
    "fwupd":   T.SHINKAI,    # blue — firmware
}
SOURCE_GLYPH = {
    "pacman":  "▲",
    "aur":     "◆",
    "flatpak": "●",
    "fwupd":   "◈",
}


def _human_size(n: int) -> str:
    f = float(n)
    for unit in ("B", "K", "M", "G", "T"):
        if f < 1024 or unit == "T":
            return f"{f:.0f}{unit}" if unit == "B" else f"{f:.1f}{unit}"
        f /= 1024
    return f"{f:.1f}T"


class UpdateView(Vertical):
    """Cross-source updater: pacman + AUR + flatpak + fwupd in einer View."""

    DEFAULT_CSS = f"""
    UpdateView {{
        background: {T.YORU};
        color: {T.YUKI};
        layout: vertical;
    }}
    UpdateView #u-stats {{
        height: 1;
        padding: 1 2 0 2;
        background: {T.YORU};
        color: {T.BASE5};
    }}
    UpdateView #u-counts {{
        height: 1;
        padding: 1 2 1 2;
        background: {T.YORU};
        color: {T.BASE4};
    }}
    UpdateView #u-table {{
        height: 1fr;
        background: {T.YORU};
        color: {T.FG_ALT};
        border: none;
        overflow-x: hidden;
        scrollbar-size-vertical: 1;
        scrollbar-color: {T.BASE2} {T.YORU};
        scrollbar-background: {T.YORU};
        scrollbar-corner-color: {T.YORU};
    }}
    UpdateView #u-table:focus {{
        border: none;
    }}
    UpdateView DataTable > .datatable--header {{
        background: {T.YORU};
        color: {T.SHINKAI};
    }}
    UpdateView DataTable > .datatable--cursor {{
        background: {T.BASE1};
        color: {T.YUKI};
    }}
    UpdateView DataTable > .datatable--hover {{
        background: {T.YORU};
    }}
    UpdateView DataTable > .datatable--even-row {{
        background: {T.YORU};
    }}
    UpdateView DataTable > .datatable--odd-row {{
        background: {T.YORU};
    }}
    UpdateView #u-advice {{
        height: auto;
        padding: 1 2;
        background: {T.YORU};
        color: {T.BASE4};
    }}
    UpdateView #u-flash {{
        height: 1;
        padding: 0 2;
        background: {T.YORU};
        color: {T.BASE4};
    }}
    """

    BINDINGS = [
        Binding("e", "execute", "ausführen (TUI)"),
        Binding("E", "execute_external", "ausführen (extern)", show=False),
        Binding("r", "rescan", "rescan"),
        Binding("j", "cursor_down", "down", show=False),
        Binding("k", "cursor_up", "up", show=False),
        Binding("g", "cursor_top", "top", show=False),
        Binding("G", "cursor_bottom", "bottom", show=False),
    ]

    def __init__(self, **kwargs) -> None:
        super().__init__(**kwargs)
        self.items: list[UpdateItem] = []
        self._loaded = False

    def compose(self) -> ComposeResult:
        yield Static("", id="u-stats", markup=True)
        yield Static("", id="u-counts", markup=True)
        yield DataTable(id="u-table", cursor_type="row", zebra_stripes=False)
        yield Static("", id="u-advice", markup=True)
        yield Static("", id="u-flash", markup=True)

    def on_mount(self) -> None:
        table = self.query_one("#u-table", DataTable)
        table.add_column("", width=2, key="glyph")
        table.add_column("quelle", width=9, key="source")
        table.add_column("paket", key="name")
        table.add_column("aktuell", key="current")
        table.add_column("→", width=1, key="arrow")
        table.add_column("neu", key="new")
        table.add_column("grösse", key="size")
        self.action_rescan()

    # ---- actions ----

    def action_rescan(self) -> None:
        self._flash("scanne updates… (kann 30+ sek dauern)")
        self._rescan_worker()

    @work(thread=True, exclusive=True, group="update-scan")
    def _rescan_worker(self) -> None:
        items = fetch_all()
        self.app.call_from_thread(self._after_rescan, items)

    def _after_rescan(self, items: list[UpdateItem]) -> None:
        self.items = items
        self._loaded = True
        self._refresh_view()
        self._flash(f"{len(items)} updates verfügbar")

    def action_execute(self) -> None:
        """Update INSIDE the TUI: sudo-prompt → streaming modal mit --noconfirm."""
        if not self.items:
            self._flash("nichts zu tun — system aktuell", color=T.TAKE)
            return
        cmds = self._build_stream_commands()
        if not cmds:
            return

        body = (
            "die folgenden befehle werden im TUI ausgeführt (live output):\n\n"
            + "\n".join(f"  · {c.label}\n      $ {' '.join(c.argv)}" for c in cmds)
            + "\n\nhinweis: --noconfirm ist aktiv, alle prompts werden auto-bestätigt.\n"
            + "für interaktiven modus mit prompts: Shift+E (suspendiert TUI)."
        )
        self.app.push_screen(
            ConfirmModal("updates im TUI ausführen?", body, danger=False),
            lambda ok: self._after_execute_confirm(ok, cmds),
        )

    def action_execute_external(self) -> None:
        """Klassischer suspend-modus für interaktive updates mit prompts."""
        if not self.items:
            self._flash("nichts zu tun — system aktuell", color=T.TAKE)
            return
        cmds = self._build_commands()
        if not cmds:
            return
        body = (
            "TUI wird suspendiert, befehle laufen interaktiv im terminal:\n\n"
            + "\n".join(f"  · {c.label}\n      $ {' '.join(c.argv)}" for c in cmds)
        )
        self.app.push_screen(
            ConfirmModal("extern interaktiv ausführen?", body, danger=False),
            lambda ok: self._after_external_confirm(ok, cmds),
        )

    def _build_commands(self) -> list[Command]:
        """Interaktive Variante (mit prompts)."""
        counts = Counter(i.source for i in self.items)
        cmds: list[Command] = []
        if counts.get("pacman") or counts.get("aur"):
            from taninux.core import sysinfo
            helper = "paru" if sysinfo.has_binary("paru") else (
                "yay" if sysinfo.has_binary("yay") else None
            )
            if helper:
                cmds.append(Command(
                    label=f"system + AUR ({counts.get('pacman', 0)} pacman, {counts.get('aur', 0)} AUR)",
                    argv=[helper, "-Syu"],
                ))
            elif counts.get("pacman"):
                cmds.append(Command(
                    label=f"system ({counts.get('pacman', 0)} pakete)",
                    argv=["sudo", "pacman", "-Syu"],
                ))
        if counts.get("flatpak"):
            cmds.append(Command(
                label=f"flatpak ({counts.get('flatpak')} apps)",
                argv=["flatpak", "update", "-y"],
            ))
        if counts.get("fwupd"):
            cmds.append(Command(
                label=f"firmware ({counts.get('fwupd')} geräte)",
                argv=["sudo", "fwupdmgr", "update"],
            ))
        return cmds

    def _build_stream_commands(self) -> list[StreamCommand]:
        """Streaming-Variante mit aggressiven unattended-flags.

        paru flags:
          --noconfirm     keine [Y/n] prompts
          --sudoloop      hält sudo-cache am leben während build
          --skipreview    keine PKGBUILD-review prompts
          --removemake    cleanup nach build
          --cleanafter    src-dir cleanup
          --batchinstall  alle AUR pakete in einem pacman call
        """
        counts = Counter(i.source for i in self.items)
        out: list[StreamCommand] = []
        if counts.get("pacman") or counts.get("aur"):
            from taninux.core import sysinfo
            helper = "paru" if sysinfo.has_binary("paru") else (
                "yay" if sysinfo.has_binary("yay") else None
            )
            if helper:
                out.append(StreamCommand(
                    label=f"system + AUR via {helper}",
                    argv=[
                        helper, "-Syu",
                        "--noconfirm", "--sudoloop",
                        "--skipreview", "--removemake", "--cleanafter",
                        "--batchinstall",
                    ],
                ))
            elif counts.get("pacman"):
                out.append(StreamCommand(
                    label="system via pacman",
                    argv=["sudo", "-n", "pacman", "-Syu", "--noconfirm"],
                ))
        if counts.get("flatpak"):
            out.append(StreamCommand(
                label=f"flatpak ({counts.get('flatpak')} apps)",
                argv=["flatpak", "update", "--noninteractive"],
            ))
        if counts.get("fwupd"):
            out.append(StreamCommand(
                label=f"firmware ({counts.get('fwupd')} geräte)",
                argv=["sudo", "-n", "fwupdmgr", "update",
                      "--assume-yes", "--no-reboot-check"],
            ))
        return out

    def _after_execute_confirm(self, confirmed: bool | None,
                                cmds: list[StreamCommand]) -> None:
        if not confirmed:
            self._flash("abgebrochen")
            return
        # Brauchen wir sudo?
        needs_sudo = any(
            c.argv[0] == "sudo" or c.argv[0] in ("paru", "yay")
            for c in cmds
        )
        if needs_sudo and not sudo.is_cached():
            self.app.push_screen(
                SudoModal(),
                lambda pw: self._after_sudo_prompt(pw, cmds),
            )
        else:
            self._launch_runner(cmds)

    def _after_sudo_prompt(self, pw: str | None,
                            cmds: list[StreamCommand]) -> None:
        if not pw:
            self._flash("kein passwort — abgebrochen")
            return
        if not sudo.cache(pw):
            self._flash("falsches passwort", color=T.RED)
            return
        self._launch_runner(cmds)

    def _launch_runner(self, cmds: list[StreamCommand]) -> None:
        self.app.push_screen(RunnerScreen(cmds), self._after_runner)

    def _after_runner(self, codes: list[int] | None) -> None:
        from taninux.core import pacman as pac
        pac.invalidate()
        if not codes:
            self._flash("kein ergebnis")
        else:
            ok = sum(1 for c in codes if c == 0)
            self._flash(
                f"{ok}/{len(codes)} befehle ok — rescan…",
                color=T.TAKE if ok == len(codes) else T.CHIKYU,
            )
        self.action_rescan()

    def _after_external_confirm(self, confirmed: bool | None,
                                 cmds: list[Command]) -> None:
        if not confirmed:
            self._flash("abgebrochen")
            return
        codes = run_interactive(self.app, cmds)
        from taninux.core import pacman as pac
        pac.invalidate()
        ok = sum(1 for c in codes if c == 0)
        self._flash(
            f"{ok}/{len(codes)} befehle ok — rescan…",
            color=T.TAKE if ok == len(codes) else T.CHIKYU,
        )
        self.action_rescan()

    def action_cursor_down(self) -> None:
        self.query_one("#u-table", DataTable).action_cursor_down()

    def action_cursor_up(self) -> None:
        self.query_one("#u-table", DataTable).action_cursor_up()

    def action_cursor_top(self) -> None:
        t = self.query_one("#u-table", DataTable)
        if t.row_count:
            t.move_cursor(row=0)

    def action_cursor_bottom(self) -> None:
        t = self.query_one("#u-table", DataTable)
        if t.row_count:
            t.move_cursor(row=t.row_count - 1)

    # ---- helpers ----

    def _flash(self, msg: str, color: str | None = None) -> None:
        color = color or T.BASE4
        self.query_one("#u-flash", Static).update(f"[{color}]{msg}[/]")

    # ---- render ----

    def _refresh_view(self) -> None:
        table = self.query_one("#u-table", DataTable)
        table.clear()

        # sort: pacman, aur, flatpak, fwupd
        order = {"pacman": 0, "aur": 1, "flatpak": 2, "fwupd": 3}
        items = sorted(self.items, key=lambda i: (order.get(i.source, 9), i.name))

        for i in items:
            c = SOURCE_COLOR.get(i.source, T.BASE5)
            g = SOURCE_GLYPH.get(i.source, "·")
            size = _human_size(i.size_bytes) if i.size_bytes else "—"
            table.add_row(
                f"[{c}]{g}[/]",
                f"[{c}]{i.source}[/]",
                f"[{T.YUKI}]{i.name}[/]",
                f"[{T.BASE4}]{i.current_version}[/]",
                f"[{T.BASE3}]→[/]",
                f"[{T.TAKE}]{i.new_version}[/]",
                f"[{T.CHIKYU}]{size}[/]" if i.size_bytes else f"[{T.BASE3}]—[/]",
            )

        # stats
        total_size = sum(i.size_bytes for i in self.items)
        if not self.items:
            stats = f"[{T.TAKE}]system ist aktuell[/]   [{T.BASE3}]·[/]   [{T.BASE5}]keine pending updates[/]"
        else:
            stats = (
                f"[{T.YUKI}]{len(self.items)}[/] [{T.BASE5}]updates verfügbar[/]"
                + (f"   [{T.BASE3}]·[/]   [{T.CHIKYU}]{_human_size(total_size)}[/] [{T.BASE5}]download[/]"
                   if total_size else "")
            )
        self.query_one("#u-stats", Static).update(stats)

        # source counts legend
        by_source = Counter(i.source for i in self.items)
        parts = []
        for src in ("pacman", "aur", "flatpak", "fwupd"):
            n = by_source.get(src, 0)
            c = SOURCE_COLOR[src]
            g = SOURCE_GLYPH[src]
            count_color = T.BASE4 if n == 0 else T.BASE5
            parts.append(f"[{c}]{g}[/] [{count_color}]{src} {n}[/]")
        self.query_one("#u-counts", Static).update(f"   [{T.BASE3}]│[/]   ".join(parts))

        # advice — concrete commands
        if not self.items:
            self.query_one("#u-advice", Static).update("")
            return
        action_lines = [
            f"[{T.SHINKAI}]aktionen[/]",
            f"  [{T.UME}]e[/]   [{T.YUKI}]alle {len(self.items)} updates im TUI installieren[/] "
            f"[{T.BASE5}](streaming, --noconfirm)[/]",
            f"  [{T.BASE5}]E[/]  [{T.BASE4}]extern interaktiv (suspend → terminal)[/]",
            f"  [{T.BASE5}]r[/]  [{T.BASE4}]rescan[/]",
            "",
            f"[{T.SHINKAI}]oder shell[/]",
        ]
        sh_cmds: list[str] = []
        if by_source.get("pacman") or by_source.get("aur"):
            sh_cmds.append("paru -Syu")
        elif by_source.get("pacman"):
            sh_cmds.append("sudo pacman -Syu")
        if by_source.get("flatpak"):
            sh_cmds.append("flatpak update -y")
        if by_source.get("fwupd"):
            sh_cmds.append("sudo fwupdmgr update")
        for c in sh_cmds:
            action_lines.append(f"  [{T.CHIKYU}]{c}[/]")
        self.query_one("#u-advice", Static).update("\n".join(action_lines))
