#!/usr/bin/env bash
# backup-system.sh — Snapshot mise + fish configs and a TXT inventory of apps
#                    (GUI .app bundles, Homebrew formulae/casks, CLI tools, mas, npm/pip/cargo/gem globals)
#
# Usage:  bash backup-system.sh [destination-dir]
# Default destination: ./backups/<host>-<YYYY-MM-DD-HHMMSS>/

set -euo pipefail

# ───── pretty output ──────────────────────────────────────────────────────────
GRN=$'\033[1;32m'; YLW=$'\033[1;33m'; BLU=$'\033[1;34m'; DIM=$'\033[2m'; RST=$'\033[0m'
info() { printf "%s[i]%s %s\n" "$BLU" "$RST" "$*"; }
ok()   { printf "%s[✓]%s %s\n" "$GRN" "$RST" "$*"; }
warn() { printf "%s[!]%s %s\n" "$YLW" "$RST" "$*"; }
skip() { printf "%s[-]%s %s\n" "$DIM" "$RST" "$*"; }

# ───── destination ────────────────────────────────────────────────────────────
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
STAMP="$(date +%Y-%m-%d-%H%M%S)"
HOST="$(scutil --get LocalHostName 2>/dev/null || hostname -s)"
DEST="${1:-$SCRIPT_DIR/backups/${HOST}-${STAMP}}"
mkdir -p "$DEST"
info "Backup destination: $DEST"

# ───── 1. mise config ─────────────────────────────────────────────────────────
info "Backing up mise config…"
MISE_DIR="$DEST/mise"
mkdir -p "$MISE_DIR"
copied_any=false
# Directory: copy *contents* (trailing /.) to avoid mise/mise/ nesting
if [[ -d "$HOME/.config/mise" ]]; then
  cp -aR "$HOME/.config/mise/." "$MISE_DIR/" && copied_any=true
fi
# Loose files
for src in "$HOME/.mise.toml" "$HOME/.tool-versions"; do
  if [[ -f "$src" ]]; then
    cp -a "$src" "$MISE_DIR/" && copied_any=true
  fi
done
if command -v mise >/dev/null 2>&1; then
  mise ls --installed > "$MISE_DIR/installed-tools.txt" 2>/dev/null || true
  mise current      > "$MISE_DIR/current-tools.txt"   2>/dev/null || true
fi
$copied_any && ok "mise config saved" || skip "no mise config files found"

# ───── 2. fish config ─────────────────────────────────────────────────────────
info "Backing up fish config…"
FISH_DIR="$DEST/fish"
if [[ -d "$HOME/.config/fish" ]]; then
  mkdir -p "$FISH_DIR"
  # Trailing /. → copy contents, not the dir itself (avoid fish/fish/ nesting)
  cp -aR "$HOME/.config/fish/." "$FISH_DIR/"
  if command -v fish >/dev/null 2>&1; then
    fish -c 'functions -n' > "$FISH_DIR/functions-list.txt" 2>/dev/null || true
    fish -c 'abbr --show'  > "$FISH_DIR/abbreviations.txt"  2>/dev/null || true
    fish -c 'set -U -n'    > "$FISH_DIR/universal-vars.txt" 2>/dev/null || true
  fi
  ok "fish config saved"
else
  skip "no ~/.config/fish directory"
fi

# ───── 3. Safari bookmarks ────────────────────────────────────────────────────
info "Backing up Safari bookmarks…"
SAFARI_DIR="$DEST/safari"
SAFARI_SRC="$HOME/Library/Safari/Bookmarks.plist"
if [[ -f "$SAFARI_SRC" ]]; then
  # Real read attempt — `[[ -r ]]` is fooled by TCC, only an actual read tells the truth.
  if head -c 1 "$SAFARI_SRC" >/dev/null 2>&1; then
    mkdir -p "$SAFARI_DIR"
    # Raw binary plist (what Safari actually uses)
    cp -a "$SAFARI_SRC" "$SAFARI_DIR/Bookmarks.plist"
    # Text-readable XML form for inspection / grep
    plutil -convert xml1 -o "$SAFARI_DIR/Bookmarks.xml" "$SAFARI_SRC" 2>/dev/null || true
    # Plain URL list (one URL per line)
    plutil -convert xml1 -o - "$SAFARI_SRC" 2>/dev/null \
      | awk '/<key>URLString<\/key>/{getline; gsub(/<\/?string>/,""); gsub(/^[ \t]+|[ \t]+$/,""); print}' \
      | sort -u > "$SAFARI_DIR/bookmarks-urls.txt"
    COUNT="$(wc -l < "$SAFARI_DIR/bookmarks-urls.txt" 2>/dev/null | tr -d ' ')"
    ok "safari bookmarks saved (${COUNT:-0} URLs)"
  else
    warn "Safari bookmarks file exists but is blocked by TCC (Full Disk Access)."
    warn "  Fix: System Settings → Privacy & Security → Full Disk Access → add your terminal app (Ghostty / Terminal / iTerm)."
    warn "  Then re-run this script."
  fi
else
  skip "no Safari Bookmarks.plist found"
fi

# ───── 4. application & CLI inventory ─────────────────────────────────────────
INV="$DEST/inventory"
mkdir -p "$INV"
info "Generating inventory…"

# 3a — GUI apps in /Applications and ~/Applications
{
  for dir in /Applications "$HOME/Applications"; do
    [[ -d "$dir" ]] || continue
    echo "## $dir"
    find "$dir" -maxdepth 2 -name "*.app" -prune -print 2>/dev/null \
      | sed 's|.*/||; s|\.app$||' | sort -fu
    echo
  done
} > "$INV/applications.txt"
ok "applications.txt"

# 3b — Homebrew
if command -v brew >/dev/null 2>&1; then
  brew leaves             > "$INV/brew-formulae.txt"   2>/dev/null || true
  brew list --cask        > "$INV/brew-casks.txt"      2>/dev/null || true
  brew tap                > "$INV/brew-taps.txt"       2>/dev/null || true
  brew bundle dump --describe --force --file="$INV/Brewfile" >/dev/null 2>&1 || true
  ok "brew-formulae.txt / brew-casks.txt / brew-taps.txt / Brewfile"
else
  skip "brew not installed"
fi

# 3c — Mac App Store (mas)
if command -v mas >/dev/null 2>&1; then
  mas list > "$INV/mas-apps.txt" 2>/dev/null || true
  ok "mas-apps.txt"
else
  skip "mas not installed — install with: brew install mas"
fi

# 3d — CLI tools in common bin dirs (names only)
{
  for d in /opt/homebrew/bin /opt/homebrew/sbin /usr/local/bin /usr/local/sbin "$HOME/.local/bin" "$HOME/bin"; do
    [[ -d "$d" ]] || continue
    echo "## $d"
    find "$d" -maxdepth 1 -mindepth 1 \( -type f -o -type l \) -perm -111 -print 2>/dev/null \
      | sed 's|.*/||' | sort -fu
    echo
  done
} > "$INV/cli-tools.txt"
ok "cli-tools.txt"

# 3e — language-specific globals
if command -v npm >/dev/null 2>&1; then
  npm ls -g --depth=0 --parseable 2>/dev/null | sed 's|.*/||' | grep -v '^$' | sort -fu > "$INV/npm-globals.txt" || true
  ok "npm-globals.txt"
fi
if command -v pnpm >/dev/null 2>&1; then
  pnpm list -g --depth 0 > "$INV/pnpm-globals.txt" 2>/dev/null || true
  if [[ -s "$INV/pnpm-globals.txt" ]]; then
    ok "pnpm-globals.txt"
  else
    skip "pnpm-globals.txt (no global packages)"
    rm -f "$INV/pnpm-globals.txt"
  fi
fi
if command -v pipx >/dev/null 2>&1; then
  pipx list --short > "$INV/pipx-list.txt" 2>/dev/null || true
  ok "pipx-list.txt"
fi
if command -v pip3 >/dev/null 2>&1; then
  pip3 freeze --user > "$INV/pip3-user.txt" 2>/dev/null || true
fi
if command -v uv >/dev/null 2>&1; then
  uv tool list > "$INV/uv-tools.txt" 2>/dev/null || true
  ok "uv-tools.txt"
fi
if command -v cargo >/dev/null 2>&1; then
  cargo install --list > "$INV/cargo-installed.txt" 2>/dev/null || true
  ok "cargo-installed.txt"
fi
if command -v gem >/dev/null 2>&1; then
  gem list > "$INV/ruby-gems.txt" 2>/dev/null || true
fi
if command -v go >/dev/null 2>&1 && [[ -d "${GOPATH:-$HOME/go}/bin" ]]; then
  ls "${GOPATH:-$HOME/go}/bin" 2>/dev/null | sort -fu > "$INV/go-bin.txt" || true
fi
if command -v code >/dev/null 2>&1; then
  code --list-extensions > "$INV/vscode-extensions.txt" 2>/dev/null || true
  ok "vscode-extensions.txt"
fi
if command -v cursor >/dev/null 2>&1; then
  cursor --list-extensions > "$INV/cursor-extensions.txt" 2>/dev/null || true
fi

# 3f — system metadata
{
  echo "Host:        $HOST"
  echo "Date:        $(date)"
  echo "macOS:       $(sw_vers -productName) $(sw_vers -productVersion) (build $(sw_vers -buildVersion))"
  echo "Kernel:      $(uname -srm)"
  echo "Shell:       $SHELL"
  echo "CPU:         $(sysctl -n machdep.cpu.brand_string 2>/dev/null || echo n/a)"
} > "$INV/system-info.txt"

# ───── 5. cleanup macOS detritus ──────────────────────────────────────────────
find "$DEST" -name '.DS_Store' -delete 2>/dev/null || true

# ───── 6. compress ────────────────────────────────────────────────────────────
ARCHIVE="${DEST}.tar.gz"
info "Compressing → $ARCHIVE"
tar -czf "$ARCHIVE" -C "$(dirname "$DEST")" "$(basename "$DEST")"
ok "Archive ready: $ARCHIVE ($(du -h "$ARCHIVE" | cut -f1))"

echo
ok "Backup complete."
echo "  • Folder : $DEST"
echo "  • Tarball: $ARCHIVE"
