"""Packages-Pillar — Übersicht aller installierten Pakete mit Filter-Tabs."""
from __future__ import annotations

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, Tab, Tabs

from taninux.core import pacman as pac, pkg, sudo, sysinfo
from taninux.core import theme as T
from taninux.core.confirm import ConfirmModal
from taninux.core.pkg import Package
from taninux.core.streaming import RunnerScreen, StreamCommand
from taninux.core.sudo_modal import SudoModal


TABS = [
    ("all", "alle"),
    ("explicit", "explicit"),
    ("deps", "deps"),
    ("orphans", "orphans"),
    ("aur", "AUR"),
]


REPO_COLOR = {
    "core":     T.UME,
    "extra":    T.SHINKAI,
    "multilib": T.CHIKYU,
    "aur":      T.CHIKYU,
    "local":    T.BASE4,
}


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 PackagesView(Vertical):
    """Pacman-Pakete: tabs für alle/explicit/deps/orphans/AUR."""

    DEFAULT_CSS = f"""
    PackagesView {{
        background: {T.YORU};
        color: {T.YUKI};
        layout: vertical;
    }}
    PackagesView Tabs {{
        background: {T.YORU};
        height: 3;
    }}
    PackagesView Tab {{
        background: {T.YORU};
        color: {T.BASE4};
        padding: 0 2;
    }}
    PackagesView Tab.-active {{
        color: {T.UME};
    }}
    PackagesView Underline {{
        background: {T.YORU};
        color: {T.BASE2};
    }}
    PackagesView Underline > .underline--bar {{
        background: {T.YORU};
        color: {T.UME};
    }}
    PackagesView #p-stats {{
        height: 1;
        padding: 0 2;
        background: {T.YORU};
        color: {T.BASE5};
    }}
    PackagesView #p-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};
    }}
    PackagesView #p-table:focus {{
        border: none;
    }}
    PackagesView DataTable > .datatable--header {{
        background: {T.YORU};
        color: {T.SHINKAI};
    }}
    PackagesView DataTable > .datatable--cursor {{
        background: {T.BASE1};
        color: {T.YUKI};
    }}
    PackagesView DataTable > .datatable--hover {{
        background: {T.YORU};
    }}
    PackagesView DataTable > .datatable--even-row {{
        background: {T.YORU};
    }}
    PackagesView DataTable > .datatable--odd-row {{
        background: {T.YORU};
    }}
    PackagesView #p-flash {{
        height: 1;
        padding: 0 2;
        background: {T.YORU};
        color: {T.BASE4};
    }}
    """

    BINDINGS = [
        Binding("r", "rescan", "rescan"),
        Binding("x", "remove", "entfernen"),
        Binding("X", "remove_orphans", "alle orphans"),
        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.all_packages: list[Package] = []
        self.current_tab = "all"
        self.filtered: list[Package] = []

    def compose(self) -> ComposeResult:
        yield Tabs(*[Tab(label, id=tab_id) for tab_id, label in TABS], id="p-tabs")
        yield Static("", id="p-stats", markup=True)
        yield DataTable(id="p-table", cursor_type="row", zebra_stripes=False)
        yield Static("", id="p-flash", markup=True)

    def on_mount(self) -> None:
        t = self.query_one("#p-table", DataTable)
        t.add_column("name", width=30, key="name")
        t.add_column("version", width=20, key="version")
        t.add_column("grösse", width=10, key="size")
        t.add_column("reason", width=10, key="reason")
        t.add_column("repo", width=10, key="repo")
        t.add_column("beschreibung", key="desc")
        self.action_rescan()

    # ---- actions ----

    def action_rescan(self) -> None:
        self._flash("scanne pakete…")
        self._scan_worker()

    @work(thread=True, exclusive=True, group="packages-scan")
    def _scan_worker(self) -> None:
        pkgs = pkg.all_installed()
        self.app.call_from_thread(self._after_scan, pkgs)

    def _after_scan(self, pkgs: list[Package]) -> None:
        self.all_packages = pkgs
        self._apply_filter()
        self._flash(f"{len(pkgs)} pakete installiert")

    def action_remove(self) -> None:
        cur = self._current()
        if cur is None:
            return
        body = (
            f"paket entfernen?\n\n"
            f"  {cur.name}  {cur.version}\n"
            f"  {_human_size(cur.size_bytes)}   reason: {cur.reason}   repo: {cur.repo}\n\n"
            f"command: sudo pacman -Rns {cur.name}"
        )
        self.app.push_screen(
            ConfirmModal(f"remove {cur.name}?", body, danger=True),
            lambda ok: self._after_remove(ok, [cur.name]),
        )

    def action_remove_orphans(self) -> None:
        orphans = [p for p in self.all_packages if p.is_orphan]
        if not orphans:
            self._flash("keine orphans", color=T.TAKE)
            return
        total = sum(p.size_bytes for p in orphans)
        body = (
            f"alle orphans entfernen?\n\n"
            f"  {len(orphans)} pakete   {_human_size(total)}\n\n"
            f"command: sudo pacman -Rns $(pacman -Qdtq)"
        )
        self.app.push_screen(
            ConfirmModal(
                f"remove {len(orphans)} orphans?",
                body, danger=True,
            ),
            lambda ok: self._after_remove(ok, [p.name for p in orphans]),
        )

    def _after_remove(self, ok: bool | None, names: list[str]) -> None:
        if not ok or not names:
            self._flash("abgebrochen")
            return
        cmds = [StreamCommand(
            label=f"remove {len(names)} paket{'e' if len(names) > 1 else ''}",
            argv=["sudo", "-n", "pacman", "-Rns", "--noconfirm", *names],
        )]
        if not sudo.is_cached():
            self.app.push_screen(
                SudoModal(),
                lambda pw: self._sudo_then_run(pw, cmds),
            )
        else:
            self.app.push_screen(RunnerScreen(cmds), self._after_runner)

    def _sudo_then_run(self, pw, cmds):
        if not pw:
            self._flash("kein passwort — abgebrochen")
            return
        if not sudo.cache(pw):
            self._flash("falsches passwort", color=T.RED)
            return
        self.app.push_screen(RunnerScreen(cmds), self._after_runner)

    def _after_runner(self, codes):
        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)} ok — rescan…",
                color=T.TAKE if ok == len(codes) else T.CHIKYU,
            )
        self.action_rescan()

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

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

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

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

    # ---- helpers ----

    def _current(self) -> Package | None:
        t = self.query_one("#p-table", DataTable)
        r = t.cursor_row
        if r is None or r < 0 or r >= len(self.filtered):
            return None
        return self.filtered[r]

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

    def _apply_filter(self) -> None:
        all_pkgs = self.all_packages
        tab = self.current_tab
        if tab == "explicit":
            f = [p for p in all_pkgs if p.reason == "explicit"]
        elif tab == "deps":
            f = [p for p in all_pkgs if p.reason == "dependency"]
        elif tab == "orphans":
            f = [p for p in all_pkgs if p.is_orphan]
        elif tab == "aur":
            f = [p for p in all_pkgs if p.repo == "aur"]
        else:
            f = list(all_pkgs)
        # Sort by size desc
        f.sort(key=lambda p: -p.size_bytes)
        self.filtered = f
        self._refresh_view()

    def _refresh_view(self) -> None:
        t = self.query_one("#p-table", DataTable)
        t.clear()
        for p in self.filtered:
            repo_color = REPO_COLOR.get(p.repo, T.BASE4)
            reason_color = T.UME if p.reason == "explicit" else T.BASE5
            size_styled = (
                f"[{T.CHIKYU}]{_human_size(p.size_bytes)}[/]"
                if p.size_bytes else f"[{T.BASE3}]—[/]"
            )
            t.add_row(
                f"[{T.YUKI}]{p.name}[/]",
                f"[{T.BASE5}]{p.version}[/]",
                size_styled,
                f"[{reason_color}]{p.reason}[/]",
                f"[{repo_color}]{p.repo}[/]",
                f"[{T.BASE5}]{p.desc[:80]}[/]",
            )

        total_size = sum(p.size_bytes for p in self.filtered)
        all_size = sum(p.size_bytes for p in self.all_packages)
        stats_parts = [
            f"[{T.YUKI}]{len(self.filtered)}[/] [{T.BASE5}]pakete[/]",
            f"[{T.CHIKYU}]{_human_size(total_size)}[/]",
        ]
        if self.current_tab != "all":
            stats_parts.append(
                f"[{T.BASE4}](von {len(self.all_packages)} · {_human_size(all_size)} total)[/]"
            )
        self.query_one("#p-stats", Static).update(
            "   " + f"   [{T.BASE3}]·[/]   ".join(stats_parts)
        )

    # ---- events ----

    def on_tabs_tab_activated(self, event: Tabs.TabActivated) -> None:
        if event.tab is None:
            return
        self.current_tab = event.tab.id
        self._apply_filter()
