"""Flatpak-Pillar: installierte Apps + Runtimes verwalten."""
from __future__ import annotations

import subprocess
from dataclasses import dataclass

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 theme as T
from taninux.core.confirm import ConfirmModal
from taninux.core.runner import Command, run_interactive
from taninux.maintain.scanners.pacman import _parse_size


@dataclass
class FlatpakItem:
    ref: str          # full ref like 'org.gnome.Calculator/x86_64/stable'
    app_id: str       # 'org.gnome.Calculator'
    branch: str
    arch: str
    version: str
    size_bytes: int
    origin: str       # remote name like 'flathub'
    kind: str         # 'app' or 'runtime'


def _flatpak_available() -> bool:
    try:
        subprocess.run(["flatpak", "--version"],
                       capture_output=True, check=False, timeout=5)
        return True
    except (FileNotFoundError, subprocess.TimeoutExpired):
        return False


def _list_kind(kind: str) -> list[FlatpakItem]:
    if not _flatpak_available():
        return []
    flag = "--app" if kind == "app" else "--runtime"
    try:
        r = subprocess.run(
            ["flatpak", "list", flag,
             "--columns=application,branch,arch,version,size,origin"],
            capture_output=True, text=True, check=False, timeout=10,
        )
    except (FileNotFoundError, subprocess.TimeoutExpired):
        return []
    items: list[FlatpakItem] = []
    for line in r.stdout.splitlines():
        s = line.strip()
        if not s or s.startswith("Application"):
            continue
        parts = s.split("\t")
        if len(parts) < 3:
            continue
        app_id = parts[0]
        branch = parts[1]
        arch = parts[2]
        version = parts[3] if len(parts) > 3 else ""
        size_str = parts[4] if len(parts) > 4 else ""
        origin = parts[5] if len(parts) > 5 else ""
        items.append(FlatpakItem(
            ref=f"{app_id}/{arch}/{branch}",
            app_id=app_id,
            branch=branch,
            arch=arch,
            version=version,
            size_bytes=_parse_size(size_str) if size_str else 0,
            origin=origin,
            kind=kind,
        ))
    return items


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 FlatpakView(Vertical):
    """Flatpak Apps + Runtimes verwalten (list, sizes, remove)."""

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

    BINDINGS = [
        Binding("r", "rescan", "rescan"),
        Binding("x", "remove", "entfernen"),
        Binding("u", "update_all", "alle updaten", show=False),
        Binding("ctrl+a", "remove_unused", "unused runtimes"),
        Binding("j", "cursor_down", "down", show=False),
        Binding("k", "cursor_up", "up", show=False),
    ]

    def __init__(self, **kwargs) -> None:
        super().__init__(**kwargs)
        self.current_tab = "app"
        self.items_by_tab: dict[str, list[FlatpakItem]] = {"app": [], "runtime": []}

    def compose(self) -> ComposeResult:
        yield Tabs(
            Tab("apps", id="app"),
            Tab("runtimes", id="runtime"),
            id="f-tabs",
        )
        yield Static("", id="f-stats", markup=True)
        yield DataTable(id="f-table", cursor_type="row", zebra_stripes=False)
        yield Static("", id="f-flash", markup=True)

    def on_mount(self) -> None:
        if not _flatpak_available():
            self._flash("flatpak ist nicht installiert", color=T.RED)
            return
        t = self.query_one("#f-table", DataTable)
        t.add_column("app-id", key="appid")
        t.add_column("branch", width=10, key="branch")
        t.add_column("version", width=18, key="version")
        t.add_column("grösse", width=10, key="size")
        t.add_column("origin", width=14, key="origin")

    # ---- actions ----

    def action_rescan(self) -> None:
        self._flash(f"scanne flatpak {self.current_tab}s…")
        self._rescan_worker(self.current_tab)

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

    def _after_rescan(self, kind: str, items: list[FlatpakItem]) -> None:
        self.items_by_tab[kind] = items
        if kind == self.current_tab:
            self._refresh_view()
            self._flash(f"{len(items)} {kind}s")

    def action_remove(self) -> None:
        cur = self._current()
        if cur is None:
            return
        body = (
            f"flatpak entfernen?\n\n"
            f"  {cur.app_id}\n"
            f"  branch: {cur.branch}\n"
            f"  grösse: {_human_size(cur.size_bytes)}"
        )
        self.app.push_screen(
            ConfirmModal(f"uninstall {cur.app_id}?", body, danger=True),
            lambda ok: self._after_remove(ok, cur),
        )

    def _after_remove(self, ok: bool | None, item: FlatpakItem) -> None:
        if not ok:
            self._flash("abgebrochen")
            return
        codes = run_interactive(
            self.app,
            [Command(
                label=f"uninstall {item.app_id}",
                argv=["flatpak", "uninstall", "-y", item.app_id],
            )],
        )
        success = codes and codes[0] == 0
        self._flash(
            f"{item.app_id} entfernt" if success else "uninstall fehlgeschlagen",
            color=T.TAKE if success else T.RED,
        )
        self.action_rescan()

    def action_remove_unused(self) -> None:
        body = "alle unused runtimes via `flatpak uninstall --unused -y` entfernen?"
        self.app.push_screen(
            ConfirmModal("unused runtimes weg?", body, danger=False),
            self._after_remove_unused,
        )

    def _after_remove_unused(self, ok: bool | None) -> None:
        if not ok:
            self._flash("abgebrochen")
            return
        codes = run_interactive(
            self.app,
            [Command(
                label="flatpak uninstall --unused",
                argv=["flatpak", "uninstall", "--unused", "-y"],
            )],
        )
        success = codes and codes[0] == 0
        self._flash(
            "unused runtimes weg" if success else "fehlgeschlagen",
            color=T.TAKE if success else T.RED,
        )
        self.action_rescan()

    def action_update_all(self) -> None:
        codes = run_interactive(
            self.app,
            [Command(label="flatpak update", argv=["flatpak", "update", "-y"])],
        )
        success = codes and codes[0] == 0
        self._flash("update fertig" if success else "update fehlgeschlagen",
                    color=T.TAKE if success else T.RED)
        self.action_rescan()

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

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

    # ---- helpers ----

    @property
    def items(self) -> list[FlatpakItem]:
        return self.items_by_tab.get(self.current_tab, [])

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

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

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

        total_size = sum(i.size_bytes for i in items)
        kind_label = "apps" if self.current_tab == "app" else "runtimes"
        stats = (
            f"[{T.YUKI}]{len(items)}[/] [{T.BASE5}]{kind_label}[/]"
            + (f"   [{T.BASE3}]·[/]   [{T.CHIKYU}]{_human_size(total_size)}[/] [{T.BASE5}]installiert[/]"
               if total_size else "")
        )
        self.query_one("#f-stats", Static).update(stats)

        for i in items:
            size_styled = (
                f"[{T.CHIKYU}]{_human_size(i.size_bytes)}[/]"
                if i.size_bytes else f"[{T.BASE3}]—[/]"
            )
            t.add_row(
                f"[{T.YUKI}]{i.app_id}[/]",
                f"[{T.BASE5}]{i.branch}[/]",
                f"[{T.BASE5}]{i.version}[/]",
                size_styled,
                f"[{T.BASE5}]{i.origin}[/]",
            )

    # ---- events ----

    def on_tabs_tab_activated(self, event: Tabs.TabActivated) -> None:
        if event.tab is None:
            return
        self.current_tab = event.tab.id
        if not self.items_by_tab.get(self.current_tab):
            self.action_rescan()
        else:
            self._refresh_view()
