"""A minimalist system-stats line for the top of the panel.

CPU / RAM / GPU / disk utilisation plus network up/down rate, refreshed on a timer.
Everything is read straight from /proc and /sys on the main thread — these are local
file reads that take microseconds, so no subprocess or worker thread is needed.
"""

from __future__ import annotations

import glob
import os
import time

import gi

gi.require_version("Gtk", "4.0")
from gi.repository import GLib, Gtk  # noqa: E402

_GPU_FILES = glob.glob("/sys/class/drm/card*/device/gpu_busy_percent")
_NET_SKIP = ("lo", "docker", "veth", "br-", "virbr", "tun", "tap")


def _human(rate: float) -> str:
    for unit in ("B", "K", "M", "G"):
        if rate < 1024 or unit == "G":
            return f"{rate:4.1f}{unit}"
        rate /= 1024
    return f"{rate:4.1f}G"


class Statsbar(Gtk.Box):
    def __init__(self):
        super().__init__(orientation=Gtk.Orientation.HORIZONTAL, spacing=18,
                         halign=Gtk.Align.CENTER)
        self.add_css_class("statsbar")

        # short text labels rather than nerd glyphs (some don't exist in the font and
        # render as tofu); keeps the line legible on any theme.
        self._cpu = self._cell("CPU")
        self._ram = self._cell("MEM")
        self._gpu = self._cell("GPU")
        self._disk = self._cell("DISK")
        self._down = self._cell("↓")
        self._up = self._cell("↑")

        self._prev_cpu: tuple[int, int] | None = None
        self._prev_net: tuple[int, int] | None = None
        self._prev_t: float | None = None

        self._refresh()
        self._source = GLib.timeout_add_seconds(2, self._tick)

    def _cell(self, icon: str) -> Gtk.Label:
        box = Gtk.Box(orientation=Gtk.Orientation.HORIZONTAL, spacing=6)
        ic = Gtk.Label(label=icon)
        ic.add_css_class("stat-icon")
        val = Gtk.Label(label="…")
        val.add_css_class("stat-value")
        box.append(ic)
        box.append(val)
        self.append(box)
        return val

    def _tick(self) -> bool:
        self._refresh()
        return GLib.SOURCE_CONTINUE

    # -- readers -----------------------------------------------------------
    @staticmethod
    def _cpu_pct(prev) -> tuple[float, tuple[int, int]]:
        parts = [int(x) for x in open("/proc/stat").readline().split()[1:]]
        idle = parts[3] + parts[4]           # idle + iowait
        total = sum(parts)
        if prev is None:
            return 0.0, (total, idle)
        dt, di = total - prev[0], idle - prev[1]
        pct = (1 - di / dt) * 100 if dt > 0 else 0.0
        return max(0.0, min(100.0, pct)), (total, idle)

    @staticmethod
    def _ram_pct() -> float:
        mem = {}
        for line in open("/proc/meminfo"):
            k, _, v = line.partition(":")
            mem[k] = int(v.split()[0])
            if len(mem) > 4 and "MemAvailable" in mem:
                break
        total, avail = mem.get("MemTotal", 1), mem.get("MemAvailable", 0)
        return (1 - avail / total) * 100

    @staticmethod
    def _gpu_pct() -> float:
        best = 0
        for f in _GPU_FILES:
            try:
                best = max(best, int(open(f).read().strip()))
            except (OSError, ValueError):
                pass
        return float(best)

    @staticmethod
    def _disk_pct() -> float:
        s = os.statvfs("/")
        return (1 - s.f_bfree / s.f_blocks) * 100 if s.f_blocks else 0.0

    @staticmethod
    def _net_bytes() -> tuple[int, int]:
        rx = tx = 0
        with open("/proc/net/dev") as fh:
            for line in fh.readlines()[2:]:
                name, _, rest = line.partition(":")
                name = name.strip()
                if name.startswith(_NET_SKIP):
                    continue
                cols = rest.split()
                rx += int(cols[0])
                tx += int(cols[8])
        return rx, tx

    def _refresh(self) -> None:
        cpu, self._prev_cpu = self._cpu_pct(self._prev_cpu)
        self._cpu.set_text(f"{cpu:2.0f}%")
        self._ram.set_text(f"{self._ram_pct():2.0f}%")
        self._gpu.set_text(f"{self._gpu_pct():2.0f}%")
        self._disk.set_text(f"{self._disk_pct():2.0f}%")

        now = time.monotonic()
        rx, tx = self._net_bytes()
        if self._prev_net is not None and self._prev_t is not None and now > self._prev_t:
            dt = now - self._prev_t
            self._down.set_text(_human((rx - self._prev_net[0]) / dt) + "/s")
            self._up.set_text(_human((tx - self._prev_net[1]) / dt) + "/s")
        self._prev_net, self._prev_t = (rx, tx), now
