#!/bin/bash
set -euo pipefail

remote_folder="${REMOTE_FOLDER:-/opt/www/virtual/SkinbaseNova}"
remote_server="${REMOTE_SERVER:-klevze@server3.klevze.si}"
remote_release_root="${REMOTE_RELEASE_ROOT:-${REMOTE_FOLDER:-/opt/www/virtual/SkinbaseNova}.releases}"
remote_shared_root="${REMOTE_SHARED_ROOT:-${REMOTE_RELEASE_ROOT:-/opt/www/virtual/SkinbaseNova.releases}/shared}"
php_bin="${PHP_BIN:-php}"
composer_bin="${COMPOSER_BIN:-composer}"
ssh_bin="${SSH_BIN:-ssh}"

dry_run=0
list_only=0
rollback_release_id=""
use_previous=0
skip_maintenance=0

usage() {
  cat <<'EOF'
Usage: bash scripts/rollback-production.sh [options]

Options:
  --previous           Switch to the previous retained release on the production server.
  --release-id ID      Switch to a specific retained release ID.
  --list               List retained releases and exit.
  --dry-run            Preview the release switch without changing the active release.
  --no-maintenance     Skip php artisan down/up during the switch.
  --help               Show this help.

Environment overrides:
  REMOTE_FOLDER, REMOTE_SERVER, REMOTE_RELEASE_ROOT, REMOTE_SHARED_ROOT, PHP_BIN, COMPOSER_BIN, SSH_BIN

Notes:
  - Rollback changes the active server release; it does not re-upload files from local.
  - Database migrations are not reversed automatically.
EOF
}

log_step() {
  printf '\n==> %s\n' "$1"
}

log_info() {
  printf '  -> %s\n' "$1"
}

die() {
  printf 'ERROR: %s\n' "$1" >&2
  exit 1
}

require_command() {
  local command_name="$1"
  local description="$2"

  command -v "$command_name" >/dev/null 2>&1 || die "$description is required but was not found in PATH ($command_name)."
}

sanitize_release_fragment() {
  local value="$1"

  value="${value//[^A-Za-z0-9._-]/-}"
  value="${value#-}"
  value="${value%-}"

  printf '%s' "$value"
}

while [[ $# -gt 0 ]]; do
  case "$1" in
    --previous)
      use_previous=1
      ;;
    --release-id)
      shift
      rollback_release_id="$(sanitize_release_fragment "${1:?Missing value for --release-id}")"
      ;;
    --release-id=*)
      rollback_release_id="$(sanitize_release_fragment "${1#*=}")"
      ;;
    --list)
      list_only=1
      ;;
    --dry-run)
      dry_run=1
      ;;
    --no-maintenance)
      skip_maintenance=1
      ;;
    --help|-h)
      usage
      exit 0
      ;;
    *)
      die "Unknown option: $1"
      ;;
  esac
  shift
done

require_command "$ssh_bin" "SSH"

if [[ "$list_only" -eq 0 && "$use_previous" -eq 0 && -z "$rollback_release_id" ]]; then
  die "Choose either --previous or --release-id, or use --list to inspect retained releases."
fi

if [[ "$use_previous" -eq 1 && -n "$rollback_release_id" ]]; then
  die "Use either --previous or --release-id, not both."
fi

if [[ "$list_only" -eq 1 ]]; then
  log_step "Retained releases on $remote_server"
  "$ssh_bin" "$remote_server" \
    REMOTE_RELEASE_ROOT="$(printf '%q' "$remote_release_root")" \
    'bash -s' <<'EOF'
set -euo pipefail

current_release="unknown"
if [[ -f "${REMOTE_RELEASE_ROOT}/current-release.txt" ]]; then
  current_release="$(cat "${REMOTE_RELEASE_ROOT}/current-release.txt")"
fi

printf 'Current release: %s\n' "$current_release"

if [[ ! -d "${REMOTE_RELEASE_ROOT}/releases" ]]; then
  printf 'No retained releases found in %s\n' "$REMOTE_RELEASE_ROOT"
  exit 0
fi

find "${REMOTE_RELEASE_ROOT}/releases" -mindepth 1 -maxdepth 1 -type d | sort -r | while read -r path; do
  release_id="$(basename "$path")"
  marker=" "
  if [[ "$release_id" == "$current_release" ]]; then
    marker="*"
  fi
  printf '%s %s\n' "$marker" "$release_id"
done
EOF
  exit 0
fi

log_step "Switching production release on $remote_server"
if [[ "$dry_run" -eq 1 ]]; then
  log_info "Dry-run mode enabled; remote current release will not be changed"
fi

"$ssh_bin" "$remote_server" \
  REMOTE_FOLDER="$(printf '%q' "$remote_folder")" \
  REMOTE_RELEASE_ROOT="$(printf '%q' "$remote_release_root")" \
  REMOTE_SHARED_ROOT="$(printf '%q' "$remote_shared_root")" \
  PHP_BIN="$(printf '%q' "$php_bin")" \
  COMPOSER_BIN="$(printf '%q' "$composer_bin")" \
  ROLLBACK_RELEASE_ID="$(printf '%q' "$rollback_release_id")" \
  USE_PREVIOUS="$use_previous" \
  DRY_RUN="$dry_run" \
  SKIP_MAINTENANCE="$skip_maintenance" \
  'bash -s' <<'EOF'
set -euo pipefail

current_link="${REMOTE_RELEASE_ROOT}/current"

log_step() {
  printf '\n==> %s\n' "$1"
}

log_info() {
  printf '  -> %s\n' "$1"
}

log_warn() {
  printf 'WARN: %s\n' "$1" >&2
}

die() {
  printf 'ERROR: %s\n' "$1" >&2
  exit 1
}

current_release_id() {
  if [[ -L "$current_link" ]]; then
    basename "$(readlink "$current_link")"
    return 0
  fi

  printf '%s\n' ''
}

resolve_target_release() {
  local current_release
  local target_release
  local -a releases=()

  [[ -d "${REMOTE_RELEASE_ROOT}/releases" ]] || die "No retained releases exist under ${REMOTE_RELEASE_ROOT}/releases"

  current_release="$(current_release_id)"

  if [[ "$USE_PREVIOUS" -eq 1 ]]; then
    mapfile -t releases < <(find "${REMOTE_RELEASE_ROOT}/releases" -mindepth 1 -maxdepth 1 -type d | sort -r)
    for release_path in "${releases[@]}"; do
      target_release="$(basename "$release_path")"
      if [[ "$target_release" != "$current_release" ]]; then
        printf '%s\n' "$target_release"
        return 0
      fi
    done

    die "No previous retained release is available."
  fi

  printf '%s\n' "$ROLLBACK_RELEASE_ID"
}

ensure_dir() {
  mkdir -p "$1"
}

ensure_php_runtime_dir() {
  local target_dir="$1"
  local -a privileged_cmd=()

  if command -v sudo >/dev/null 2>&1 && sudo -n true >/dev/null 2>&1; then
    privileged_cmd=(sudo -n)
  elif [[ "$(id -u)" -eq 0 ]]; then
    privileged_cmd=()
  fi

  if [[ ! -d "$target_dir" ]]; then
    if [[ ${#privileged_cmd[@]} -gt 0 ]]; then
      "${privileged_cmd[@]}" mkdir -p "$target_dir"
    else
      mkdir -p "$target_dir"
    fi
  fi

  if [[ ${#privileged_cmd[@]} -gt 0 || "$(id -u)" -eq 0 ]]; then
    "${privileged_cmd[@]}" chown -R skinbase:skinbase "$target_dir"
    "${privileged_cmd[@]}" chmod 770 "$target_dir"
    return
  fi

  chmod 770 "$target_dir" >/dev/null 2>&1 || true
}

link_shared_paths() {
  local target_release="$1"

  ensure_dir "$target_release/public" "$target_release/var"

  rm -f "$target_release/.env"
  ln -sfn "${REMOTE_SHARED_ROOT}/.env" "$target_release/.env"

  rm -rf "$target_release/storage"
  ln -sfn "${REMOTE_SHARED_ROOT}/storage" "$target_release/storage"

  rm -rf "$target_release/public/files"
  ln -sfn "${REMOTE_SHARED_ROOT}/public/files" "$target_release/public/files"

  rm -rf "$target_release/public/sitemaps"
  ln -sfn "${REMOTE_SHARED_ROOT}/public/sitemaps" "$target_release/public/sitemaps"

  rm -rf "$target_release/var/php-tmp"
  ln -sfn "${REMOTE_SHARED_ROOT}/var/php-tmp" "$target_release/var/php-tmp"

  rm -rf "$target_release/var/php-sessions"
  ln -sfn "${REMOTE_SHARED_ROOT}/var/php-sessions" "$target_release/var/php-sessions"
}

bring_app_up() {
  if [[ "$SKIP_MAINTENANCE" -eq 0 && -f "$REMOTE_FOLDER/artisan" ]]; then
    "$PHP_BIN" "$REMOTE_FOLDER/artisan" up >/dev/null 2>&1 || true
  fi
}

target_release="$(resolve_target_release)"
[[ -n "$target_release" ]] || die "Unable to resolve a target release."

target_release_path="${REMOTE_RELEASE_ROOT}/releases/${target_release}"
[[ -d "$target_release_path" ]] || die "Retained release not found: ${target_release_path}"

current_release="$(current_release_id)"
[[ -n "$current_release" ]] || die "No active current release is configured under ${current_link}"

if [[ "$target_release" == "$current_release" ]]; then
  die "Target release is already active: ${target_release}"
fi

ensure_php_runtime_dir "${REMOTE_SHARED_ROOT}/var/php-tmp"
ensure_php_runtime_dir "${REMOTE_SHARED_ROOT}/var/php-sessions"
link_shared_paths "$target_release_path"

[[ -f "${REMOTE_SHARED_ROOT}/.env" ]] || die "Shared production .env is missing at ${REMOTE_SHARED_ROOT}/.env"
[[ -f "${target_release_path}/artisan" ]] || die "Target release is missing artisan: ${target_release_path}/artisan"

log_info "Current release: ${current_release}"
log_info "Target release: ${target_release}"
log_info "Target path: ${target_release_path}"

if [[ "$DRY_RUN" -eq 1 ]]; then
  exit 0
fi

trap bring_app_up EXIT

if [[ "$SKIP_MAINTENANCE" -eq 0 && -f "$REMOTE_FOLDER/artisan" ]]; then
  log_step "Enabling maintenance mode"
  "$PHP_BIN" "$REMOTE_FOLDER/artisan" down --retry=60 || true
fi

if [[ ! -f "${target_release_path}/vendor/autoload.php" ]]; then
  log_step "Installing Composer dependencies in target release"
  (
    cd "$target_release_path"
    "$COMPOSER_BIN" install --no-dev --prefer-dist --optimize-autoloader --no-interaction
  )
fi

log_step "Switching current release to ${target_release}"
ln -sfn "$target_release_path" "$current_link"
ln -sfn "$current_link" "$REMOTE_FOLDER"

cd "$REMOTE_FOLDER"

log_step "Refreshing caches"
"$PHP_BIN" artisan view:clear
"$PHP_BIN" artisan optimize:clear
"$PHP_BIN" artisan optimize
"$PHP_BIN" artisan view:cache

if [[ "$SKIP_MAINTENANCE" -eq 0 ]]; then
  log_step "Bringing application back online"
  "$PHP_BIN" artisan up
  trap - EXIT
fi

if ! "$PHP_BIN" artisan homepage:warm-guest-cache; then
  log_warn "Homepage guest cache warm failed during rollback."
fi

if ! "$PHP_BIN" artisan posts:warm-trending; then
  log_warn "Post trending cache warm failed during rollback."
fi

log_step "Restarting queue workers"
"$PHP_BIN" artisan queue:restart || true

cat > "${REMOTE_RELEASE_ROOT}/current-release.json" <<JSON
{
  "release_id": "${target_release}",
  "rolled_back_from_release_id": "${current_release}",
  "rolled_back_at": "$(date -u +%Y-%m-%dT%H:%M:%SZ)",
  "remote_folder": "${REMOTE_FOLDER}",
  "release_path": "${target_release_path}"
}
JSON

printf '%s\n' "${target_release}" > "${REMOTE_RELEASE_ROOT}/current-release.txt"

log_step "Release switch complete"
EOF
