"""Pacman-Pakete-Metadaten (name/version/size/reason/repo/desc).

Bevorzugt `expac` für Geschwindigkeit (parse alle 6k pakete in <500ms).
Fallback auf `pacman -Qi` falls expac nicht installiert (~5s)."""
from __future__ import annotations

import re
import shutil
import subprocess
from dataclasses import dataclass


_SIZE_RE = re.compile(r"([\d.]+)\s*([KMGT]i?B|B)")
_UNITS = {
    "B": 1, "KiB": 1024, "MiB": 1024**2, "GiB": 1024**3, "TiB": 1024**4,
    "KB": 1000, "MB": 1000**2, "GB": 1000**3, "TB": 1000**4,
}


def _parse_size(s: str) -> int:
    m = _SIZE_RE.search(s)
    if not m:
        return 0
    val = float(m.group(1))
    return int(val * _UNITS.get(m.group(2), 1))


@dataclass
class Package:
    name: str
    version: str
    size_bytes: int
    reason: str         # 'explicit' / 'dependency'
    repo: str           # 'core' / 'extra' / 'multilib' / 'aur' (foreign) / 'local'
    desc: str = ""
    is_orphan: bool = False


def _foreign_set() -> set[str]:
    try:
        r = subprocess.run(
            ["pacman", "-Qmq"],
            capture_output=True, text=True, check=False, timeout=10,
        )
        return {l.strip() for l in r.stdout.splitlines() if l.strip()}
    except (FileNotFoundError, subprocess.TimeoutExpired):
        return set()


def _orphan_set() -> set[str]:
    try:
        r = subprocess.run(
            ["pacman", "-Qdtq"],
            capture_output=True, text=True, check=False, timeout=10,
        )
        return {l.strip() for l in r.stdout.splitlines() if l.strip()}
    except (FileNotFoundError, subprocess.TimeoutExpired):
        return set()


def _via_expac() -> list[Package]:
    try:
        r = subprocess.run(
            ["expac", "-Q", "%n\t%v\t%m\t%w\t%r\t%d"],
            capture_output=True, text=True, check=False, timeout=30,
        )
    except (FileNotFoundError, subprocess.TimeoutExpired):
        return []
    out: list[Package] = []
    for line in r.stdout.splitlines():
        parts = line.split("\t")
        if len(parts) < 5:
            continue
        try:
            size = int(parts[2])
        except ValueError:
            size = 0
        out.append(Package(
            name=parts[0],
            version=parts[1],
            size_bytes=size,
            reason=parts[3] or "explicit",
            repo=parts[4] or "local",
            desc=parts[5] if len(parts) > 5 else "",
        ))
    return out


def _via_pacman() -> list[Package]:
    try:
        r = subprocess.run(
            ["pacman", "-Qi"],
            capture_output=True, text=True, check=False, timeout=60,
        )
    except (FileNotFoundError, subprocess.TimeoutExpired):
        return []
    out: list[Package] = []
    cur: dict[str, object] = {}
    for line in r.stdout.splitlines():
        if not line.strip():
            if cur.get("name"):
                out.append(Package(
                    name=str(cur["name"]),
                    version=str(cur.get("version", "")),
                    size_bytes=int(cur.get("size", 0)),
                    reason=str(cur.get("reason", "explicit")),
                    repo=str(cur.get("repo", "local")),
                    desc=str(cur.get("desc", "")),
                ))
            cur = {}
            continue
        if ":" not in line:
            continue
        key, _, val = line.partition(":")
        key = key.strip().lower()
        val = val.strip()
        if key == "name":
            cur["name"] = val
        elif key == "version":
            cur["version"] = val
        elif key == "installed size":
            cur["size"] = _parse_size(val)
        elif key == "install reason":
            cur["reason"] = "explicit" if "explicit" in val.lower() else "dependency"
        elif key == "repository":
            cur["repo"] = val
        elif key == "description":
            cur["desc"] = val
    if cur.get("name"):
        out.append(Package(
            name=str(cur["name"]),
            version=str(cur.get("version", "")),
            size_bytes=int(cur.get("size", 0)),
            reason=str(cur.get("reason", "explicit")),
            repo=str(cur.get("repo", "local")),
            desc=str(cur.get("desc", "")),
        ))
    return out


def all_installed() -> list[Package]:
    if shutil.which("expac"):
        pkgs = _via_expac()
    else:
        pkgs = _via_pacman()

    foreign = _foreign_set()
    orphans = _orphan_set()
    for p in pkgs:
        if p.name in foreign:
            p.repo = "aur"
        if p.name in orphans:
            p.is_orphan = True
    return pkgs
