"""Kernel-Pillar — Liste verfügbarer Kernels, Status, Install/Switch, Boot-Default."""
from __future__ import annotations

import subprocess
from dataclasses import dataclass
from pathlib import Path

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

from taninux.core import bootloader, pacman as pac, sudo, sysinfo
from taninux.core import 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


# Standard Arch-Kernel-Varianten
KERNEL_VARIANTS = [
    ("linux", "stable, latest mainline"),
    ("linux-lts", "long-term support — stabilster"),
    ("linux-zen", "performance + interactivity tweaks (gut für gaming/desktop)"),
    ("linux-hardened", "security-fokus, härteren default-configs"),
    ("linux-rt", "real-time scheduler (audio, robotics)"),
    ("linux-rt-lts", "real-time + LTS"),
]


@dataclass
class KernelInfo:
    name: str            # 'linux-zen'
    description: str
    installed: bool
    headers_installed: bool
    version: str | None
    is_running: bool
    boot_entry: Path | None      # systemd-boot .conf entry, falls match
    is_default_boot: bool        # ist der konfigurierte default für nächsten boot


def _installed_with_versions() -> dict[str, str]:
    try:
        r = subprocess.run(
            ["pacman", "-Q"],
            capture_output=True, text=True, check=False, timeout=10,
        )
    except (FileNotFoundError, subprocess.TimeoutExpired):
        return {}
    out: dict[str, str] = {}
    for line in r.stdout.splitlines():
        parts = line.split()
        if len(parts) >= 2:
            out[parts[0]] = parts[1]
    return out


def _is_running(variant: str, running: str) -> bool:
    if variant == "linux":
        return not any(s in running for s in ("zen", "lts", "hardened", "rt"))
    suffix = variant.removeprefix("linux-")
    return suffix in running


def list_kernels() -> tuple[list[KernelInfo], bootloader.BootloaderInfo]:
    installed = _installed_with_versions()
    running = sysinfo.kernel_release()
    bl = bootloader.detect()
    out: list[KernelInfo] = []
    for name, desc in KERNEL_VARIANTS:
        ver = installed.get(name)
        headers_ver = installed.get(f"{name}-headers")
        entry = bootloader.entry_for_kernel(bl, name)
        out.append(KernelInfo(
            name=name,
            description=desc,
            installed=ver is not None,
            headers_installed=headers_ver is not None,
            version=ver,
            is_running=ver is not None and _is_running(name, running),
            boot_entry=entry,
            is_default_boot=entry is not None and bootloader.is_default(bl, entry),
        ))
    return out, bl


class KernelView(VerticalScroll):
    DEFAULT_CSS = f"""
    KernelView {{
        background: {T.YORU};
        padding: 1 2;
        scrollbar-color: {T.BASE2} {T.YORU};
        scrollbar-background: {T.YORU};
        scrollbar-corner-color: {T.YORU};
        scrollbar-size-vertical: 1;
    }}
    KernelView Static {{
        background: {T.YORU};
        margin-bottom: 1;
    }}
    KernelView #k-table {{
        height: auto;
        background: {T.YORU};
        color: {T.FG_ALT};
        border: none;
        overflow-x: hidden;
    }}
    KernelView #k-table:focus {{
        border: none;
    }}
    KernelView DataTable > .datatable--header {{
        background: {T.YORU};
        color: {T.SHINKAI};
    }}
    KernelView DataTable > .datatable--cursor {{
        background: {T.BASE1};
        color: {T.YUKI};
    }}
    KernelView DataTable > .datatable--even-row {{
        background: {T.YORU};
    }}
    KernelView DataTable > .datatable--odd-row {{
        background: {T.YORU};
    }}
    """

    BINDINGS = [
        Binding("r", "rescan", "rescan"),
        Binding("i", "install", "install +headers"),
        Binding("H", "install_headers", "headers only", show=False),
        Binding("b", "set_default", "boot-default"),
        Binding("x", "remove", "entfernen"),
        Binding("j", "cursor_down", "down", show=False),
        Binding("k", "cursor_up", "up", show=False),
    ]

    def __init__(self, **kwargs) -> None:
        super().__init__(**kwargs)
        self.kernels: list[KernelInfo] = []
        self.bl: bootloader.BootloaderInfo | None = None

    def compose(self) -> ComposeResult:
        yield Static("", id="k-intro", markup=True)
        yield DataTable(id="k-table", cursor_type="row", zebra_stripes=False)
        yield Static("", id="k-detail", markup=True)
        yield Static("", id="k-flash", markup=True)

    def on_mount(self) -> None:
        t = self.query_one("#k-table", DataTable)
        t.add_column("", width=2, key="glyph")
        t.add_column("kernel", width=18, key="name")
        t.add_column("status", width=12, key="status")
        t.add_column("version", width=18, key="version")
        t.add_column("hdr", width=4, key="headers")
        t.add_column("boot", width=10, key="boot")
        t.add_column("beschreibung", key="desc")
        self.action_rescan()

    def action_rescan(self) -> None:
        pac.invalidate()
        self.kernels, self.bl = list_kernels()
        self._refresh_view()

    # ---- install / install-headers / remove ----

    def action_install(self) -> None:
        cur = self._current()
        if cur is None:
            return
        if cur.installed and cur.headers_installed:
            self._flash("kernel + headers bereits installiert", color=T.BASE4)
            return
        pkgs = []
        if not cur.installed:
            pkgs.append(cur.name)
        if not cur.headers_installed:
            pkgs.append(f"{cur.name}-headers")
        body = (
            f"installieren?\n\n"
            f"  pakete: {', '.join(pkgs)}\n"
            f"  beschreibung: {cur.description}\n\n"
            f"nach dem install:\n"
            f"  · mkinitcpio läuft automatisch via pacman-hook\n"
            f"  · bootloader-config wird {self._bootloader_hint()}"
        )
        self.app.push_screen(
            ConfirmModal(f"install {' + '.join(pkgs)}?", body, danger=False),
            lambda ok: self._after_install(ok, pkgs, cur),
        )

    def action_install_headers(self) -> None:
        cur = self._current()
        if cur is None or not cur.installed:
            self._flash("kernel nicht installiert", color=T.BASE4)
            return
        if cur.headers_installed:
            self._flash("headers schon installiert", color=T.BASE4)
            return
        pkg = f"{cur.name}-headers"
        self.app.push_screen(
            ConfirmModal(
                f"install {pkg}?",
                f"nur headers für {cur.name} installieren?",
                danger=False,
            ),
            lambda ok: self._after_install(ok, [pkg], cur),
        )

    def action_remove(self) -> None:
        cur = self._current()
        if cur is None or not cur.installed:
            self._flash("nicht installiert", color=T.BASE4)
            return
        if cur.is_running:
            self._flash(
                "kannst den laufenden kernel nicht entfernen — wechsle erst per reboot",
                color=T.RED,
            )
            return
        if cur.is_default_boot:
            self._flash(
                "kernel ist als boot-default gesetzt — wechsle erst (`b`)",
                color=T.RED,
            )
            return
        pkgs = [cur.name]
        if cur.headers_installed:
            pkgs.append(f"{cur.name}-headers")
        body = (
            f"entfernen?\n\n"
            f"  pakete: {', '.join(pkgs)}\n\n"
            f"  · sicherstellen dass ein anderer kernel installiert + bootbar ist\n"
            f"  · bootloader-config wird vom pacman-hook regeneriert"
        )
        self.app.push_screen(
            ConfirmModal(f"remove {cur.name}?", body, danger=True),
            lambda ok: self._after_remove(ok, pkgs, cur),
        )

    # ---- boot default ----

    def action_set_default(self) -> None:
        cur = self._current()
        if cur is None:
            return
        if self.bl is None or self.bl.kind == "unknown":
            self._flash("bootloader unbekannt — siehe detail-pane", color=T.CHIKYU)
            return
        if not cur.installed:
            self._flash("kernel nicht installiert — kann nicht booten", color=T.BASE4)
            return
        if cur.boot_entry is None:
            if self.bl.kind == "systemd-boot":
                self._flash(
                    f"kein boot-entry für {cur.name} gefunden in /boot/loader/entries/",
                    color=T.CHIKYU,
                )
            else:
                self._flash(
                    f"GRUB: manuell `sudo grub-set-default …` — siehe detail-pane",
                    color=T.CHIKYU,
                )
            return
        if cur.is_default_boot:
            self._flash(f"{cur.name} ist schon boot-default", color=T.BASE4)
            return

        entry_name = cur.boot_entry.name
        body = (
            f"boot-default ändern?\n\n"
            f"  bootloader: {self.bl.kind}\n"
            f"  entry:      {entry_name}\n"
            f"  kernel:     {cur.name} ({cur.version})\n\n"
            f"  wird beim nächsten reboot gebootet."
        )
        self.app.push_screen(
            ConfirmModal(f"set boot-default → {cur.name}?", body, danger=False),
            lambda ok: self._after_set_default(ok, cur, entry_name),
        )

    def _after_set_default(self, ok: bool | None, k: KernelInfo, entry_name: str) -> None:
        if not ok:
            self._flash("abgebrochen")
            return
        cmds = [StreamCommand(
            label=f"set-default → {entry_name}",
            argv=["sudo", "-n", "bootctl", "set-default", entry_name],
        )]
        self._run_with_sudo(
            cmds,
            on_done=lambda codes: self._after_set_default_done(codes, k, entry_name),
        )

    def _after_set_default_done(self, codes, k: KernelInfo, entry_name: str) -> None:
        success = codes and codes[0] == 0
        self._flash(
            f"boot-default → {k.name} ({entry_name})" if success
            else "set-default fehlgeschlagen",
            color=T.TAKE if success else T.RED,
        )
        self.action_rescan()

    # ---- install/remove follow-up ----

    def _after_install(self, ok: bool | None, pkgs: list[str], k: KernelInfo) -> None:
        if not ok:
            self._flash("abgebrochen")
            return
        helper = "paru" if sysinfo.has_binary("paru") else (
            "yay" if sysinfo.has_binary("yay") else None
        )
        if helper:
            argv = [
                helper, "-S", "--needed",
                "--noconfirm", "--sudoloop",
                "--skipreview", "--removemake", "--cleanafter",
                *pkgs,
            ]
        else:
            argv = ["sudo", "-n", "pacman", "-S", "--needed", "--noconfirm", *pkgs]
        cmds = [StreamCommand(label=f"install {' + '.join(pkgs)}", argv=argv)]
        self._run_with_sudo(
            cmds,
            on_done=lambda codes: self._after_install_done(codes, pkgs, k),
        )

    def _after_install_done(self, codes, pkgs, k: KernelInfo) -> None:
        success = codes and codes[0] == 0
        if success:
            msg = f"{', '.join(pkgs)} installiert"
            if self.bl and self.bl.kind == "systemd-boot":
                msg += "  ·  systemd-boot regeneriert via hook"
            elif self.bl and self.bl.kind == "grub":
                msg += "  ·  prüfe: sudo grub-mkconfig -o /boot/grub/grub.cfg"
            self._flash(msg, color=T.TAKE)
        else:
            self._flash("install fehlgeschlagen — siehe runner-log", color=T.RED)
        pac.invalidate()
        self.action_rescan()

    def _after_remove(self, ok: bool | None, pkgs: list[str], k: KernelInfo) -> None:
        if not ok:
            self._flash("abgebrochen")
            return
        cmds = [StreamCommand(
            label=f"remove {' + '.join(pkgs)}",
            argv=["sudo", "-n", "pacman", "-Rns", "--noconfirm", *pkgs],
        )]
        self._run_with_sudo(
            cmds,
            on_done=lambda codes: self._after_remove_done(codes, k),
        )

    def _after_remove_done(self, codes, k: KernelInfo) -> None:
        success = codes and codes[0] == 0
        pac.invalidate()
        self._flash(
            f"{k.name} entfernt" if success else f"remove fehlgeschlagen",
            color=T.TAKE if success else T.RED,
        )
        self.action_rescan()

    # ---- shared sudo + runner helper ----

    def _run_with_sudo(self, cmds: list[StreamCommand], on_done) -> None:
        """Stellt sicher dass sudo gecached ist, dann launched RunnerScreen."""
        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._sudo_then_run(pw, cmds, on_done),
            )
        else:
            self.app.push_screen(RunnerScreen(cmds), on_done)

    def _sudo_then_run(self, pw, cmds, on_done) -> None:
        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), on_done)

    # ---- nav ----

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

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

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

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

    def _bootloader_hint(self) -> str:
        if self.bl is None or self.bl.kind == "unknown":
            return "manuell regeneriert"
        if self.bl.kind == "systemd-boot":
            return "automatisch via pacman-hook regeneriert"
        return "evt. via `sudo grub-mkconfig -o /boot/grub/grub.cfg` regeneriert"

    # ---- render ----

    def _refresh_view(self) -> None:
        running = sysinfo.kernel_release()
        bl_kind = self.bl.kind if self.bl else "unknown"
        bl_default = self.bl.default_entry if self.bl and self.bl.default_entry else "—"

        intro = (
            f"[{T.UME}]⊙ kernel[/]\n"
            f"[{T.SHINKAI}]aktuell laufend[/]     [{T.YUKI}]{running}[/]\n"
            f"[{T.SHINKAI}]bootloader[/]          [{T.YUKI}]{bl_kind}[/]   "
            f"[{T.BASE5}]default:[/] [{T.YUKI}]{bl_default}[/]\n"
            f"[{T.BASE5}]`i` install +headers · `H` headers only · `b` boot-default · `x` entfernen[/]"
        )
        self.query_one("#k-intro", Static).update(intro)

        t = self.query_one("#k-table", DataTable)
        t.clear()
        for k in self.kernels:
            if k.is_running:
                glyph = f"[{T.UME}]▲[/]"
                status_str = f"[{T.UME}]laufend[/]"
            elif k.installed:
                glyph = f"[{T.TAKE}]●[/]"
                status_str = f"[{T.TAKE}]installiert[/]"
            else:
                glyph = f"[{T.BASE3}]○[/]"
                status_str = f"[{T.BASE4}]nicht inst.[/]"
            ver_styled = f"[{T.CHIKYU}]{k.version}[/]" if k.version else f"[{T.BASE3}]—[/]"

            if not k.installed:
                hdr = f"[{T.BASE3}]—[/]"
            elif k.headers_installed:
                hdr = f"[{T.TAKE}]✓[/]"
            else:
                hdr = f"[{T.CHIKYU}]✗[/]"

            if k.is_default_boot:
                boot = f"[{T.UME}]default[/]"
            elif k.boot_entry is not None:
                boot = f"[{T.BASE5}]bootbar[/]"
            elif k.installed and self.bl and self.bl.kind != "unknown":
                boot = f"[{T.CHIKYU}]?[/]"
            else:
                boot = f"[{T.BASE3}]—[/]"

            t.add_row(
                glyph,
                f"[{T.YUKI}]{k.name}[/]",
                status_str,
                ver_styled,
                hdr,
                boot,
                f"[{T.BASE5}]{k.description}[/]",
            )

        installed_count = sum(1 for k in self.kernels if k.installed)
        with_headers = sum(1 for k in self.kernels if k.installed and k.headers_installed)
        without_headers = installed_count - with_headers

        detail_lines = [
            f"[{T.SHINKAI}]übersicht[/]",
            f"  installiert:        [{T.YUKI}]{installed_count}[/]",
            f"  headers ok:         [{T.TAKE}]{with_headers}[/]"
            + (f"  ·  fehlt:  [{T.CHIKYU}]{without_headers}[/]" if without_headers else ""),
        ]
        if self.bl and self.bl.kind == "systemd-boot":
            detail_lines += [
                "",
                f"[{T.SHINKAI}]systemd-boot[/]",
                f"  config:   [{T.BASE5}]/boot/loader/loader.conf[/]",
                f"  default:  [{T.YUKI}]{bl_default}[/]",
                f"  switch:   [{T.CHIKYU}]sudo bootctl set-default <entry>[/]",
                f"            [{T.BASE5}](oder hier mit `b` auf die zeile)[/]",
            ]
        elif self.bl and self.bl.kind == "grub":
            detail_lines += [
                "",
                f"[{T.SHINKAI}]GRUB[/]",
                f"  config:   [{T.BASE5}]/etc/default/grub + /boot/grub/grub.cfg[/]",
                f"  default:  [{T.YUKI}]{bl_default}[/]",
                f"  switch:   [{T.CHIKYU}]edit /etc/default/grub (GRUB_DEFAULT=…)[/]",
                f"            [{T.CHIKYU}]sudo grub-mkconfig -o /boot/grub/grub.cfg[/]",
            ]
        else:
            detail_lines += [
                "",
                f"[{T.SHINKAI}]bootloader[/]",
                f"  [{T.BASE5}]nicht erkannt — kein /boot/loader/loader.conf oder /boot/grub/grub.cfg[/]",
            ]
        self.query_one("#k-detail", Static).update("\n".join(detail_lines))
