#!/usr/bin/env bash
# 一键部署：本地 dev → Studio(100) → 105
#
# 用法：
#   ./rsync_to_server.sh                # 全量部署（100 先，105 后）
#   ./rsync_to_server.sh --only-100     # 只更新 Studio
#   ./rsync_to_server.sh --only-105     # 只触发 105 同步（Studio 代码已是最新）
#   ./rsync_to_server.sh --skip-build   # 跳过 vite build（纯后端改动时加速）
#   ./rsync_to_server.sh --no-restart   # 只同步代码，不重启服务
#   ./rsync_to_server.sh --dry-run      # 预览将同步哪些文件，不做任何修改

set -euo pipefail

# ── 配置 ──────────────────────────────────────────────────────────────────────
STUDIO_HOST="${STUDIO_HOST:-100.99.38.66}"
STUDIO_REMOTE="${STUDIO_REMOTE:-/Users/john/Project/Memind}"
STUDIO_NODE_BIN="${STUDIO_NODE_BIN:-/opt/homebrew/opt/node@24/bin}"
STUDIO_LAUNCHAGENT_PORTAL="cn.tkmind.memind-portal"
STUDIO_LAUNCHAGENT_PLAZA="cn.tkmind.plaza"
STUDIO_HEALTH="http://127.0.0.1:8081/api/status"
STUDIO_PLAZA_HEALTH="http://127.0.0.1:3001/plaza"
STUDIO_LOG_PORTAL="$HOME/Library/Logs/memind-portal.log"
STUDIO_LOG_PLAZA="$HOME/Library/Logs/memind-plaza.log"

LOCAL_ROOT="$(cd "$(dirname "$0")" && pwd)"
EXCLUDE_STUDIO="${LOCAL_ROOT}/scripts/.rsync-exclude-studio"

# ── 参数解析 ──────────────────────────────────────────────────────────────────
ONLY_100=0
ONLY_105=0
SKIP_BUILD=0
NO_RESTART=0
DRY_RUN=0
AUTO_YES=0

for arg in "$@"; do
  case "$arg" in
    --only-100)   ONLY_100=1 ;;
    --only-105)   ONLY_105=1 ;;
    --skip-build) SKIP_BUILD=1 ;;
    --no-restart) NO_RESTART=1 ;;
    --dry-run)    DRY_RUN=1 ;;
    --yes|-y)     AUTO_YES=1 ;;
    -h|--help)
      sed -n '2,9p' "$0"
      exit 0
      ;;
    *)
      echo "未知参数: $arg" >&2
      echo "用法: $0 [--only-100] [--only-105] [--skip-build] [--no-restart] [--dry-run]" >&2
      exit 1
      ;;
  esac
done

if [[ "${ONLY_100}" -eq 1 && "${ONLY_105}" -eq 1 ]]; then
  echo "错误: --only-100 与 --only-105 不能同时使用" >&2; exit 1
fi

# ── 工具函数 ──────────────────────────────────────────────────────────────────
RED='\033[0;31m'; YELLOW='\033[1;33m'; GREEN='\033[0;32m'; BOLD='\033[1m'; NC='\033[0m'

hr()   { echo "──────────────────────────────────────────────────────"; }
ok()   { echo -e "  ${GREEN}✓${NC} $*"; }
warn() { echo -e "  ${YELLOW}⚠${NC}  $*"; }
fail() { echo -e "  ${RED}✗${NC}  $*" >&2; }
die()  { fail "$*"; hr; exit 1; }

ssh_studio() { ssh -o BatchMode=yes -o ConnectTimeout=15 "${STUDIO_HOST}" "$@"; }
ssh_105()    { ssh_studio "ssh -o BatchMode=yes -o ConnectTimeout=15 ssh105 '$*'" 2>/dev/null; }

# ══════════════════════════════════════════════════════════════════════════════
# ① 本地 Pre-flight
# ══════════════════════════════════════════════════════════════════════════════
preflight_local() {
  echo ""
  echo -e "${BOLD}[Pre-flight] 本地检查${NC}"

  # exclude 文件
  [[ -f "${EXCLUDE_STUDIO}" ]] \
    && ok "exclude 文件存在: scripts/.rsync-exclude-studio" \
    || die "缺少 ${EXCLUDE_STUDIO}，无法继续"

  # 关键源码文件
  local missing=()
  for f in server.mjs user-auth.mjs user-publish.mjs user-space.mjs package.json; do
    [[ -f "${LOCAL_ROOT}/${f}" ]] || missing+=("${f}")
  done
  if [[ ${#missing[@]} -gt 0 ]]; then
    die "本地缺少关键文件: ${missing[*]}"
  fi
  ok "关键源码文件存在"

  # WORKSPACE_MAINTENANCE patch —— 必须存在，否则 105 下次重启会卡死
  if ! grep -q 'WORKSPACE_MAINTENANCE_ENABLED' "${LOCAL_ROOT}/server.mjs"; then
    die "server.mjs 缺少 WORKSPACE_MAINTENANCE_ENABLED patch！\n    此 patch 防止 105 在 rclone 挂载上启动时卡死。\n    请合并 Studio 生产版的 server.mjs patch 后再部署。"
  fi
  ok "server.mjs 含 WORKSPACE_MAINTENANCE patch"

  # 本地 .env 不能被意外同步（确认在 exclude 里）
  if grep -q '^\.env$' "${EXCLUDE_STUDIO}"; then
    ok ".env 已在 exclude 列表，不会被同步到 Studio"
  else
    die "exclude 文件未排除 .env！请检查 scripts/.rsync-exclude-studio"
  fi

  # MindSpace 已被排除
  if grep -q '^MindSpace/' "${EXCLUDE_STUDIO}"; then
    ok "MindSpace/ 已在 exclude 列表"
  else
    die "exclude 文件未排除 MindSpace/！"
  fi
}

# ══════════════════════════════════════════════════════════════════════════════
# ② 远端 Pre-flight（Studio）
# ══════════════════════════════════════════════════════════════════════════════
preflight_studio() {
  echo ""
  echo -e "${BOLD}[Pre-flight] Studio(${STUDIO_HOST}) 检查${NC}"

  # SSH 连通性
  if ! ssh_studio "echo ok" &>/dev/null; then
    die "无法 SSH 到 Studio (${STUDIO_HOST})"
  fi
  ok "SSH 连通"

  # .env 存在且非空
  local env_size
  env_size="$(ssh_studio "stat -f%z '${STUDIO_REMOTE}/.env' 2>/dev/null || echo 0")"
  if [[ "${env_size}" -lt 10 ]]; then
    die "Studio .env 不存在或为空！（size=${env_size}）"
  fi
  ok ".env 存在（${env_size} bytes）"

  # MindSpace 有内容
  local ms_count
  ms_count="$(ssh_studio "ls '${STUDIO_REMOTE}/MindSpace' | wc -l | tr -d ' '")"
  if [[ "${ms_count}" -lt 1 ]]; then
    die "Studio MindSpace/ 为空！部署前请确认生产数据正常。"
  fi
  ok "MindSpace/ 有 ${ms_count} 个条目"

  # data/ 目录存在
  if ! ssh_studio "test -d '${STUDIO_REMOTE}/data'"; then
    die "Studio data/ 目录不存在！"
  fi
  ok "data/ 目录存在"

  # 记录 .env 快照（大小 + 修改时间），用于 post-rsync 验证
  STUDIO_ENV_STAT="$(ssh_studio "stat -f'%z %m' '${STUDIO_REMOTE}/.env'")"
  STUDIO_MS_COUNT="${ms_count}"
}

# ══════════════════════════════════════════════════════════════════════════════
# ③ Dry-run 预览
# ══════════════════════════════════════════════════════════════════════════════
show_dry_run() {
  echo ""
  echo -e "${BOLD}[预览] 将同步以下文件变更到 Studio${NC}"
  rsync -az --delete --dry-run --itemize-changes \
    --exclude-from="${EXCLUDE_STUDIO}" \
    "${LOCAL_ROOT}/" \
    "${STUDIO_HOST}:${STUDIO_REMOTE}/" \
    2>/dev/null \
    | grep -v '^\.' \
    | head -80 \
    || true
  echo ""

  if [[ "${DRY_RUN}" -eq 1 ]]; then
    echo "（--dry-run 模式，未做任何实际修改）"
    return 0
  fi

  if [[ "${AUTO_YES}" -eq 1 ]]; then
    ok "已自动确认（--yes）"
    return 0
  fi

  # 交互确认（直接读 /dev/tty，不受 stdin pipe 影响）
  local confirm
  read -r -p "  确认同步？[y/N] " confirm </dev/tty
  if [[ ! "${confirm}" =~ ^[Yy]$ ]]; then
    echo "已取消。"
    exit 0
  fi
}

# ══════════════════════════════════════════════════════════════════════════════
# ④ Post-rsync 安全校验
# ══════════════════════════════════════════════════════════════════════════════
verify_studio_after_rsync() {
  echo ""
  echo -e "${BOLD}[校验] rsync 后 Studio 生产数据完整性${NC}"

  # .env 未被覆盖（大小+修改时间应与 rsync 前完全一致）
  local env_stat_now
  env_stat_now="$(ssh_studio "stat -f'%z %m' '${STUDIO_REMOTE}/.env'")"
  if [[ "${env_stat_now}" != "${STUDIO_ENV_STAT}" ]]; then
    die ".env 在 rsync 后发生变化！（before: ${STUDIO_ENV_STAT} after: ${env_stat_now}）\n    立即检查 Studio .env 是否被覆盖！"
  fi
  ok ".env 未变动（${env_stat_now}）"

  # MindSpace 条目数未减少
  local ms_now
  ms_now="$(ssh_studio "ls '${STUDIO_REMOTE}/MindSpace' | wc -l | tr -d ' '")"
  if [[ "${ms_now}" -lt "${STUDIO_MS_COUNT}" ]]; then
    die "MindSpace/ 条目从 ${STUDIO_MS_COUNT} 减少到 ${ms_now}！rsync 可能误删了用户数据！"
  fi
  ok "MindSpace/ 条目数正常（${ms_now}）"

  # data/ 仍存在
  if ! ssh_studio "test -d '${STUDIO_REMOTE}/data'"; then
    die "data/ 目录在 rsync 后消失！"
  fi
  ok "data/ 目录存在"

  # server.mjs patch 仍在（rsync 不能丢失 patch）
  local patch_count
  patch_count="$(ssh_studio "grep -c 'WORKSPACE_MAINTENANCE_ENABLED' '${STUDIO_REMOTE}/server.mjs' || echo 0")"
  if [[ "${patch_count}" -lt 1 ]]; then
    die "Studio server.mjs 的 WORKSPACE_MAINTENANCE patch 丢失！\n    这会导致 105 下次重启时卡死，请立即还原！"
  fi
  ok "server.mjs WORKSPACE_MAINTENANCE patch 存在（${patch_count} 处）"

  # 关键文件 md5 一致
  echo ""
  echo -e "${BOLD}[校验] 关键文件 md5${NC}"
  local all_ok=1
  for f in server.mjs user-publish.mjs user-space.mjs user-auth.mjs mindspace-sandbox-mcp.mjs capabilities.mjs package.json; do
    [[ ! -f "${LOCAL_ROOT}/${f}" ]] && continue
    local local_sum remote_sum
    local_sum="$(md5 -q "${LOCAL_ROOT}/${f}" 2>/dev/null || md5sum "${LOCAL_ROOT}/${f}" | awk '{print $1}')"
    remote_sum="$(ssh_studio "openssl md5 '${STUDIO_REMOTE}/${f}' 2>/dev/null | awk '{print \$2}'")"
    if [[ "${local_sum}" == "${remote_sum}" ]]; then
      ok "${f}"
    else
      fail "${f} md5 不一致（local=${local_sum} remote=${remote_sum}）"
      all_ok=0
    fi
  done
  [[ "${all_ok}" -eq 1 ]] || die "md5 校验失败，请检查文件同步是否正常"
}

# ══════════════════════════════════════════════════════════════════════════════
# ⑤ Studio 重启并健康检查（Portal → Plaza 顺序）
# ══════════════════════════════════════════════════════════════════════════════
wait_studio_healthy() {
  local STUDIO_UID
  STUDIO_UID="$(ssh_studio "id -u")"

  # ── Portal ──────────────────────────────────────────────────────────────────
  echo ""
  echo -e "${BOLD}[重启] Studio portal (cn.tkmind.memind-portal)${NC}"
  ssh_studio "launchctl kickstart -k gui/${STUDIO_UID}/${STUDIO_LAUNCHAGENT_PORTAL}" >/dev/null

  echo -n "  等待 portal :8081..."
  local ok_flag=0
  for i in $(seq 1 20); do
    sleep 2
    if ssh_studio "curl -sf '${STUDIO_HEALTH}'" &>/dev/null; then
      echo " ✓ (${i}×2s = $((i*2))s)"
      ok_flag=1
      break
    fi
    printf "."
  done
  if [[ "${ok_flag}" -eq 0 ]]; then
    echo ""
    fail "portal 40s 内未就绪"
    echo "  最近日志："
    ssh_studio "tail -30 '${STUDIO_LOG_PORTAL}' 2>/dev/null" | sed 's/^/    /'
    die "Studio portal 启动失败，已中止，不会继续重启 Plaza 或同步 105"
  fi

  # 日志扫描
  local portal_errors
  portal_errors="$(ssh_studio "tail -60 '${STUDIO_LOG_PORTAL}' 2>/dev/null \
    | grep -iE 'error|exception|uncaught|fatal' || true")"
  if [[ -n "${portal_errors}" ]]; then
    warn "portal 启动日志含警告/错误（请人工确认）："
    echo "${portal_errors}" | head -8 | sed 's/^/    /'
  else
    ok "portal 启动日志无 ERROR"
  fi

  # ── Plaza ───────────────────────────────────────────────────────────────────
  # Plaza 依赖 portal 先就绪（run-plaza-prod.sh 内有等待逻辑），所以 portal 健康后才重启
  echo ""
  echo -e "${BOLD}[重启] Studio plaza (cn.tkmind.plaza)${NC}"
  ssh_studio "launchctl kickstart -k gui/${STUDIO_UID}/${STUDIO_LAUNCHAGENT_PLAZA}" >/dev/null

  echo -n "  等待 plaza :3001..."
  ok_flag=0
  for i in $(seq 1 30); do
    sleep 3
    if ssh_studio "curl -sf -o /dev/null '${STUDIO_PLAZA_HEALTH}'" &>/dev/null; then
      echo " ✓ (${i}×3s = $((i*3))s)"
      ok_flag=1
      break
    fi
    printf "."
  done
  if [[ "${ok_flag}" -eq 0 ]]; then
    echo ""
    warn "plaza 90s 内未就绪（非致命，portal 和 105 仍可用）"
    echo "  最近日志："
    ssh_studio "tail -20 '${STUDIO_LOG_PLAZA}' 2>/dev/null" | sed 's/^/    /'
  else
    local plaza_errors
    plaza_errors="$(ssh_studio "tail -40 '${STUDIO_LOG_PLAZA}' 2>/dev/null \
      | grep -iE 'error|exception|uncaught|fatal' || true")"
    if [[ -n "${plaza_errors}" ]]; then
      warn "plaza 启动日志含警告/错误："
      echo "${plaza_errors}" | head -8 | sed 's/^/    /'
    else
      ok "plaza 启动日志无 ERROR"
    fi
  fi
}

# ══════════════════════════════════════════════════════════════════════════════
# ⑥ 105 Post-sync 额外校验
# ══════════════════════════════════════════════════════════════════════════════
verify_105_after_sync() {
  echo ""
  echo -e "${BOLD}[校验] 105 部署后检查${NC}"

  # goose-h5 正在运行
  local svc_status
  svc_status="$(ssh_studio "ssh ssh105 'systemctl is-active goose-h5 2>/dev/null'" 2>/dev/null || echo "unknown")"
  if [[ "${svc_status}" == "active" ]]; then
    ok "105 goose-h5 状态: active"
  else
    die "105 goose-h5 不是 active 状态（${svc_status}）"
  fi

  # 105 .env 关键开关仍然正确
  local maintenance_val
  maintenance_val="$(ssh_studio "ssh ssh105 'grep MEMIND_WORKSPACE_MAINTENANCE /root/tkmind_go/ui/h5/.env 2>/dev/null'" 2>/dev/null || echo "")"
  if echo "${maintenance_val}" | grep -q 'MEMIND_WORKSPACE_MAINTENANCE=0'; then
    ok "105 .env: MEMIND_WORKSPACE_MAINTENANCE=0 ✓"
  else
    die "105 .env 缺少或错误设置 MEMIND_WORKSPACE_MAINTENANCE！\n    当前值: ${maintenance_val}\n    必须为 0，否则 105 重启会卡死！"
  fi

  # 105 server.mjs patch 存在
  local patch_count_105
  patch_count_105="$(ssh_studio "ssh ssh105 'grep -c WORKSPACE_MAINTENANCE_ENABLED /root/tkmind_go/ui/h5/server.mjs 2>/dev/null || echo 0'" 2>/dev/null || echo "0")"
  if [[ "${patch_count_105}" -ge 1 ]]; then
    ok "105 server.mjs WORKSPACE_MAINTENANCE patch 存在"
  else
    die "105 server.mjs 缺少 WORKSPACE_MAINTENANCE patch！\n    105 重启时将对 rclone 挂载做 fs.watch，导致卡死！"
  fi

  # 105 MindSpace 挂载有内容
  local ms_105
  ms_105="$(ssh_studio "ssh ssh105 'ls /mnt/memind-shared/MindSpace 2>/dev/null | wc -l | tr -d \" \"'" 2>/dev/null || echo "0")"
  if [[ "${ms_105}" -gt 0 ]]; then
    ok "105 MindSpace 挂载正常（${ms_105} 个条目）"
  else
    warn "105 /mnt/memind-shared/MindSpace 为空，请确认 rclone 挂载是否正常"
  fi

  # 105 goose-h5 健康检查
  if ssh_studio "ssh ssh105 'curl -sf http://127.0.0.1:8080/api/status'" &>/dev/null; then
    ok "105 goose-h5 :8080/api/status 健康"
  else
    die "105 goose-h5 健康检查失败"
  fi

  # 105 goose-plaza-web（可选服务，不在 sync-to-105.sh 里重启，检查状态即可）
  local plaza_105_status
  plaza_105_status="$(ssh_studio "ssh ssh105 'systemctl is-active goose-plaza-web 2>/dev/null || echo inactive'" 2>/dev/null || echo "unknown")"
  if [[ "${plaza_105_status}" == "active" ]]; then
    ok "105 goose-plaza-web 状态: active"
  else
    warn "105 goose-plaza-web 不是 active（${plaza_105_status}）"
    warn "如有 plaza 代码变更，需手动重启: ssh ssh105 systemctl restart goose-plaza-web"
  fi
}

# ══════════════════════════════════════════════════════════════════════════════
# Banner
# ══════════════════════════════════════════════════════════════════════════════
hr
echo -e " ${BOLD}Memind 一键部署${NC}  $(date '+%Y-%m-%d %H:%M:%S')"
[[ "${ONLY_105}" -eq 0 ]] && echo " 本地   → Studio: ${STUDIO_HOST}:${STUDIO_REMOTE}"
[[ "${ONLY_100}" -eq 0 ]] && echo " Studio → 105:    经 sync-to-105.sh 触发"
[[ "${SKIP_BUILD}" -eq 1 ]] && echo " 模式: --skip-build（跳过 vite build）"
[[ "${NO_RESTART}" -eq 1 ]] && echo " 模式: --no-restart（只同步代码）"
[[ "${DRY_RUN}" -eq 1 ]]    && echo " 模式: --dry-run（预览，不做修改）"
hr

# ══════════════════════════════════════════════════════════════════════════════
# 主流程
# ══════════════════════════════════════════════════════════════════════════════
if [[ "${ONLY_105}" -eq 0 ]]; then
  preflight_local
  preflight_studio
  show_dry_run

  if [[ "${DRY_RUN}" -eq 1 ]]; then
    echo ""
    echo "（--dry-run 完成：以上仅为预览，未对 Studio/105 做任何修改）"
    exit 0
  fi

  echo ""
  echo -e "${BOLD}▶ [Studio 1/4] rsync 代码...${NC}"
  rsync -az --delete \
    --exclude-from="${EXCLUDE_STUDIO}" \
    "${LOCAL_ROOT}/" \
    "${STUDIO_HOST}:${STUDIO_REMOTE}/"
  ok "代码同步完成"

  verify_studio_after_rsync

  echo ""
  echo -e "${BOLD}▶ [Studio 2/4] npm install...${NC}"
  ssh_studio "cd '${STUDIO_REMOTE}' && PATH='${STUDIO_NODE_BIN}':'\$PATH' npm install --prefer-offline 2>&1 | tail -5"

  if [[ "${SKIP_BUILD}" -eq 0 ]]; then
    echo ""
    echo -e "${BOLD}▶ [Studio 3/4] npm run build...${NC}"
    if ! ssh_studio "cd '${STUDIO_REMOTE}' && PATH='${STUDIO_NODE_BIN}':'\$PATH' npm run build"; then
      warn "vite build 失败，服务仍在运行旧产物"
      if [[ "${AUTO_YES}" -eq 0 ]]; then
        local confirm
        read -r -p "  继续重启服务？[y/N] " confirm </dev/tty
        [[ "${confirm}" =~ ^[Yy]$ ]] || exit 1
      else
        warn "已自动继续重启（--yes）"
      fi
    else
      ok "build 完成"
    fi
  else
    echo ""
    echo -e "${BOLD}▶ [Studio 3/4] 跳过 build（--skip-build）${NC}"
  fi

  if [[ "${NO_RESTART}" -eq 0 ]]; then
    echo ""
    echo -e "${BOLD}▶ [Studio 4/4] 重启服务（portal → plaza）${NC}"
    wait_studio_healthy
  else
    echo ""
    echo -e "${BOLD}▶ [Studio 4/4] 跳过重启（--no-restart）${NC}"
  fi
fi

if [[ "${ONLY_100}" -eq 0 ]]; then
  if [[ "${DRY_RUN}" -eq 1 ]]; then
    echo ""
    echo "（--dry-run：跳过 105 同步，未做任何修改）"
    exit 0
  fi

  # 确保 Studio 健康再触发 105
  if [[ "${NO_RESTART}" -eq 0 && "${ONLY_105}" -eq 0 ]]; then
    echo ""
    echo -e "${BOLD}[安全] 再次确认 Studio 健康后再推 105...${NC}"
    if ! ssh_studio "curl -sf '${STUDIO_HEALTH}'" &>/dev/null; then
      die "Studio portal 此刻不健康，已暂停 105 同步，请先排查 Studio 问题"
    fi
    ok "Studio portal 确认健康"
  fi

  echo ""
  echo -e "${BOLD}▶ [105] 触发 Studio → 105 同步...${NC}"
  SYNC_ENV=""
  [[ "${SKIP_BUILD}" -eq 1 ]] && SYNC_ENV="${SYNC_ENV} SKIP_BUILD=1"
  [[ "${NO_RESTART}" -eq 1 ]] && SYNC_ENV="${SYNC_ENV} NO_RESTART=1"
  ssh_studio "cd '${STUDIO_REMOTE}' && env ${SYNC_ENV} bash scripts/sync-to-105.sh"

  if [[ "${NO_RESTART}" -eq 0 ]]; then
    verify_105_after_sync
  fi
fi

# ══════════════════════════════════════════════════════════════════════════════
# 完成
# ══════════════════════════════════════════════════════════════════════════════
echo ""
hr
if [[ "${ONLY_105}" -eq 1 ]]; then
  echo -e " ${GREEN}${BOLD}✅ 105 已更新${NC}"
elif [[ "${ONLY_100}" -eq 1 ]]; then
  echo -e " ${GREEN}${BOLD}✅ Studio(100) 已更新${NC}"
else
  echo -e " ${GREEN}${BOLD}✅ Studio(100) + 105 均已更新${NC}"
fi
echo " $(date '+%Y-%m-%d %H:%M:%S')"
hr
