#!/usr/bin/env bash
# system-clean.sh — cross-distro disk cleanup helper
# Works on: Debian/Ubuntu (incl. Raspberry Pi OS), Fedora/RHEL/CentOS/Alma/Rocky, Arch/Manjaro, openSUSE, Alpine
# Optional tools handled if installed: snap, flatpak, docker, podman, npm, yarn, pip, conda
set -euo pipefail

# ------------- Defaults & flags -------------
DRYRUN=0
ASSUME_YES=0
JOURNAL_AGE="7d"         # keep last 7 days of logs
TMP_AGE_DAYS=7           # delete files older than this in /tmp and /var/tmp
CLEAN_USER_CACHE=1
CLEAN_DOCKER=0
CLEAN_DOCKER_VOLUMES=0
CLEAN_PODMAN=0
CLEAN_SNAP=0
CLEAN_FLATPAK=0
CLEAN_LANG_CACHES=0      # pip/npm/yarn/conda caches
AGGRESSIVE_PKG_CACHE=0   # stronger package cache cleanup (Arch -Scc, etc.)
AGGRESSIVE_DOCS=0        # remove man pages/locales/docs where safe/available (opt-in; not recommended on full systems)

# ------------- Helpers -------------
msg() { printf "%b\n" "$*"; }
warn() { printf "\033[33m%s\033[0m\n" "$*" >&2; }
err() { printf "\033[31m%s\033[0m\n" "$*" >&2; }
run() {
  if [[ $DRYRUN -eq 1 ]]; then
    echo "[dry-run] $*"
  else
    eval "$@"
  fi
}

need_confirm() {
  [[ $ASSUME_YES -eq 1 ]] && return 0
  read -rp "$1 [y/N]: " ans
  [[ "${ans:-}" =~ ^[Yy]$ ]]
}

bytes_free_root() {
  df -B1 / | awk 'NR==2{print $4}'
}

human() {
  num=${1:-0}
  awk -v b="$num" 'function human(x){ s="B KMGTPEZY"; while (x>=1024 && length(s)>1){x/=1024; s=substr(s,3)} return sprintf("%.1f %s", x, substr(s,1,1)) } BEGIN{ print human(b) }'
}

# ------------- OS / pkg manager detection -------------
OS_ID=""; OS_LIKE=""
if [[ -r /etc/os-release ]]; then
  # shellcheck disable=SC1091
  . /etc/os-release
  OS_ID="${ID:-}"; OS_LIKE="${ID_LIKE:-}"
fi

has() { command -v "$1" >/dev/null 2>&1; }

PKG=""
if has apt-get; then PKG="apt"
elif has dnf; then PKG="dnf"
elif has yum; then PKG="yum"
elif has pacman; then PKG="pacman"
elif has zypper; then PKG="zypper"
elif has apk; then PKG="apk"
fi

# ------------- Usage -------------
usage() {
cat <<'EOF'
Usage: system-clean.sh [options]

General:
  -n, --dry-run              Show what would be done, but don’t change anything
  -y, --yes                  Assume "yes" to prompts (non-interactive)
  --journal AGE              Keep only AGE of systemd journals (default: 7d)
  --tmp-days N               Delete files older than N days in /tmp, /var/tmp (default: 7)
  --no-user-cache            Do NOT clear the invoking user’s ~/.cache
  -A, --aggressive-cache     Stronger package cache cleanup (e.g., Arch -Scc)
  --lang-caches              Clear pip/npm/yarn/conda caches if present

Optional subsystems (off by default unless enabled):
  --docker                   Prune Docker images/containers/networks (no volumes)
  --docker-with-volumes      Also removes UNUSED Docker volumes (more space, more destructive)
  --podman                   Prune Podman images/containers/networks
  --snap                     Remove disabled Snap revisions
  --flatpak                  Remove unused Flatpak runtimes/apps

Aggressive (opt-in; know what you’re doing):
  --aggressive-docs          Try to remove local manuals/docs/locales to save space where supported

Help:
  -h, --help                 Show this help

Examples:
  sudo ./system-clean.sh -y --docker --flatpak --lang-caches
  sudo ./system-clean.sh -n --journal 3d --tmp-days 5
  sudo ./system-clean.sh -y -A --snap
EOF
}

# ------------- Parse args -------------
while [[ $# -gt 0 ]]; do
  case "$1" in
    -n|--dry-run) DRYRUN=1 ;;
    -y|--yes) ASSUME_YES=1 ;;
    --journal) JOURNAL_AGE="${2:-}"; shift ;;
    --tmp-days) TMP_AGE_DAYS="${2:-}"; shift ;;
    --no-user-cache) CLEAN_USER_CACHE=0 ;;
    -A|--aggressive-cache) AGGRESSIVE_PKG_CACHE=1 ;;
    --lang-caches) CLEAN_LANG_CACHES=1 ;;
    --docker) CLEAN_DOCKER=1 ;;
    --docker-with-volumes) CLEAN_DOCKER=1; CLEAN_DOCKER_VOLUMES=1 ;;
    --podman) CLEAN_PODMAN=1 ;;
    --snap) CLEAN_SNAP=1 ;;
    --flatpak) CLEAN_FLATPAK=1 ;;
    --aggressive-docs) AGGRESSIVE_DOCS=1 ;;
    -h|--help) usage; exit 0 ;;
    *) err "Unknown option: $1"; usage; exit 1 ;;
  esac
  shift
done

# ------------- Pre-flight -------------
if [[ $EUID -ne 0 ]]; then
  err "Please run as root (sudo)."
  exit 1
fi

START_FREE=$(bytes_free_root)

msg "Detected package manager: ${PKG:-none}"
if [[ -z "${PKG:-}" ]]; then
  warn "No known package manager found. Proceeding with generic cleanup only."
fi

msg "Starting cleanup... (dry-run: ${DRYRUN})"

# ------------- Package cache & orphans by distro -------------
case "$PKG" in
  apt)
    msg "[apt] Cleaning package caches and removing unused deps..."
    run "apt-get clean"
    run "apt-get autoremove --purge -y"
    if [[ $AGGRESSIVE_DOCS -eq 1 ]]; then
      # Highly optional: remove manpages/locales/docs to save space on tiny systems
      # (Keep en* locales; adjust as needed)
      msg "[apt] Aggressive docs/locales cleanup..."
      run "apt-get purge -y man-db || true"
      run "apt-get purge -y manpages manpages-dev || true"
      # Strip docs but keep licenses
      run "rm -rf /usr/share/doc/* /usr/share/info/* || true"
      # Locales (keep en*); requires locales package present
      if [[ -d /usr/share/locale ]]; then
        run "find /usr/share/locale -mindepth 1 -maxdepth 1 -type d ! -name 'en*' -print0 | xargs -0r rm -rf"
      fi
    fi
    ;;
  dnf)
    msg "[dnf] Cleaning caches and old packages..."
    run "dnf clean all -y"
    run "dnf autoremove -y || true"
    # Remove old kernels, keep last 3
    run "dnf remove --oldinstallonly --setopt installonly_limit=3 -y || true"
    ;;
  yum)
    msg "[yum] Cleaning caches and orphans..."
    run "yum clean all -y"
    run "package-cleanup --quiet --leaves --all | xargs -r yum remove -y || true"
    ;;
  pacman)
    msg "[pacman] Cleaning package cache..."
    if has paccache; then
      run "paccache -r"                            # remove unused cached pkgs (keep recent 3)
      [[ $AGGRESSIVE_PKG_CACHE -eq 1 ]] && run "paccache -rk0" # remove all cached packages
    else
      if [[ $AGGRESSIVE_PKG_CACHE -eq 1 ]]; then
        run "yes | pacman -Scc"
      else
        run "pacman -Sc --noconfirm"
      fi
    fi
    # Remove orphan packages
    ORPHANS=$(pacman -Qtdq 2>/dev/null || true)
    if [[ -n "${ORPHANS}" ]]; then
      if [[ $ASSUME_YES -eq 1 ]] || need_confirm "[pacman] Remove orphans: ${ORPHANS//$'\n'/ } ?"; then
        run "pacman -Rns --noconfirm ${ORPHANS}"
      fi
    fi
    ;;
  zypper)
    msg "[zypper] Cleaning caches and old kernels..."
    run "zypper --non-interactive clean --all || true"
    # Purge old kernels (keep last 2-3)
    run "zypper --non-interactive purge-kernels || true"
    # Remove orphaned packages
    if has zypper; then
      ORPH=$(zypper packages --orphaned | awk '/^[ip]/ {print $3}' | tail -n +2 || true)
      if [[ -n "${ORPH}" ]]; then
        if [[ $ASSUME_YES -eq 1 ]] || need_confirm "[zypper] Remove orphans?"; then
          run "zypper --non-interactive remove --clean-deps ${ORPH}"
        fi
      fi
    fi
    ;;
  apk)
    msg "[apk] Cleaning cache..."
    run "apk cache clean || true"
    run "rm -rf /var/cache/apk/* || true"
    ;;
esac

# ------------- Journals & logs -------------
if has journalctl; then
  msg "[logs] Vacuuming systemd journal to last ${JOURNAL_AGE}..."
  run "journalctl --vacuum-time='${JOURNAL_AGE}'"
else
  warn "[logs] systemd-journald not found; skipping journal vacuum."
fi

msg "[logs] Checking /var/log size and compressing rotated logs if any..."
# Compress large plain text logs and remove very old compressed logs (>90d)
run "find /var/log -type f -name '*.log' -size +10M -print0 | xargs -0r gzip -9"
run "find /var/log -type f -name '*.gz' -mtime +90 -print0 | xargs -0r rm -f"

# ------------- Temp directories -------------
msg "[tmp] Deleting files older than ${TMP_AGE_DAYS} days in /tmp and /var/tmp..."
run "find /tmp -xdev -mindepth 1 -mtime +${TMP_AGE_DAYS} -print0 | xargs -0r rm -rf"
run "find /var/tmp -xdev -mindepth 1 -mtime +${TMP_AGE_DAYS} -print0 | xargs -0r rm -rf"

# ------------- User cache(s) -------------
SUDO_USER_NAME="${SUDO_USER:-$(logname 2>/dev/null || echo '')}"
if [[ $CLEAN_USER_CACHE -eq 1 && -n "$SUDO_USER_NAME" ]]; then
  USER_HOME=$(getent passwd "$SUDO_USER_NAME" | cut -d: -f6)
  if [[ -n "$USER_HOME" && -d "$USER_HOME" ]]; then
    msg "[user] Clearing cache for $SUDO_USER_NAME in $USER_HOME/.cache ..."
    run "sudo -u \"$SUDO_USER_NAME\" bash -c 'rm -rf \"$HOME/.cache\"/* \"$HOME/.cache\"/.[!.]* 2>/dev/null || true'"
    # Thumbnail cache (GNOME/KDE/etc.)
    run "sudo -u \"$SUDO_USER_NAME\" bash -c 'rm -rf \"$HOME/.cache/thumbnails\"/* 2>/dev/null || true'"
    # npm/yarn/pip/conda caches (optional)
    if [[ $CLEAN_LANG_CACHES -eq 1 ]]; then
      msg "[user] Clearing language/tool caches for $SUDO_USER_NAME ..."
      run "sudo -u \"$SUDO_USER_NAME\" bash -c 'command -v npm >/dev/null && npm cache clean --force || true'"
      run "sudo -u \"$SUDO_USER_NAME\" bash -c 'command -v yarn >/dev/null && yarn cache clean || true'"
      run "sudo -u \"$SUDO_USER_NAME\" bash -c 'command -v pip >/dev/null && pip cache purge || true'"
      run "sudo -u \"$SUDO_USER_NAME\" bash -c 'command -v conda >/dev/null && conda clean -afy || true'"
    fi
  fi
fi

# ------------- Snap / Flatpak (optional) -------------
if [[ $CLEAN_SNAP -eq 1 ]] && has snap; then
  msg "[snap] Removing disabled revisions..."
  # remove disabled (old) revisions
  if [[ $DRYRUN -eq 1 ]]; then
    run "snap list --all | awk '/disabled/{print \$1, \$2}'"
  else
    snap list --all | awk '/disabled/{print $1, $2}' | while read -r name rev; do
      run "snap remove \"$name\" --revision=\"$rev\" || true"
    done
  fi
fi

if [[ $CLEAN_FLATPAK -eq 1 ]] && has flatpak; then
  msg "[flatpak] Removing unused runtimes/apps..."
  run "flatpak uninstall --unused -y || true"
fi

# ------------- Containers (optional) -------------
if [[ $CLEAN_DOCKER -eq 1 ]] && has docker; then
  msg "[docker] Pruning unused images/containers/networks..."
  if [[ $CLEAN_DOCKER_VOLUMES -eq 1 ]]; then
    run "docker system prune -af --volumes"
  else
    run "docker system prune -af"
  fi
fi

if [[ $CLEAN_PODMAN -eq 1 ]] && has podman; then
  msg "[podman] Pruning unused images/containers/networks..."
  run "podman system prune -af"
fi

# ------------- Misc: crash dumps & cores -------------
msg "[misc] Removing large core dumps and crash files (>100M)..."
run "find / -xdev -type f \\( -name 'core' -o -name 'core.*' -o -name '*.crash' \\) -size +100M -print0 2>/dev/null | xargs -0r rm -f"

# ------------- Finish -------------
END_FREE=$(bytes_free_root)
FREED=$(( END_FREE - START_FREE ))
msg "Done."
msg "Free space before: $(human "$START_FREE")"
msg "Free space after : $(human "$END_FREE")"
if (( FREED >= 0 )); then
  msg "Freed approximately: $(human "$FREED")"
else
  msg "Space used increased by: $(human "$((-FREED))") (this can happen due to journal rotation or package metadata updates)"
fi
