#!/usr/bin/env bash
# Roll a release out to every client install, or to the ones you pick.
#
#   bash release.sh workflow-release.zip --all                 every client in the list
#   bash release.sh workflow-release.zip shreeji client2       just these
#   bash release.sh workflow-release.zip --channel beta        clients marked "beta" in the list
#   add --dry-run to see what would happen without changing anything
#
# Everything lives in ~/LemonLogicSoftware (LLS_HOME=... to change): clients.conf (the client list,
# see deploy/clients.example.conf), releases/ (every zip rolled out, newest 5 kept, for rollback),
# logs/ (one log per client per release) and the <Name>-WebApp folders of the clients.
# For each client: code is replaced (its .env, storage, uploads, backups and logo are kept), then
# deploy/deploy.sh backs up the database, installs packages, runs migrations and rebuilds caches.
# One client failing does not stop the others; a summary is printed at the end.
set -uo pipefail

ZIP="${1:-}"
shift || true
if [ -z "$ZIP" ] || [ ! -f "$ZIP" ]; then
  echo "Usage: bash release.sh <release.zip> (--all | --channel NAME | client ...) [--dry-run]"
  exit 1
fi
ZIP="$(cd "$(dirname "$ZIP")" && pwd)/$(basename "$ZIP")"

LLS_HOME="${LLS_HOME:-$HOME/LemonLogicSoftware}"
CLIENTS_FILE="${CLIENTS_FILE:-$LLS_HOME/clients.conf}"
if [ ! -f "$CLIENTS_FILE" ]; then
  echo "*** No client list at $CLIENTS_FILE. Copy deploy/clients.example.conf there and edit it."
  exit 1
fi

ALL=0; CHANNEL=""; DRY=0; PICK=()
while [ $# -gt 0 ]; do
  case "$1" in
    --all) ALL=1 ;;
    --channel) CHANNEL="${2:-}"; shift ;;
    --dry-run) DRY=1 ;;
    *) PICK+=("$1") ;;
  esac
  shift
done
if [ $ALL -eq 0 ] && [ -z "$CHANNEL" ] && [ ${#PICK[@]} -eq 0 ]; then
  echo "Say which clients: --all, --channel NAME, or their ids."
  exit 1
fi

if [ -z "${PHP:-}" ]; then
  if [ -x /opt/alt/php83/usr/bin/php ]; then PHP=/opt/alt/php83/usr/bin/php; else PHP=php; fi
fi
export PHP

# Unpack once into a staging folder.
STAMP="$(date +%Y%m%d-%H%M%S)"
STAGE="$LLS_HOME/.staging/$STAMP"
LOGS="$LLS_HOME/logs"
RELEASES="$LLS_HOME/releases"
mkdir -p "$STAGE" "$LOGS" "$RELEASES"
unzip -q -o "$ZIP" -d "$STAGE" || { echo "*** Could not unzip $ZIP"; exit 1; }
VERSION="$(cat "$STAGE/VERSION" 2>/dev/null || echo unknown)"
echo "==> Release $VERSION"

# Keep a copy of every release for rollback (newest 5).
KEEP="$RELEASES/workflow-$VERSION.zip"
if [ $DRY -eq 0 ] && [ "$ZIP" != "$KEEP" ]; then
  cp -f "$ZIP" "$KEEP"
  ls -1t "$RELEASES"/workflow-*.zip 2>/dev/null | tail -n +6 | xargs -r rm -f
fi

expand() { local p="$1"; echo "${p/#\~/$HOME}"; }

OK=(); FAILED=(); SKIPPED=()
while read -r id app web channel _; do
  [ -z "${id:-}" ] && continue
  case "$id" in \#*) continue ;; esac
  channel="${channel:-stable}"

  chosen=0
  [ $ALL -eq 1 ] && chosen=1
  [ -n "$CHANNEL" ] && [ "$channel" = "$CHANNEL" ] && chosen=1
  for p in "${PICK[@]:-}"; do [ "$p" = "$id" ] && chosen=1; done
  [ $chosen -eq 0 ] && continue

  app="$(expand "$app")"; web="$(expand "$web")"
  echo
  echo "==> $id  ($app)"
  if [ ! -f "$app/.env" ]; then
    echo "    *** no .env in $app – skipped (set it up with deploy/new-client.sh)"
    SKIPPED+=("$id"); continue
  fi
  if [ $DRY -eq 1 ]; then
    echo "    would update code to $VERSION and run deploy.sh (web folder $web)"
    OK+=("$id"); continue
  fi

  LOG="$LOGS/$STAMP-$id.log"
  {
    # Replace the code, keeping everything that belongs to this client.
    if command -v rsync >/dev/null 2>&1; then
      rsync -a --delete \
        --exclude='/.env' --exclude='/storage/' --exclude='/vendor/' --exclude='/backups/' \
        --exclude='/bootstrap/cache/' --exclude='/deploy/.web-root' --exclude='/public/brand/' \
        --exclude='/composer.phar' \
        "$STAGE/" "$app/"
    else
      cp -a "$STAGE/." "$app/"
    fi
    cd "$app" && WEB="$web" bash deploy/deploy.sh
  } >"$LOG" 2>&1
  if [ $? -eq 0 ]; then
    echo "    done ($LOG)"; OK+=("$id")
  else
    echo "    *** FAILED – see $LOG"; tail -n 15 "$LOG" | sed 's/^/    | /'
    FAILED+=("$id")
    # Leave maintenance mode so the client is not stuck on "be right back".
    (cd "$app" && $PHP artisan up >/dev/null 2>&1) || true
  fi
done < "$CLIENTS_FILE"

rm -rf "$STAGE"
echo
echo "==> Summary for $VERSION"
if [ $DRY -eq 1 ]; then echo "    would update (dry run, nothing changed): ${OK[*]:-none}"; else echo "    updated: ${OK[*]:-none}"; fi
[ ${#SKIPPED[@]} -gt 0 ] && echo "    skipped: ${SKIPPED[*]}"
[ ${#FAILED[@]} -gt 0 ] && { echo "    FAILED:  ${FAILED[*]}"; exit 1; }
exit 0
