"""Health-Pillar — Live-Dashboard mit System-Diagnose-Sektionen."""
from __future__ import annotations

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

from taninux.core import sysinfo
from taninux.core import theme as T


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"


def _human_duration(seconds: float) -> str:
    s = int(seconds)
    if s < 60:
        return f"{s}s"
    if s < 3600:
        return f"{s // 60}m {s % 60}s"
    h, rem = divmod(s, 3600)
    if h < 24:
        return f"{h}h {rem // 60}m"
    d, h = divmod(h, 24)
    return f"{d}d {h}h"


def _status_glyph(ok: bool, warn: bool = False) -> str:
    if warn:
        return f"[{T.CHIKYU}]◐[/]"
    return f"[{T.TAKE}]●[/]" if ok else f"[{T.RED}]●[/]"


class HealthView(VerticalScroll):
    """Live system health dashboard. Auto-refresh alle 10s."""

    DEFAULT_CSS = f"""
    HealthView {{
        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;
    }}
    HealthView Static {{
        background: {T.YORU};
        margin-bottom: 1;
    }}
    """

    BINDINGS = [
        Binding("r", "refresh", "refresh"),
    ]

    def compose(self) -> ComposeResult:
        yield Static("", id="h-system", markup=True)
        yield Static("", id="h-perf", markup=True)
        yield Static("", id="h-gpu", markup=True)
        yield Static("", id="h-storage", markup=True)
        yield Static("", id="h-net", markup=True)
        yield Static("", id="h-services", markup=True)
        yield Static("", id="h-logs", markup=True)

    def on_mount(self) -> None:
        self._refresh_worker()
        self.set_interval(10.0, self._refresh_worker)

    def action_refresh(self) -> None:
        self._refresh_worker()

    @work(thread=True, exclusive=True, group="health-refresh")
    def _refresh_worker(self) -> None:
        # subprocess-heavy calls passieren im worker, render dann auf UI thread
        self.app.call_from_thread(self._refresh_all)

    # ---- section builders ----

    def _section(self, title: str) -> str:
        return f"[{T.UME}]{title}[/]"

    def _refresh_all(self) -> None:
        self._render_system()
        self._render_perf()
        self._render_gpu()
        self._render_storage()
        self._render_net()
        self._render_services()
        self._render_logs()

    def _render_system(self) -> None:
        boot = sysinfo.boot_time_seconds()
        up = sysinfo.uptime_seconds()
        lines = [
            self._section("◈ system"),
            f"  [{T.SHINKAI}]host[/]      [{T.YUKI}]{sysinfo.hostname()}[/]",
            f"  [{T.SHINKAI}]distro[/]    [{T.YUKI}]{sysinfo.distro_pretty()}[/]",
            f"  [{T.SHINKAI}]kernel[/]    [{T.YUKI}]{sysinfo.kernel_release()}[/]",
            f"  [{T.SHINKAI}]uptime[/]    [{T.YUKI}]{_human_duration(up)}[/]",
        ]
        if boot is not None:
            color = T.TAKE if boot < 30 else (T.CHIKYU if boot < 60 else T.RED)
            lines.append(f"  [{T.SHINKAI}]boot[/]      [{color}]{boot:.1f}s[/]")
        self.query_one("#h-system", Static).update("\n".join(lines))

    def _render_perf(self) -> None:
        mem = sysinfo.memory()
        la = sysinfo.load_avg()
        ncpu = sysinfo.cpu_count()
        total = mem.get("MemTotal", 0)
        avail = mem.get("MemAvailable", 0)
        used = total - avail
        used_pct = (used * 100 / total) if total else 0
        swap_total = mem.get("SwapTotal", 0)
        swap_free = mem.get("SwapFree", 0)
        swap_used = swap_total - swap_free
        swap_pct = (swap_used * 100 / swap_total) if swap_total else 0

        load_color = T.TAKE if la[0] < ncpu * 0.7 else (T.CHIKYU if la[0] < ncpu else T.RED)
        mem_color = T.TAKE if used_pct < 60 else (T.CHIKYU if used_pct < 85 else T.RED)
        swap_color = T.TAKE if swap_pct < 20 else (T.CHIKYU if swap_pct < 60 else T.RED)

        lines = [
            self._section("⊙ perf"),
            f"  [{T.SHINKAI}]load[/]      "
            f"[{load_color}]{la[0]:.2f}[/] "
            f"[{T.BASE5}]{la[1]:.2f}[/] "
            f"[{T.BASE5}]{la[2]:.2f}[/]   "
            f"[{T.BASE4}]({ncpu} cpus)[/]",
            f"  [{T.SHINKAI}]memory[/]    "
            f"[{mem_color}]{_human_size(used)}[/] [{T.BASE5}]/[/] "
            f"[{T.YUKI}]{_human_size(total)}[/]   "
            f"[{T.BASE4}]({used_pct:.0f}%)[/]",
        ]
        if swap_total:
            lines.append(
                f"  [{T.SHINKAI}]swap[/]      "
                f"[{swap_color}]{_human_size(swap_used)}[/] [{T.BASE5}]/[/] "
                f"[{T.YUKI}]{_human_size(swap_total)}[/]   "
                f"[{T.BASE4}]({swap_pct:.0f}%)[/]"
            )
        else:
            lines.append(f"  [{T.SHINKAI}]swap[/]      [{T.BASE5}]nicht konfiguriert[/]")
        self.query_one("#h-perf", Static).update("\n".join(lines))

    def _render_gpu(self) -> None:
        gpus = sysinfo.gpus()
        lines = [self._section("▣ gpu")]
        if not gpus:
            lines.append(f"  [{T.BASE5}](keine gpu erkannt)[/]")
        else:
            for g in gpus:
                vendor_color = {
                    "nvidia": T.TAKE,
                    "amd": T.RED,
                    "intel": T.SHINKAI,
                }.get(g.vendor, T.BASE4)
                driver_part = (
                    f"[{T.SHINKAI}]driver[/] [{T.CHIKYU}]{g.driver}[/]"
                    if g.driver else f"[{T.BASE4}](kein driver geladen)[/]"
                )
                lines.append(
                    f"  [{vendor_color}]{g.vendor:7s}[/]   "
                    f"[{T.YUKI}]{g.model[:60]}[/]"
                )
                lines.append(f"            {driver_part}")
        lines.append(f"  [{T.SHINKAI}]audio[/]     [{T.YUKI}]{sysinfo.audio_server()}[/]")
        self.query_one("#h-gpu", Static).update("\n".join(lines))

    def _render_storage(self) -> None:
        disks = sysinfo.disks()
        lines = [self._section("◇ storage")]
        if not disks:
            lines.append(f"  [{T.BASE5}](keine disks)[/]")
        else:
            for d in disks:
                kind = "hdd" if d.rotational else "ssd"
                kind_color = T.CHIKYU if d.rotational else T.SHINKAI
                lines.append(
                    f"  [{kind_color}]{kind}[/]  "
                    f"[{T.YUKI}]/dev/{d.name}[/]   "
                    f"[{T.CHIKYU}]{_human_size(d.size_bytes)}[/]   "
                    f"[{T.BASE5}]{d.model}[/]"
                )
        self.query_one("#h-storage", Static).update("\n".join(lines))

    def _render_net(self) -> None:
        ifaces = sysinfo.network_interfaces()
        lines = [self._section("⇆ netzwerk")]
        if not ifaces:
            lines.append(f"  [{T.BASE5}](keine interfaces)[/]")
        else:
            for iface in ifaces:
                if iface.name == "lo":
                    continue
                state_color = {
                    "UP": T.TAKE,
                    "DOWN": T.BASE3,
                    "UNKNOWN": T.BASE4,
                }.get(iface.state, T.BASE4)
                addrs = " ".join(iface.addrs) if iface.addrs else "—"
                lines.append(
                    f"  [{state_color}]●[/] "
                    f"[{T.YUKI}]{iface.name:12s}[/] "
                    f"[{T.BASE5}]{iface.state:8s}[/] "
                    f"[{T.BASE4}]{addrs}[/]"
                )
        self.query_one("#h-net", Static).update("\n".join(lines))

    def _render_services(self) -> None:
        failed = sysinfo.systemctl_failed()
        lines = [self._section("◉ services")]
        if not failed:
            lines.append(
                f"  [{T.TAKE}]●[/]  [{T.YUKI}]0[/] [{T.BASE5}]failed units[/]"
            )
        else:
            lines.append(
                f"  [{T.RED}]●[/]  [{T.YUKI}]{len(failed)}[/] [{T.BASE5}]failed units[/]"
            )
            for unit in failed[:10]:
                lines.append(f"      [{T.RED}]{unit}[/]")
            if len(failed) > 10:
                lines.append(f"      [{T.BASE4}]… +{len(failed) - 10} mehr[/]")
        self.query_one("#h-services", Static).update("\n".join(lines))

    def _render_logs(self) -> None:
        today = sysinfo.journal_errors_today()
        boot = sysinfo.journal_errors_boot()
        def label(n: int, txt: str) -> str:
            if n < 0:
                return f"  [{T.BASE3}]?[/]  [{T.BASE5}]{txt} (nicht verfügbar)[/]"
            color = T.TAKE if n == 0 else (T.CHIKYU if n < 20 else T.RED)
            return f"  [{color}]●[/]  [{T.YUKI}]{n}[/]  [{T.BASE5}]{txt}[/]"
        lines = [
            self._section("✎ logs"),
            label(today, "errors heute (journalctl -p 3)"),
            label(boot, "errors seit boot (journalctl -p 3 -b)"),
        ]
        self.query_one("#h-logs", Static).update("\n".join(lines))
