#!/usr/bin/env bash
# ============================================================
# Push EVERY project under the cPanel home to its own PRIVATE
# GitHub repo — CODE ONLY (no secrets, deps, media, or DB dumps).
#
# For each site dir it stages a code-only snapshot (using a scratch
# git dir so the project's own .git is never touched), removes any
# file matching a secret pattern, skips empties, creates a private
# repo, and pushes. Produces a CSV report.
#
#   ops/push-all-projects.sh --dry-run          # show what WOULD happen
#   ops/push-all-projects.sh                     # do it
#   ops/push-all-projects.sh --limit 5           # first 5 only
#   ops/push-all-projects.sh --prefix hva-       # repo name prefix
#
# Run as the account owner (ashraffarid2010) — it owns the files and
# holds the gh credentials. Requires: gh (authenticated), git.
# ============================================================
set -uo pipefail

HOME_DIR="/home/ashraffarid2010"
SCRATCH="/home/ashraffarid2010/.project-push"
REPORT="$HOME_DIR/halavoice.store/logs/push-all-projects.csv"
DRY=0 ; LIMIT=0 ; PREFIX=""
while [ $# -gt 0 ]; do
  case "$1" in
    --dry-run) DRY=1 ;;
    --limit) LIMIT="$2"; shift ;;
    --prefix) PREFIX="$2"; shift ;;
    *) echo "unknown arg: $1" >&2; exit 2 ;;
  esac; shift
done

log(){ echo -e "\033[1;36m[push]\033[0m $*"; }
ok(){ echo -e "\033[1;32m[ ok ]\033[0m $*"; }
warn(){ echo -e "\033[1;33m[skip]\033[0m $*"; }

command -v gh >/dev/null || { echo "gh not found"; exit 1; }
mkdir -p "$SCRATCH" "$(dirname "$REPORT")"
GH_USER="$(gh api user -q .login 2>/dev/null || echo NewBabyFace)"

# ---- exclude list: secrets + deps + media + heavy ----
EXCL="$SCRATCH/excludes"
cat > "$EXCL" <<'EOF'
# secrets
.env
.env.*
*.env
wp-config.php
config.php
configuration.php
settings.php
local_settings.py
*.pem
*.key
id_rsa*
.htpasswd*
*credential*.json
*service-account*.json
.npmrc
.pgpass
auth.json
# vcs / deps
.git/
node_modules/
vendor/
.venv/
venv/
bower_components/
# media / heavy / data
uploads/
wp-content/uploads/
storage/app/public/
*.mp4
*.mov
*.zip
*.gz
*.tar
*.rar
*.7z
*.iso
*.sql
*.dump
*.log
*.pt
*.onnx
*.bin
cache/
.cache/
tmp/
EOF

# strong live-secret content patterns (files matching are dropped)
SECRET_RE='sk-(proj-)?[A-Za-z0-9_-]{30,}|AKIA[0-9A-Z]{16}|-----BEGIN (RSA|EC|OPENSSH|PRIVATE) |AIza[0-9A-Za-z_-]{35}|xox[baprs]-[A-Za-z0-9-]{10,}|postgres(ql)?://[^:@/]+:[^@/ ]{4,}@|mysql://[^:@/]+:[^@/ ]{4,}@|DB_PASS(WORD)?["'"'"' ]*[:=][^ ]{6,}|SECRET_KEY["'"'"' ]*[:=][^ ]{12,}'

echo "project,repo,files,size_kb,status,reason,secrets_dropped" > "$REPORT"

sanitize(){ echo "$1" | tr '[:upper:]' '[:lower:]' | sed -E 's/[^a-z0-9]+/-/g; s/^-+//; s/-+$//' | cut -c1-90; }

mapfile -t PROJECTS < <(cd "$HOME_DIR" && ls -d */ 2>/dev/null | sed 's#/##' | grep -viE '^(mail|etc|ssl|logs|access-logs|tmp|\.trash|public_ftp|\.cpanel|\.cphorde|\.htpasswds|perl5|cache|\.cache|cpanelbackups|backups|.*-backups|\.spamassassin|\.razor|\.subaccounts|\.well-known|var|\.cl\.selector|\.pki|\.gnupg|\.ssh|\.config|\.npm|\.pm2|node_modules|www|\.cpaddons|\.project-push|\.razor|\.spamassassin)$')

log "${#PROJECTS[@]} candidate projects. gh user: $GH_USER. dry-run: $DRY"
n=0; pushed=0; skipped=0
for proj in "${PROJECTS[@]}"; do
  [ "$LIMIT" -gt 0 ] && [ "$n" -ge "$LIMIT" ] && break
  n=$((n+1))
  src="$HOME_DIR/$proj"
  [ -d "$src" ] || continue
  repo="$PREFIX$(sanitize "$proj")"
  gd="$SCRATCH/$repo.git"
  rm -rf "$gd"
  GIT_DIR="$gd" GIT_WORK_TREE="$src" git init -q 2>/dev/null
  GIT_DIR="$gd" git config core.excludesFile "$EXCL"
  GIT_DIR="$gd" git config core.autocrlf false
  GIT_DIR="$gd" GIT_WORK_TREE="$src" git add -A 2>/dev/null

  # drop files > 95MB (GitHub hard limit 100MB)
  GIT_DIR="$gd" GIT_WORK_TREE="$src" git diff --cached --name-only 2>/dev/null | while IFS= read -r f; do
    [ -f "$src/$f" ] && [ "$(stat -c%s "$src/$f" 2>/dev/null || echo 0)" -gt 99000000 ] && GIT_DIR="$gd" GIT_WORK_TREE="$src" git rm -q --cached --ignore-unmatch "$f" >/dev/null 2>&1
  done

  # secret content gate: drop any staged text-ish file that contains a live secret
  dropped=0
  while IFS= read -r f; do
    case "$f" in
      *.php|*.js|*.ts|*.py|*.json|*.yml|*.yaml|*.ini|*.conf|*.cfg|*.sh|*.txt|*.xml|*.rb|*.go|*.java) ;;
      *) continue ;;
    esac
    [ -f "$src/$f" ] || continue
    [ "$(stat -c%s "$src/$f" 2>/dev/null || echo 0)" -gt 1048576 ] && continue
    if grep -aEl "$SECRET_RE" "$src/$f" >/dev/null 2>&1; then
      GIT_DIR="$gd" GIT_WORK_TREE="$src" git rm -q --cached --ignore-unmatch "$f" >/dev/null 2>&1
      dropped=$((dropped+1))
    fi
  done < <(GIT_DIR="$gd" GIT_WORK_TREE="$src" git diff --cached --name-only 2>/dev/null)

  files=$(GIT_DIR="$gd" GIT_WORK_TREE="$src" git diff --cached --name-only 2>/dev/null | wc -l)
  if [ "$files" -lt 1 ]; then
    warn "$proj — empty/parked (no code after excludes)"
    echo "$proj,$repo,0,0,skipped,empty,$dropped" >> "$REPORT"; skipped=$((skipped+1)); rm -rf "$gd"; continue
  fi
  size_kb=$(GIT_DIR="$gd" GIT_WORK_TREE="$src" git diff --cached --name-only -z 2>/dev/null | (cd "$src" && xargs -0 du -ck 2>/dev/null | tail -1 | cut -f1))

  if [ "$DRY" = 1 ]; then
    ok "$proj → $repo  ($files files, ${size_kb}KB, $dropped secret-files dropped)  [dry-run]"
    echo "$proj,$repo,$files,${size_kb:-0},dryrun,ok,$dropped" >> "$REPORT"; rm -rf "$gd"; continue
  fi

  GIT_DIR="$gd" GIT_WORK_TREE="$src" git -c user.name="$GH_USER" -c user.email="ops@halavoice.store" commit -q -m "snapshot: $proj ($(date -u +%FT%TZ))" 2>/dev/null
  # create private repo (idempotent) + push
  gh repo create "$GH_USER/$repo" --private >/dev/null 2>&1 || true
  GIT_DIR="$gd" git remote add origin "https://github.com/$GH_USER/$repo.git" 2>/dev/null || \
    GIT_DIR="$gd" git remote set-url origin "https://github.com/$GH_USER/$repo.git"
  if GIT_DIR="$gd" GIT_WORK_TREE="$src" git push -q -u origin HEAD:main 2>/dev/null; then
    ok "$proj → $GH_USER/$repo  ($files files, ${size_kb}KB, $dropped dropped)"
    echo "$proj,$repo,$files,${size_kb:-0},pushed,ok,$dropped" >> "$REPORT"; pushed=$((pushed+1))
  else
    warn "$proj → push failed for $repo"
    echo "$proj,$repo,$files,${size_kb:-0},failed,push_error,$dropped" >> "$REPORT"; skipped=$((skipped+1))
  fi
  rm -rf "$gd"
done

echo
ok "Done. pushed=$pushed  skipped=$skipped  report: $REPORT"
