"""Ubuntu Studio Installer backend."""

from __future__ import annotations

import re
import subprocess
from collections.abc import Callable
from dataclasses import dataclass

from i18n import _

OutputCallback = Callable[[str], None] | None
ProgressCallback = Callable[[float], None] | None

# Matches the apt summary line, e.g.
# "0 upgraded, 5 newly installed, 0 to remove and 0 not upgraded."
_APT_SUMMARY_RE = re.compile(
    r"(\d+)\s+upgraded,\s*(\d+)\s+newly installed,\s*(\d+)\s+to remove"
)

# ---------------------------------------------------------------------------

APP_TITLE = _("Ubuntu Studio Installer")

ICON_PATH = "/usr/share/icons/hicolor/scalable/apps/ubuntustudio-installer.svg"

HELPER = "/usr/sbin/ubuntustudio-installer-helper"

# The metapackages offered for install / removal.
METAPACKAGES: tuple[str, ...] = (
    "ubuntustudio-lowlatency-settings",
    "ubuntustudio-performance-tweaks",
    "ubuntustudio-audio",
    "ubuntustudio-graphics",
    "ubuntustudio-photography",
    "ubuntustudio-publishing",
    "ubuntustudio-video",
    "ubuntustudio-wallpapers",
    "ubuntustudio-menu",
    "ubuntu-edu-music",
)


# ---------------------------------------------------------------------------


@dataclass
class PackageInfo:
    """A metapackage row for the checklist."""

    name: str
    description: str
    installed: bool


# ---------------------------------------------------------------------------


def dpkg_installed(package: str) -> bool:
    """Return True if *package* is currently installed."""
    try:
        result = subprocess.run(
            ["dpkg", "-s", package],
            capture_output=True,
            text=True,
        )
        return result.returncode == 0
    except FileNotFoundError:
        return False


def apt_cache_description(package: str) -> str:
    """Return the short description from apt-cache."""
    try:
        result = subprocess.run(
            ["apt-cache", "search", f"^{package}$"],
            capture_output=True,
            text=True,
        )
        for line in result.stdout.strip().splitlines():
            if line.startswith(package):
                # Format: "package-name - Some description"
                parts = line.split(" - ", 1)
                if len(parts) == 2:
                    return parts[1].strip()
        return ""
    except FileNotFoundError:
        return ""


def apt_cache_depends(package: str) -> list[str]:
    """Return direct dependency names of *package*."""
    try:
        result = subprocess.run(
            ["apt-cache", "depends", package],
            capture_output=True,
            text=True,
        )
        deps: list[str] = []
        for line in result.stdout.strip().splitlines():
            if ":" in line:
                dep = line.split(":", 1)[1].strip()
                if dep and dep != "386":
                    deps.append(dep)
        return deps
    except FileNotFoundError:
        return []


def _run_streaming(
    cmd: list[str],
    on_output: OutputCallback = None,
) -> bool:
    """Run *cmd*, streaming stdout/stderr to *on_output*. Returns True on success."""
    try:
        proc = subprocess.Popen(
            cmd,
            stdout=subprocess.PIPE,
            stderr=subprocess.STDOUT,
            text=True,
        )
        assert proc.stdout is not None
        for line in proc.stdout:
            if on_output is not None:
                on_output(line)
        proc.wait()
        return proc.returncode == 0
    except FileNotFoundError:
        return False


def update_apt_cache(on_output: OutputCallback = None) -> bool:
    """Run apt-get update via pkexec."""
    return _run_streaming(["pkexec", HELPER, "update"], on_output)


def build_package_table() -> list[PackageInfo]:
    """Return a PackageInfo list for every metapackage."""
    table: list[PackageInfo] = []
    for pkg in METAPACKAGES:
        installed = dpkg_installed(pkg)
        description = apt_cache_description(pkg)
        table.append(PackageInfo(name=pkg, description=description, installed=installed))
    return table


def install_packages(
    packages: list[str],
    on_output: OutputCallback = None,
    on_progress: ProgressCallback = None,
) -> bool:
    """Install *packages* via pkexec, optionally tracking progress."""
    if not packages:
        return True
    cmd = ["pkexec", HELPER, "install"] + packages

    if on_progress is None:
        return _run_streaming(cmd, on_output)

    total = 0
    done = 0

    def _track(line: str) -> None:
        nonlocal total, done
        if on_output is not None:
            on_output(line)
        m = _APT_SUMMARY_RE.search(line)
        if m:
            total = int(m.group(1)) + int(m.group(2))  # upgraded + newly installed
        if total > 0 and line.startswith("Setting up "):
            done += 1
            on_progress(min(done / total, 1.0))

    return _run_streaming(cmd, _track)


def run_installer_fix() -> bool:
    """Run the audio-configuration fix via pkexec."""
    try:
        result = subprocess.run(["pkexec", HELPER, "fix"])
        return result.returncode == 0
    except FileNotFoundError:
        return False


def autoremove_packages(
    packages: list[str],
    on_output: OutputCallback = None,
    on_progress: ProgressCallback = None,
) -> bool:
    """Auto-mark deps and autoremove *packages* via pkexec."""
    if not packages:
        return True
    cmd = ["pkexec", HELPER, "autoremove"] + packages

    if on_progress is None:
        return _run_streaming(cmd, on_output)

    total = 0
    done = 0

    def _track(line: str) -> None:
        nonlocal total, done
        if on_output is not None:
            on_output(line)
        m = _APT_SUMMARY_RE.search(line)
        if m:
            total = int(m.group(3))  # "to remove"
        if total > 0 and line.startswith("Removing "):
            done += 1
            on_progress(min(done / total, 1.0))

    return _run_streaming(cmd, _track)



