#!/usr/bin/env bash
set -Eeuo pipefail

deploy_root=${1:?缺少部署根目录}
release_dir=${2:?缺少 release 目录}
app_name=${3:?缺少 PM2 应用名}
pm2_bin=${4:?缺少 PM2 可执行文件路径}
pm2_home=${5:?缺少 PM2_HOME}
pm2_use_sudo=${6:-1}
healthcheck_url=${7:-http://127.0.0.1:${CMS_PORT:-8790}/}
pipeline_id=${8:?缺少 CI pipeline ID}

case "$deploy_root" in
  /*) ;;
  *) echo "部署根目录必须是绝对路径" >&2; exit 2 ;;
esac
case "$release_dir" in
  "$deploy_root"/releases/*) ;;
  *) echo "release 目录必须位于 $deploy_root/releases/ 下" >&2; exit 2 ;;
esac
case "$app_name" in
  *[!A-Za-z0-9_.-]*|'') echo "PM2 应用名包含不安全字符" >&2; exit 2 ;;
esac
case "$pm2_bin" in
  /*) ;;
  *) echo "PM2 可执行文件必须是绝对路径" >&2; exit 2 ;;
esac
case "$pm2_home" in
  /*) ;;
  *) echo "PM2_HOME 必须是绝对路径" >&2; exit 2 ;;
esac
case "$pm2_use_sudo" in
  0|1) ;;
  *) echo "PM2_USE_SUDO 只能是 0 或 1" >&2; exit 2 ;;
esac
case "$pipeline_id" in
  *[!0-9]*|'') echo "CI pipeline ID 必须是数字" >&2; exit 2 ;;
esac

shared_dir="$deploy_root/shared"
shared_env="$shared_dir/.env"
current_link="$deploy_root/current"

if [ "${YINZHUANG_SITE_LOCK_HELD:-}" != "1" ]; then
  echo "部署必须通过 run-with-site-lock.mjs 执行" >&2
  exit 2
fi
if [ ! -d "$release_dir" ] || [ ! -f "$release_dir/package-lock.json" ]; then
  echo "release 目录不完整：$release_dir" >&2
  exit 2
fi
if [ ! -f "$shared_env" ]; then
  echo "缺少生产环境配置：$shared_env" >&2
  exit 2
fi
case "${CMS_DATA_DIR:-}" in
  "$shared_dir"/*) ;;
  *) echo "CMS_DATA_DIR 必须位于 $shared_dir/ 下" >&2; exit 2 ;;
esac
case "${CMS_BUILD_LOCK:-}" in
  "$shared_dir"/*) ;;
  *) echo "CMS_BUILD_LOCK 必须位于 $shared_dir/ 下" >&2; exit 2 ;;
esac
if [ -e "$current_link" ] && [ ! -L "$current_link" ]; then
  echo "$current_link 必须是软链接，拒绝覆盖真实目录" >&2
  exit 2
fi
command -v curl >/dev/null
test -x "$pm2_bin"
if [ ! -f "$release_dir/.pipeline-id" ] || [ "$(cat "$release_dir/.pipeline-id")" != "$pipeline_id" ]; then
  echo "release 的 pipeline 标识不匹配" >&2
  exit 2
fi

pm2_command() {
  if [ "$pm2_use_sudo" = "1" ]; then
    sudo -n env PM2_HOME="$pm2_home" "$pm2_bin" "$@"
  else
    env PM2_HOME="$pm2_home" "$pm2_bin" "$@"
  fi
}

# 只删除并重建 APP_NAME 对应的一个进程；不执行 pm2 kill、delete all、restart all 或 pm2 save。
start_current_app() {
  if pm2_command describe "$app_name" >/dev/null 2>&1; then
    pm2_command delete "$app_name"
  fi
  pm2_command start "$current_link/server.mjs" \
    --name "$app_name" \
    --cwd "$current_link" \
    --node-args="--env-file=$shared_env"
}

previous_target=""
if [ -L "$current_link" ]; then
  previous_target=$(readlink -f "$current_link")
fi

# GitLab 11.7 没有 resource_group。若较新的 pipeline 已上线，拒绝旧 pipeline 覆盖它。
if [ -n "$previous_target" ] && [ -f "$previous_target/.pipeline-id" ]; then
  current_pipeline_id=$(cat "$previous_target/.pipeline-id")
  case "$current_pipeline_id" in
    *[!0-9]*|'') echo "当前版本的 pipeline 标识不合法" >&2; exit 2 ;;
  esac
  if [ "$current_pipeline_id" -gt "$pipeline_id" ]; then
    echo "较新的 pipeline $current_pipeline_id 已部署，拒绝旧 pipeline $pipeline_id 覆盖" >&2
    exit 3
  fi
  if [ "$current_pipeline_id" -eq "$pipeline_id" ] && [ "$previous_target" = "$release_dir" ]; then
    echo "该 pipeline 已经部署，无需重复切换"
    exit 0
  fi
fi
switched=0

rollback() {
  exit_code=$?
  trap - ERR
  if [ "$switched" = "1" ]; then
    echo "新版本部署失败，正在恢复上一版本……" >&2
    rollback_link="$deploy_root/.current-rollback-$$"
    if [ -n "$previous_target" ] && [ -d "$previous_target" ]; then
      ln -s "$previous_target" "$rollback_link"
      mv -Tf "$rollback_link" "$current_link"
      start_current_app || true
    else
      if pm2_command describe "$app_name" >/dev/null 2>&1; then
        pm2_command delete "$app_name" || true
      fi
      echo "没有可恢复的上一版本，已停止本项目进程" >&2
    fi
  fi
  exit "$exit_code"
}
trap rollback ERR

cd "$release_dir"
echo "[deploy] 使用共享用户内容执行最终构建"
npm run build:inside-lock

next_link="$deploy_root/.current-next-$$"
ln -s "$release_dir" "$next_link"
mv -Tf "$next_link" "$current_link"
switched=1

start_current_app

healthy=0
for attempt in $(seq 1 30); do
  if curl --fail --silent --show-error --max-time 3 "$healthcheck_url" >/dev/null; then
    healthy=1
    break
  fi
  echo "等待服务启动... ($attempt/30)"
  sleep 1
done

if [ "$healthy" != "1" ]; then
  echo "健康检查失败：$healthcheck_url" >&2
  pm2_command logs "$app_name" --lines 30 --nostream || true
  false
fi

trap - ERR
previous_marker_tmp="$shared_dir/.previous-release-$$"
printf '%s\n' "$previous_target" > "$previous_marker_tmp"
mv -f "$previous_marker_tmp" "$shared_dir/previous-release"
printf '%s\n' "$pipeline_id" > "$release_dir/.deploy-success"
echo "部署成功：$release_dir"
