#!/bin/bash
# ╔══════════════════════════════════════════════════════════════════════════════╗
# ║  setup/audit-packages.sh — Package source audit tool                      ║
# ║                                                                              ║
# ║  PURPOSE:                                                                    ║
# ║    Verifies that installed packages come from the expected source           ║
# ║    (official pacman repos, AUR, or Flatpak). Reports:                     ║
# ║      - Missing packages that should be installed                           ║
# ║      - Packages installed from the wrong source (e.g. pacman pkg from AUR) ║
# ║      - Unexpected foreign/AUR packages not declared in setup scripts       ║
# ║                                                                              ║
# ║  USAGE:                                                                      ║
# ║    bash audit-packages.sh          # report only                           ║
# ║    bash audit-packages.sh --fix    # report + auto-reinstall wrong-source  ║
# ║    bash audit-packages.sh -f       # shorthand for --fix                   ║
# ║                                                                              ║
# ║  WHY THIS TOOL EXISTS:                                                       ║
# ║    On Arch, some packages exist in both the official repos AND the AUR      ║
# ║    (often with a different build/version). If an AUR package shadows an     ║
# ║    official one, pacman -Syu won't update it. This script detects drift.   ║
# ║                                                                              ║
# ║  EXIT CODES:                                                                 ║
# ║    0 — all checks passed                                                    ║
# ║    1 — one or more issues found                                             ║
# ╚══════════════════════════════════════════════════════════════════════════════╝

# Verifies installed packages come from the expected source (pacman/AUR/flatpak).
# Reports missing packages, wrong-source installs, and unexpected foreign packages.
# Pass --fix / -f to automatically reinstall packages from the correct source.
set -euo pipefail

# ── Output formatting ──────────────────────────────────────────────────────────
# ANSI color codes for colored terminal output

RED="\e[31m"; YELLOW="\e[33m"; GREEN="\e[32m"; CYAN="\e[36m"; BOLD="\e[1m"; RESET="\e[0m"

ok()   { echo -e "  ${GREEN}✔${RESET} $1"; }     # Green checkmark: package is correct
warn() { echo -e "  ${YELLOW}⚠${RESET} $1"; }    # Yellow warning: non-critical issue
err()  { echo -e "  ${RED}✘${RESET} $1"; }        # Red X: package missing or wrong
hdr()  { echo -e "\n${BOLD}${CYAN}$1${RESET}"; }  # Bold cyan section header
fix()  { echo -e "  ${CYAN}↺${RESET} $1"; }       # Cyan spinner: fix action in progress

# ── Fix mode flag ─────────────────────────────────────────────────────────────
# Parse the --fix / -f argument. When FIX=1, wrong-source packages will be
# automatically reinstalled from the correct source at the end of the audit.

FIX=0
[[ "${1:-}" == "--fix" || "${1:-}" == "-f" ]] && FIX=1

# ── Issue counter ─────────────────────────────────────────────────────────────
# ISSUES tracks the number of problems found. The final exit code is 1 if > 0.
ISSUES=0
flag() { ISSUES=$((ISSUES + 1)); }  # Increment the issue counter

# ── Wrong-source package lists ────────────────────────────────────────────────
# These arrays collect packages that need reinstallation when --fix is used.
WRONG_SOURCE_OFFICIAL=()   # expected from pacman, installed from AUR
WRONG_SOURCE_AUR=()        # expected from AUR, installed from official

# ---------------------------------------------------------------------------
# Expected package sources — keep in sync with setup/modules/
# ---------------------------------------------------------------------------
# IMPORTANT: When you add packages to any module script, add them here too.
# The lists are manually maintained to stay in sync with what modules install.

# ── PACMAN_PKGS: packages that MUST come from official Arch repositories ──────
# If any of these are found to be installed from the AUR instead, that's a drift
# issue — the AUR version may not receive official security updates.
PACMAN_PKGS=(
    # core-packages.sh
    7zip arch-install-scripts atftp atool
    base base-devel bind bluez btrfs-progs btop
    cockpit cockpit-files cockpit-podman cronie curl
    distrobox fail2ban fastfetch fd ffmpeg firefox flatpak
    gcc glib2 greetd-tuigreet grub
    htop inetutils iwd jq ldns less libpulse linux linux-firmware
    man-db mc nano neovim networkmanager
    openssh pciutils pipewire podman podman-compose
    python python-pip qrencode ruby-pkg-config rust rustup
    ipcalc iputils mtr net-tools nmap
    smartmontools symlinks tcpdump traceroute tree
    udisks2 udisks2-btrfs udiskie ufw usbutils
    vim vnstat wget wireplumber wireless_tools wpa_supplicant wprs
    yazi zip unzip zram-generator
    # shell-setup.sh
    zsh pyright bash atftp bash-language-server clang fzf hyfetch
    lua-language-server micro pulsemixer z dysk glow
    # hyprland.sh (official-repo portion)
    hyprland hyprcursor wl-clipboard hyprpaper hyprlock wofi kitty dunst
    nwg-dock-hyprland nwg-drawer nwg-menu nwg-look
    cmake meson cpio pkgconf
    hyprsunset hypridle ksshaskpass
    nm-connection-editor network-manager-applet blueman
    pipewire alsa-utils greetd-tuigreet
    grim slurp gst-plugin-pipewire imagemagick
    nerd-fonts otf-font-awesome
    pipewire-alsa pipewire-jack pipewire-pulse
    qt5-wayland qt6-wayland swww ttf-jetbrains-mono
    qt6ct xdg-desktop-portal-hyprland xdg-utils
    xorg-server xorg-xinit papirus-icon-theme
    cool-retro-term qalculate-gtk dbus
    thunar tumbler thunar-archive-plugin thunar-shares-plugin thunar-volman
    hyprpicker pcmanfm-qt ly
    hyprpolkitagent pavucontrol playerctl wf-recorder sound-theme-freedesktop
    kew
)

# ── AUR_PKGS: packages that MUST come from the AUR ───────────────────────────
# If any of these are found in the official repos instead, it may mean Arch has
# promoted them — but could also mean a naming collision. Flagged for review.
# NOTE: AUR packages are treated as optional (warn, not err) when missing,
#       because they may be legitimately excluded on some systems.
AUR_PKGS=(
    # hyprland.sh AUR portion (not in official repos)
    hyprland-workspaces vicinae-bin bluetuith wvkbd iwmenu
    walker-bin ulauncher bzmenu wofi-calc bri chamel
    # optional apps (only installed if selected in TUI)
    pamtester          # PAM authentication tester
    pinta              # Simple image editor (paint.net-like)
    localsend          # LAN file transfer
    vesktop            # Discord with Vencord themes
    onlyoffice-bin     # Office suite (pre-built binary from AUR)
    vintagestory       # Survival game
    wprs-git           # Wayland proxy (git version)
    zfs-dkms           # ZFS kernel module
)

# ── FLATPAK_PKGS: apps installed via Flatpak (Flathub) ───────────────────────
# These are cross-distro app bundles. Listed by their Flatpak app ID.
FLATPAK_PKGS=(
    org.prismlauncher.PrismLauncher   # Minecraft launcher
)

# ---------------------------------------------------------------------------
# Snapshot current state
# ---------------------------------------------------------------------------
# Take a snapshot of what's installed now so we don't repeatedly call pacman.
# This also ensures consistent results across the whole run.

ALL_INSTALLED=$(pacman -Qq 2>/dev/null)   # All installed packages (name only)
FOREIGN=$(pacman -Qmq 2>/dev/null)        # AUR / manually installed (foreign) packages
OFFICIAL=$(pacman -Qnq 2>/dev/null)       # Packages from official sync repos

# Helper functions for membership testing — grep -qx matches whole lines exactly
is_installed()      { echo "$ALL_INSTALLED" | grep -qx "$1"; }
is_from_official()  { echo "$OFFICIAL"      | grep -qx "$1"; }
is_from_aur()       { echo "$FOREIGN"       | grep -qx "$1"; }

# ---------------------------------------------------------------------------
# 1. Check pacman packages
# ---------------------------------------------------------------------------
# For each expected official package:
#   - NOT installed: hard error (✘) — increment issue counter
#   - Installed from AUR: warning (⚠) — drift; increment counter, queue for fix
#   - Installed from official: pass (✔)

hdr "1/3  Official-repo packages (pacman)"

for pkg in "${PACMAN_PKGS[@]}"; do
    if ! is_installed "$pkg"; then
        err "$pkg — NOT INSTALLED"; flag
    elif is_from_aur "$pkg"; then
        # This package should come from official repos but is installed from AUR.
        # This means `pacman -Syu` won't update it — potential security gap.
        warn "$pkg — installed, but from AUR instead of official repo"; flag
        WRONG_SOURCE_OFFICIAL+=("$pkg")
    else
        ok "$pkg"
    fi
done

# ---------------------------------------------------------------------------
# 2. Check AUR packages
# ---------------------------------------------------------------------------
# For each expected AUR package:
#   - NOT installed: soft warning — AUR packages are typically optional
#   - Installed from official repo: warning — may get wrong version from Arch
#   - Installed from AUR: pass (✔)

hdr "2/3  AUR packages (yay)"

for pkg in "${AUR_PKGS[@]}"; do
    if ! is_installed "$pkg"; then
        # AUR packages are often optional (selected in TUI installer), so this
        # is a warning rather than an error — intentional omission is common.
        warn "$pkg — not installed (optional — may be intentional)"
    elif is_from_official "$pkg"; then
        # This package should come from AUR but is installed from official repos.
        # The official version may differ (newer or older) — flagged for review.
        warn "$pkg — installed from official repo, not AUR (may need AUR build for correct version)"; flag
        WRONG_SOURCE_AUR+=("$pkg")
    else
        ok "$pkg"
    fi
done

# ---------------------------------------------------------------------------
# 3. Check flatpak packages
# ---------------------------------------------------------------------------
# Flatpak packages are checked separately using the `flatpak` command.
# Missing Flatpaks are soft warnings since they're always optional.

hdr "3/3  Flatpak packages"

if command -v flatpak &>/dev/null; then
    # List installed Flatpak apps (not runtimes) in the 'application' column only
    FLATPAK_INSTALLED=$(flatpak list --app --columns=application 2>/dev/null)
    for pkg in "${FLATPAK_PKGS[@]}"; do
        if echo "$FLATPAK_INSTALLED" | grep -qx "$pkg"; then
            ok "$pkg"
        else
            warn "$pkg — not installed (optional — may be intentional)"
        fi
    done
else
    warn "flatpak not found — skipping flatpak checks"
fi

# ---------------------------------------------------------------------------
# 4. Flag unexpected foreign (AUR) packages not in our lists
# ---------------------------------------------------------------------------
# Any AUR package installed on the system but NOT in our AUR_PKGS list is
# unexpected. These may have been installed manually or by other tools.
# They're highlighted for human review — not counted as issues.

hdr "Bonus: foreign packages not declared in setup scripts"

# Build a newline-separated set of declared AUR packages for grep matching
AUR_SET=$(printf "%s\n" "${AUR_PKGS[@]}")
while IFS= read -r pkg; do
    # If the foreign package is not in our declared AUR list, highlight it
    if ! echo "$AUR_SET" | grep -qx "$pkg"; then
        echo -e "  ${CYAN}?${RESET} $pkg — foreign package not in setup scripts"
    fi
done <<< "$FOREIGN"

# ---------------------------------------------------------------------------
# Fix: reinstall wrong-source packages
# ---------------------------------------------------------------------------
# When --fix is passed, reinstall any packages found in the wrong source.
# Official packages are reinstalled with pacman; AUR packages with yay --aur.

FIXED=0

if [ "$FIX" -eq 1 ]; then
    if [ ${#WRONG_SOURCE_OFFICIAL[@]} -gt 0 ] || [ ${#WRONG_SOURCE_AUR[@]} -gt 0 ]; then
        hdr "Reinstalling wrong-source packages"
    fi

    # Reinstall from official repo (packages that were incorrectly from AUR)
    for pkg in "${WRONG_SOURCE_OFFICIAL[@]}"; do
        fix "Reinstalling $pkg from official repo (was AUR)..."
        # pacman -S without --aur ensures it comes from the sync database
        if sudo pacman -S --noconfirm "$pkg"; then
            ok "$pkg reinstalled from official repo"
            FIXED=$((FIXED + 1))
            ISSUES=$((ISSUES - 1))  # Remove from issue count since it's now fixed
        else
            err "$pkg reinstall failed"
        fi
    done

    # Reinstall from AUR (packages that were incorrectly from official repos)
    for pkg in "${WRONG_SOURCE_AUR[@]}"; do
        fix "Reinstalling $pkg from AUR (was official repo)..."
        # --aur flag ensures yay only looks in the AUR, not official repos
        if yay -S --aur --noconfirm "$pkg"; then
            ok "$pkg reinstalled from AUR"
            FIXED=$((FIXED + 1))
            ISSUES=$((ISSUES - 1))
        else
            err "$pkg reinstall failed"
        fi
    done
fi

# ---------------------------------------------------------------------------
# Summary
# ---------------------------------------------------------------------------

echo
if [ "$FIX" -eq 1 ] && [ "$FIXED" -gt 0 ]; then
    echo -e "${GREEN}${BOLD}Fixed ${FIXED} wrong-source package(s).${RESET}"
fi

if [ "$ISSUES" -eq 0 ]; then
    echo -e "${GREEN}${BOLD}All checks passed — no source mismatches or missing required packages.${RESET}"
else
    echo -e "${RED}${BOLD}${ISSUES} issue(s) found — review items marked ✘ or ⚠ above.${RESET}"
    # Only show the --fix hint if there are packages that can actually be auto-fixed
    [ "$FIX" -eq 0 ] && [ $((${#WRONG_SOURCE_OFFICIAL[@]} + ${#WRONG_SOURCE_AUR[@]})) -gt 0 ] && \
        echo -e "${YELLOW}  Run with --fix to automatically reinstall wrong-source packages.${RESET}"
    exit 1
fi
