"""Flatpak-Scanner: unused runtimes + leftover ~/.var/app data."""
from __future__ import annotations

import os
import subprocess
from pathlib import Path

from taninux.maintain.scanners._util import dir_size, last_access_days
from taninux.maintain.scanners.pacman import _parse_size  # gleiche Logik
from taninux.maintain.types import Category, Classification, Finding


def _flatpak_available() -> bool:
    try:
        subprocess.run(
            ["flatpak", "--version"],
            capture_output=True, check=False, timeout=5,
        )
        return True
    except (FileNotFoundError, subprocess.TimeoutExpired):
        return False


def _installed_app_ids() -> set[str]:
    try:
        r = subprocess.run(
            ["flatpak", "list", "--app", "--columns=application"],
            capture_output=True, text=True, check=False, timeout=10,
        )
    except (FileNotFoundError, subprocess.TimeoutExpired):
        return set()
    return {
        line.strip()
        for line in r.stdout.splitlines()
        if line.strip() and line.strip() != "Application ID"
    }


def _app_runtime_refs() -> set[str]:
    """Welche Runtime-Refs werden von Apps benötigt?

    Refs haben Format `<app-id>/<arch>/<branch>`."""
    try:
        r = subprocess.run(
            ["flatpak", "list", "--app", "--columns=runtime"],
            capture_output=True, text=True, check=False, timeout=10,
        )
    except (FileNotFoundError, subprocess.TimeoutExpired):
        return set()
    refs: set[str] = set()
    for line in r.stdout.splitlines():
        s = line.strip()
        if not s or s == "Runtime":
            continue
        # Manche Outputs prefixen mit "runtime/" — abschneiden
        if s.startswith("runtime/"):
            s = s[len("runtime/"):]
        refs.add(s)
    return refs


# ----------------------------------------------------------------- runtimes


class FlatpakRuntimesScanner:
    """Installierte Runtimes ohne direkten App-Bezug.

    Heuristik (nicht so präzise wie `flatpak uninstall --unused`, das auch
    Extension-Ketten berücksichtigt) — Detail-Pane verweist auf den
    kanonischen Befehl."""
    name = "flatpak-runtimes"

    def scan(self) -> list[Finding]:
        if not _flatpak_available():
            return []

        try:
            r = subprocess.run(
                ["flatpak", "list", "--runtime",
                 "--columns=application,branch,arch,size"],
                capture_output=True, text=True, check=False, timeout=10,
            )
        except (FileNotFoundError, subprocess.TimeoutExpired):
            return []

        used = _app_runtime_refs()
        # used_app_ids enthält die Basis-IDs der genutzten Runtimes, ohne arch/branch
        used_app_ids = {ref.split("/", 1)[0] for ref in used}

        findings: list[Finding] = []
        for line in r.stdout.splitlines():
            s = line.strip()
            if not s or s.startswith("Application"):
                continue
            parts = s.split("\t")
            if len(parts) < 3:
                continue
            app_id, branch, arch = parts[0], parts[1], parts[2]
            size_str = parts[3] if len(parts) > 3 else ""
            ref = f"{app_id}/{arch}/{branch}"
            # Direkt-Match auf vollständigen Ref
            if ref in used:
                continue
            # Substring-Match auf Application-ID, fängt Extensions ein
            # (z.B. wenn org.freedesktop.Platform.GL.nvidia-... existiert
            # weil org.freedesktop.Platform genutzt wird)
            if any(app_id.startswith(u) for u in used_app_ids):
                continue
            findings.append(Finding(
                path=Path(f"flatpak://{ref}"),
                category=Category.FLATPAK,
                classification=Classification.LIKELY_ORPHAN,
                size_bytes=_parse_size(size_str) if size_str else 0,
                last_access_days=None,
                reason="Runtime hat keinen direkten App-Bezug",
                matched_package=app_id,
                extra={"kind": "runtime", "ref": ref, "branch": branch, "arch": arch},
            ))
        findings.sort(key=lambda f: -f.size_bytes)
        return findings


# ----------------------------------------------------------------- leftover data


class FlatpakDataScanner:
    """`~/.var/app/<app-id>/` für Flatpaks die nicht (mehr) installiert sind."""
    name = "flatpak-data"

    def scan(self) -> list[Finding]:
        var_app = Path.home() / ".var" / "app"
        if not var_app.is_dir():
            return []
        if not _flatpak_available():
            return []

        installed = _installed_app_ids()
        findings: list[Finding] = []
        try:
            entries = list(os.scandir(var_app))
        except (OSError, PermissionError):
            return []

        for entry in entries:
            try:
                if not entry.is_dir(follow_symlinks=False):
                    continue
            except OSError:
                continue
            app_id = entry.name
            if app_id in installed:
                continue
            path = Path(entry.path)
            size = dir_size(path)
            days = last_access_days(path)
            findings.append(Finding(
                path=path,
                category=Category.FLATPAK,
                classification=Classification.SAFE,
                size_bytes=size,
                last_access_days=days,
                reason=f"Flatpak {app_id} nicht installiert — Daten verwaist",
                matched_package=app_id,
                extra={"kind": "leftover-data", "app_id": app_id},
            ))
        findings.sort(key=lambda f: f.sort_key)
        return findings
