"""Geteilte System-Info-Erkennung — genutzt von health, suggest, kernel."""
from __future__ import annotations

import os
import platform
import re
import shutil
import subprocess
from dataclasses import dataclass, field
from pathlib import Path


# ----------------------------------------------------------------- base


def hostname() -> str:
    return platform.node() or "?"


def kernel_release() -> str:
    return platform.release()


def distro_pretty() -> str:
    try:
        for line in Path("/etc/os-release").read_text().splitlines():
            if line.startswith("PRETTY_NAME="):
                return line.split("=", 1)[1].strip().strip('"')
    except OSError:
        pass
    return "?"


def uptime_seconds() -> float:
    try:
        with open("/proc/uptime") as f:
            return float(f.read().split()[0])
    except OSError:
        return 0.0


def load_avg() -> tuple[float, float, float]:
    try:
        return os.getloadavg()
    except OSError:
        return (0.0, 0.0, 0.0)


def cpu_model() -> str:
    try:
        with open("/proc/cpuinfo") as f:
            for line in f:
                if line.startswith("model name"):
                    return line.split(":", 1)[1].strip()
    except OSError:
        pass
    return "Unknown CPU"


def cpu_count() -> int:
    return os.cpu_count() or 1


def memory() -> dict[str, int]:
    """MemTotal/MemAvailable/SwapTotal/SwapFree etc in Bytes."""
    out: dict[str, int] = {}
    try:
        with open("/proc/meminfo") as f:
            for line in f:
                parts = line.split()
                if len(parts) >= 2:
                    key = parts[0].rstrip(":")
                    try:
                        out[key] = int(parts[1]) * 1024
                    except ValueError:
                        continue
    except OSError:
        pass
    return out


def boot_time_seconds() -> float | None:
    """systemd-analyze boot duration."""
    try:
        r = subprocess.run(
            ["systemd-analyze", "time"],
            capture_output=True, text=True, check=False, timeout=5,
        )
        m = re.search(r"=\s*([\d.]+)s", r.stdout)
        if m:
            return float(m.group(1))
    except (FileNotFoundError, subprocess.TimeoutExpired):
        return None
    return None


# ----------------------------------------------------------------- hardware


@dataclass
class GPU:
    vendor: str        # "nvidia" / "amd" / "intel" / "unknown"
    model: str
    pci_slot: str
    driver: str | None = None


def _detect_gpu_vendor(text: str) -> str:
    t = text.lower()
    if "nvidia" in t:
        return "nvidia"
    if "amd" in t or "radeon" in t or "ati " in t:
        return "amd"
    if "intel" in t:
        return "intel"
    return "unknown"


def gpus() -> list[GPU]:
    """Parse `lspci -k` für VGA/3D/Display controller."""
    try:
        r = subprocess.run(
            ["lspci", "-k"],
            capture_output=True, text=True, check=False, timeout=5,
        )
    except (FileNotFoundError, subprocess.TimeoutExpired):
        return []
    found: list[GPU] = []
    current: GPU | None = None
    # lspci -k Format: jede Top-Level-Zeile beginnt mit "BB:DD.F" (bus:device.function)
    # Sub-Zeilen ("Kernel driver…", "Kernel modules…") sind eingerückt.
    for line in r.stdout.splitlines():
        # Neue Top-Level-Zeile?
        if line and not line.startswith((" ", "\t")) and ":" in line.split()[0]:
            # Reset: alte GPU bekommt keine driver-zeile mehr von hier
            current = None
            if any(k in line for k in (
                "VGA compatible controller",
                "3D controller",
                "Display controller",
            )):
                parts = line.split(":", 2)
                if len(parts) >= 3:
                    pci = parts[0].strip()
                    model = parts[2].strip()
                    current = GPU(
                        vendor=_detect_gpu_vendor(model),
                        model=model,
                        pci_slot=pci,
                        driver=None,
                    )
                    found.append(current)
        elif current and line.lstrip().startswith("Kernel driver in use:"):
            current.driver = line.split(":", 1)[1].strip()
    return found


def audio_server() -> str:
    """pipewire / pulse / ? (best-effort heuristic)."""
    uid = os.getuid()
    runtime = Path(f"/run/user/{uid}")
    if (runtime / "pipewire-0").exists():
        return "pipewire"
    if (runtime / "pulse").exists():
        return "pulse"
    return "?"


@dataclass
class NetIface:
    name: str
    state: str       # "UP" / "DOWN" / "UNKNOWN"
    addrs: list[str] = field(default_factory=list)


def network_interfaces() -> list[NetIface]:
    try:
        r = subprocess.run(
            ["ip", "-br", "addr"],
            capture_output=True, text=True, check=False, timeout=5,
        )
    except (FileNotFoundError, subprocess.TimeoutExpired):
        return []
    out: list[NetIface] = []
    for line in r.stdout.splitlines():
        parts = line.split()
        if len(parts) < 2:
            continue
        name = parts[0]
        state = parts[1]
        addrs = parts[2:] if len(parts) > 2 else []
        out.append(NetIface(name=name, state=state, addrs=addrs))
    return out


@dataclass
class Disk:
    name: str
    size_bytes: int
    model: str
    rotational: bool


def disks() -> list[Disk]:
    try:
        r = subprocess.run(
            ["lsblk", "-d", "-b", "-n", "-o", "NAME,SIZE,ROTA,MODEL"],
            capture_output=True, text=True, check=False, timeout=5,
        )
    except (FileNotFoundError, subprocess.TimeoutExpired):
        return []
    out: list[Disk] = []
    for line in r.stdout.splitlines():
        parts = line.split(None, 3)
        if len(parts) < 3:
            continue
        try:
            size = int(parts[1])
        except ValueError:
            continue
        out.append(Disk(
            name=parts[0],
            size_bytes=size,
            rotational=parts[2] == "1",
            model=parts[3].strip() if len(parts) > 3 else "",
        ))
    return out


# ----------------------------------------------------------------- services / logs


def systemctl_failed() -> list[str]:
    try:
        r = subprocess.run(
            ["systemctl", "list-units", "--failed", "--no-legend", "--plain"],
            capture_output=True, text=True, check=False, timeout=5,
        )
    except (FileNotFoundError, subprocess.TimeoutExpired):
        return []
    return [
        line.split()[0]
        for line in r.stdout.splitlines()
        if line.strip()
    ]


def journal_errors_today() -> int:
    try:
        r = subprocess.run(
            ["journalctl", "-p", "3", "--since", "today", "-q", "--no-pager"],
            capture_output=True, text=True, check=False, timeout=10,
        )
        return len([l for l in r.stdout.splitlines() if l.strip()])
    except (FileNotFoundError, subprocess.TimeoutExpired):
        return -1


def journal_errors_boot() -> int:
    try:
        r = subprocess.run(
            ["journalctl", "-p", "3", "-b", "-q", "--no-pager"],
            capture_output=True, text=True, check=False, timeout=10,
        )
        return len([l for l in r.stdout.splitlines() if l.strip()])
    except (FileNotFoundError, subprocess.TimeoutExpired):
        return -1


# ----------------------------------------------------------------- binaries


def has_binary(name: str) -> bool:
    return shutil.which(name) is not None
