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

# ⚠️ 已废弃(v1.0.0 M4 AC12a 裁定)⚠️
#
# 本文件为 v0.98.0 旧版"版本安装盘"变体,与仓库根 install.sh(v0.98.5 用户
# 安装盘,权威单一源)内容大幅分叉=陈旧示例存活三版根因(3l 池 12 号)。
# 权威单一源 = **仓库根 install.sh**——README 唯一推荐入口,含 AC1-AC7
# 语义化退出码/双入口/验证门禁。本文件仅保留作历史参照,**请勿使用**。
# 如需安装/升级/回滚/验证/状态查询,使用: bash install.sh [options]

# xwcode 版本安装盘(v0.98.0 M1)
#
# 一键、可实证、可回退的安装体验:install / upgrade / rollback / verify / status
#
# 用法: bash scripts/install.sh <subcommand> <target> [version] [options]
#   install   首装/覆盖安装(旧行为兼容;原子部署 + 验证门禁)
#   upgrade   升级(备份→构建→原子部署→验证→失败自动回滚;回滚也失败退出 2)
#   rollback  回滚(恢复最近/指定备份点)
#   verify    仅验证(--version 实证 + 部署位 sha256 一致性 + 可执行位)
#   status    查看各目标当前部署版本 + 可用备份点
#
# 示例:
#   bash scripts/install.sh upgrade km v0.98.6
#   bash scripts/install.sh install km
#   bash scripts/install.sh verify km v0.98.6
#   bash scripts/install.sh rollback km
#   bash scripts/install.sh status
#
# 退出码约定:
#   0 成功 / 1 失败(预检/部署/验证/回滚) / 2 升级后验证失败且自动回滚也失败
#
# 关键教训落实:
#   - 部署位 ≠ 构建产物(v0.97.8 L4): verify 检查 2 强制 sha256 比对
#   - 失败可回退: upgrade 验证失败自动 restore 备份,不留悬空部署位
#   - 向后兼容: install 子命令保持旧 install.sh 行为语义(覆盖安装 + 验证)

SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
PROJECT_ROOT="$(cd "$SCRIPT_DIR/.." && pwd)"

# ── 目标机器映射 ─────────────────────────────────────────────
# 格式: <name>:<host>:<user>:<deploy_path>(空 host = 本地 loopback,供锁测)
# 新增目标 = 追加一行,不改流程逻辑。单项可用 env 覆盖: TARGET_<NAME>_HOST/USER/PATH
TARGETS=(
  "km:120.48.85.61:km:~/.local/bin/xwcode"
  # loopback: 本地模拟目标(空 host → 本地执行);deploy_path 由 TARGET_LOOPBACK_PATH 覆盖
  "loopback:::loopback"
)

# 运行期当前目标(resolve_target 填充)
CUR_NAME="" CUR_HOST="" CUR_USER="" CUR_DEPLOY=""

log() { echo "[install-media] $*" >&2; }

usage() {
  cat <<'USAGE'
用法: bash scripts/install.sh <subcommand> <target> [version] [options]

子命令:
  install   <target> [version] [--allow-dirty]   首装/覆盖安装(原子部署 + 验证)
  upgrade   <target> [version] [--allow-dirty]   升级(备份→构建→部署→验证→失败自动回滚)
  rollback  <target> [version]                     回滚到最近/指定备份点
  verify    <target> [version] [build_sha256]      仅验证(三项检查)
  status    [target]                               查看部署版本 + 备份点(无 target=全部)

选项:
  --allow-dirty   允许脏工作区构建(默认拒绝,防部署位版本与 tag 漂移)

示例:
  bash scripts/install.sh upgrade km v0.98.6
  bash scripts/install.sh install km
  bash scripts/install.sh verify km v0.98.6
  bash scripts/install.sh status

退出码: 0 成功 / 1 失败 / 2 升级验证失败且自动回滚也失败
USAGE
  exit "${1:-1}"
}

# ── 目标解析 ─────────────────────────────────────────────────
resolve_target() {
  local name="$1" entry found="" n h u p
  for entry in "${TARGETS[@]}"; do
    IFS=: read -r n h u p <<< "$entry"
    if [ "$n" = "$name" ]; then
      found=1; CUR_NAME="$n"; CUR_HOST="$h"; CUR_USER="$u"; CUR_DEPLOY="$p"; break
    fi
  done
  if [ -z "$found" ]; then
    log "error: 未知目标 '$name'"
    return 1
  fi
  # env 单项覆盖(TARGET_<NAME>_HOST/USER/PATH)—— 间接展开分两步,避免嵌套默认值内间接名
  local up vh vu vp
  up="$(echo "$name" | tr '[:lower:]' '[:upper:]')"
  vh="TARGET_${up}_HOST"; vu="TARGET_${up}_USER"; vp="TARGET_${up}_PATH"
  CUR_HOST="${!vh:-$CUR_HOST}"
  CUR_USER="${!vu:-$CUR_USER}"
  CUR_DEPLOY="${!vp:-$CUR_DEPLOY}"
  return 0
}

is_local() { [ -z "$CUR_HOST" ]; }

# 远程/本地 shell 执行(空 host = 本地 bash -c)
rsh() {
  if is_local; then bash -c "$1"; else ssh -o ConnectTimeout=5 "${CUR_USER}@${CUR_HOST}" "$1"; fi
}
# 本地 → 远程/本地 复制
rcp() {
  if is_local; then cp "$1" "$2"; else scp "$1" "${CUR_USER}@${CUR_HOST}:$2"; fi
}

# ── 版本解析 ────────────────────────────────────────────────
# 输出: VTAG VCOMMIT VCOMMIT7
resolve_version() {
  local req="${1:-}"
  if [ -z "$req" ]; then
    VTAG="$(git -C "$PROJECT_ROOT" describe --tags --abbrev=0 2>/dev/null || echo HEAD)"
  else
    VTAG="$req"
  fi
  # 版本串 = --version 输出标签(同旧 install.sh 口径)。可解析为 git ref 时记 commit(worktree 构建用), 否则构建当前 HEAD
  # 注: 用 -q --verify + 长度校验(某些环境下 rev-parse 对未知 ref 行为异常, 裸 rev-parse 不可信)
  VCOMMIT="$(git -C "$PROJECT_ROOT" rev-parse -q --verify "${VTAG}^{commit}" 2>/dev/null)"
  [ "${#VCOMMIT}" -eq 40 ] || VCOMMIT=""
  if [ -z "$VCOMMIT" ]; then
    VCOMMIT="$(git -C "$PROJECT_ROOT" rev-parse -q --verify HEAD 2>/dev/null)" \
      || { log "error: 无法解析任何 commit"; return 1; }
    log "version: 标签 '$VTAG' 非 git ref, 构建当前 HEAD"
  fi
  VCOMMIT7="${VCOMMIT:0:7}"
  log "version: $VTAG -> $VCOMMIT ($VCOMMIT7)"
}

# ── 预检 ────────────────────────────────────────────────────
precheck_local() {
  local version="$1" allow_dirty="$2" fail=0
  for tool in git cargo ssh; do
    command -v "$tool" >/dev/null 2>&1 || { log "precheck: 缺少本地工具 '$tool'"; fail=1; }
  done
  if [ -z "$VCOMMIT" ]; then log "precheck: 目标版本不可解析"; fail=1; fi
  if [ "$allow_dirty" != "1" ]; then
    if ! git -C "$PROJECT_ROOT" diff --quiet 2>/dev/null || ! git -C "$PROJECT_ROOT" diff --cached --quiet 2>/dev/null; then
      log "precheck: 工作区有未提交修改(脏树)。构建脏树会致部署位版本与 tag 漂移(L4 教训)。"
      log "precheck: 请提交/暂存,或加 --allow-dirty 显式确认"
      fail=1
    fi
  fi
  return $fail
}

precheck_remote() {
  local fail=0
  if is_local; then
    # 本地: 部署位父目录须存在且可写
    local parent; parent="$(dirname "$CUR_DEPLOY")"
    if [ ! -d "$parent" ]; then log "precheck(remote): 部署位父目录不存在: $parent"; fail=1; fi
    if [ -d "$parent" ] && ! ( touch "$parent/.xw_install_probe" 2>/dev/null && rm -f "$parent/.xw_install_probe" ); then
      log "precheck(remote): 部署位父目录不可写: $parent"; fail=1
    fi
  else
    if ! rsh "true" 2>/dev/null; then log "precheck(remote): 目标不可达 ${CUR_USER}@${CUR_HOST}"; fail=1; return $fail; fi
    local parent; parent="$(dirname "$CUR_DEPLOY")"
    if ! rsh "test -d '$parent' && touch '$parent/.xw_install_probe' && rm -f '$parent/.xw_install_probe'" 2>/dev/null; then
      log "precheck(remote): 部署位父目录缺失或不可写: $parent"; fail=1
    fi
  fi
  return $fail
}

# ── 构建(可插拔)─────────────────────────────────────────
# 输出: 产物路径(stdout)
build_artifact() {
  local commit="$1"
  if [ -n "${INSTALL_PREBUILT:-}" ]; then
    log "build: 使用预置产物 shim $INSTALL_PREBUILT(跳过 cargo build)"
    echo "$INSTALL_PREBUILT"
    return 0
  fi
  local wt; wt="$(mktemp -d "${TMPDIR:-/tmp}/xwcode-build.XXXXXX")"
  log "build: worktree $wt @ $commit(隔离源码,共享 target 缓存)"
  git -C "$PROJECT_ROOT" worktree add --detach "$wt" "$commit" >/dev/null
  # shellcheck disable=SC2030
  if ! ( cd "$wt" && CARGO_TARGET_DIR="$PROJECT_ROOT/target" CARGO_BUILD_JOBS="${CARGO_BUILD_JOBS:-2}" cargo build --release ); then
    git -C "$PROJECT_ROOT" worktree remove --force "$wt" >/dev/null 2>&1 || true
    log "build: cargo build 失败"
    return 1
  fi
  git -C "$PROJECT_ROOT" worktree remove --force "$wt" >/dev/null 2>&1 || true
  echo "$PROJECT_ROOT/target/release/xwcode"
}

# ── 备份 / 轮转 / 恢复 ──────────────────────────────────────
# 输出: 备份目录路径(stdout);无既有部署位时返回空(跳过)
do_backup() {
  local deploy="$1" tag="$2" commit7="$3" version="$4"
  if ! rsh "test -f '$deploy'" 2>/dev/null; then
    log "backup: 无既有 $deploy,跳过备份"
    echo ""
    return 0
  fi
  local ts bdir; ts="$(date +%s)"
  bdir="${deploy}.backups/${ts}-${tag}-${commit7}"
  rsh "mkdir -p '$bdir'"
  rcp "$deploy" "$bdir/xwcode"
  local meta; meta="$(mktemp -p "${TMPDIR:-/tmp}" 2>/dev/null || mktemp)"
  printf '{"version":"%s","tag":"%s","commit":"%s","backed_up_at":"%s"}\n' \
    "$version" "$tag" "$commit7" "$(date -u +%FT%TZ)" > "$meta"
  rcp "$meta" "$bdir/meta.json"
  rm -f "$meta"
  log "backup: $bdir"
  rotate_backups "$deploy"
  echo "$bdir"
}

# 保留最近 3 份备份,删除更旧的(按目录名时间戳排序)
rotate_backups() {
  local deploy="$1"
  local d bname; d="$(dirname "$deploy")"; bname="$(basename "$deploy").backups"
  rsh "cd '$d' 2>/dev/null && ls -1d '$bname'/*/ 2>/dev/null | sort | head -n -3 | xargs -r rm -rf" || true
}

# 从备份点恢复(原子:cp→staging→mv)
do_restore() {
  local bdir="$1" deploy="$2"
  local staging="${deploy}.tmp.restore.$$"
  rcp "$bdir/xwcode" "$staging"
  rsh "mv '$staging' '$deploy' && chmod +x '$deploy'"
  log "restore: $bdir -> $deploy"
}

# ── 验证门禁(AC4)──────────────────────────────────────────
# remote_verify <expected_version> <build_sha256> ;逐项 VERIFY: PASS|FAIL,rc=0 全过
remote_verify() {
  local expected="$1" build_sha="$2" deploy="$CUR_DEPLOY" rc=0
  local exp="${expected#v}"

  # 检查 1: --version 实证
  local inst
  inst="$(rsh "$deploy --version 2>/dev/null" || echo NOT_FOUND)"
  if echo "$inst" | grep "$exp" >/dev/null; then
    log "VERIFY: PASS (version: $inst)"
  else
    log "VERIFY: FAIL (version: 期望含 '$exp', 实际 '$inst')"
    rc=1
  fi

  # 检查 2: 部署位 sha256 == 构建产物 sha256(L4 教训:部署位 == 构建产物)
  local rsha
  rsha="$(rsh "sha256sum '$deploy' 2>/dev/null | awk '{print \$1}'" || echo MISSING)"
  if [ "$rsha" = "$build_sha" ]; then
    log "VERIFY: PASS (artifact: sha256=$rsha)"
  else
    log "VERIFY: FAIL (artifact: 部署位=$rsha 构建=$build_sha)"
    rc=1
  fi

  # 检查 3: 可执行位
  if rsh "test -x '$deploy'" 2>/dev/null; then
    log "VERIFY: PASS (exec: $deploy 可执行)"
  else
    log "VERIFY: FAIL (exec: $deploy 不可执行)"
    rc=1
  fi
  return $rc
}

# ── 锁(mkdir 原子锁,本地/远程通用)────────────────────────
acquire_lock() {
  local lockdir="$1" parent i=0
  parent="$(dirname "$lockdir")"
  rsh "mkdir -p '$parent'"
  while ! rsh "mkdir '$lockdir' 2>/dev/null"; do
    i=$((i+1))
    if [ "$i" -ge 50 ]; then log "lock: 获取超时 $lockdir"; return 1; fi
    rsh "sleep 0.2"
  done
  return 0
}
release_lock() {
  rsh "rmdir '$1' 2>/dev/null || true"
}

# ── 原子部署(staging 已就位)──────────────────────────────
# deploy_stage <staging> <expected> <build_sha> <tag> <commit7> <version>
# 返回: 0 成功 / 1 失败
deploy_stage() {
  local staging="$1" expected="$2" build_sha="$3" tag="$4" commit7="$5" version="$6"
  local deploy="$CUR_DEPLOY"
  local bdir
  bdir="$(do_backup "$deploy" "$tag" "$commit7" "$version")"
  rsh "mkdir -p '$(dirname "$deploy")'"
  rsh "mv '$staging' '$deploy' && chmod +x '$deploy'"
  log "deploy: $staging -> $deploy"
  if remote_verify "$expected" "$build_sha"; then
    log "deploy: 验证通过"
    return 0
  fi
  # 验证失败 → 自动回滚(若有备份)
  if [ -n "$bdir" ]; then
    log "deploy: 验证失败,自动回滚到备份 $bdir"
    do_restore "$bdir" "$deploy"
    return 1
  else
    log "deploy: 验证失败且无备份可回滚(首装)"
    return 1
  fi
}

# ── 子命令实现 ──────────────────────────────────────────────
cmd_install() {
  local target="$1" version="$2" allow_dirty="$3"
  resolve_target "$target" || return 1
  resolve_version "$version" || return 1
  precheck_local "$version" "$allow_dirty" || { log "install: 本地预检失败"; return 1; }
  precheck_remote || { log "install: 远程预检失败"; return 1; }

  local artifact local_sha
  artifact="$(build_artifact "$VCOMMIT")" || { log "install: 构建失败"; return 1; }
  local_sha="$(sha256sum "$artifact" | awk '{print $1}')"
  log "install: 产物 $artifact sha256=$local_sha"

  local staging="${CUR_DEPLOY}.tmp.$$"
  rcp "$artifact" "$staging"
  local lockdir="${CUR_DEPLOY}.backups/.lock"
  acquire_lock "$lockdir" || { rm -f "$staging" 2>/dev/null || rsh "rm -f '$staging'" 2>/dev/null || true; return 1; }
  local rc=0
  deploy_stage "$staging" "$version" "$local_sha" "$VTAG" "$VCOMMIT7" "$version" || rc=1
  release_lock "$lockdir"
  if [ $rc -eq 0 ]; then log "install: ✅ 完成 ($target)"; else log "install: ❌ 失败 ($target)"; fi
  return $rc
}

cmd_upgrade() {
  local target="$1" version="$2" allow_dirty="$3"
  resolve_target "$target" || return 1
  resolve_version "$version" || return 1
  precheck_local "$version" "$allow_dirty" || { log "upgrade: 本地预检失败"; return 1; }
  precheck_remote || { log "upgrade: 远程预检失败"; return 1; }

  local artifact local_sha
  artifact="$(build_artifact "$VCOMMIT")" || { log "upgrade: 构建失败"; return 1; }
  local_sha="$(sha256sum "$artifact" | awk '{print $1}')"
  log "upgrade: 产物 $artifact sha256=$local_sha"

  local staging="${CUR_DEPLOY}.tmp.$$"
  rcp "$artifact" "$staging"
  local lockdir="${CUR_DEPLOY}.backups/.lock"
  acquire_lock "$lockdir" || { rsh "rm -f '$staging'" 2>/dev/null || rm -f "$staging" 2>/dev/null || true; return 1; }
  local rc=0
  deploy_stage "$staging" "$version" "$local_sha" "$VTAG" "$VCOMMIT7" "$version" || rc=1
  release_lock "$lockdir"
  if [ $rc -eq 0 ]; then log "upgrade: ✅ 完成 ($target -> $version)"; else
    log "upgrade: ❌ 失败 ($target),已尝试自动回滚"
    return 2
  fi
  return 0
}

cmd_rollback() {
  local target="$1" version="${2:-}"
  resolve_target "$target" || return 1
  local deploy="$CUR_DEPLOY"
  local broot="${deploy}.backups"
  local bdir
  if [ -n "$version" ]; then
    # 指定版本:找最近匹配该版本前缀的备份点
    bdir="$(rsh "cd '$(dirname "$deploy")' && ls -1d '$(basename "$deploy").backups'/*-${version}-* 2>/dev/null | sort | tail -1" || true)"
  else
    bdir="$(rsh "cd '$(dirname "$deploy")' && ls -1d '$(basename "$deploy").backups'/*/ 2>/dev/null | sort | tail -1" || true)"
  fi
  if [ -z "$bdir" ]; then log "rollback: 无可用备份点 $broot"; return 1; fi
  # 备份点目录名形如 <ts>-<tag>-<commit7>,取版本=tag 段
  local bver; bver="$(basename "$bdir" | awk -F- '{print $(NF-1)}')"
  # 从备份 meta 读版本(优先)—— 避免嵌套转义引号,先取内容再本地解析
  local meta_raw meta_version
  meta_raw="$(rsh "cat '$bdir/meta.json' 2>/dev/null" || true)"
  meta_version="$(echo "$meta_raw" | sed -n 's/.*"version":"\([^"]*\)".*/\1/p' | head -1)"
  [ -n "$meta_version" ] && bver="$meta_version"

  local lockdir="${CUR_DEPLOY}.backups/.lock"
  acquire_lock "$lockdir" || return 1
  local rc=0
  do_backup "$deploy" "$bver" "rollback" "$(date +%s)" >/dev/null || true
  do_restore "$bdir" "$deploy" || rc=1
  release_lock "$lockdir"
  # 回滚后 verify(回滚不级联:verify 失败仅报错留现场,不自动再回滚)
  if [ $rc -eq 0 ]; then
    # 无法取构建产物 sha(历史备份),跳过 artifact 检查做 version+exec 轻量验证
    local inst; inst="$(rsh "$deploy --version 2>/dev/null" || echo NOT_FOUND)"
    if echo "$inst" | grep "${bver#v}" >/dev/null; then
      log "rollback: ✅ 完成 (恢复到 $bver, $inst)"
      return 0
    else
      log "rollback: ⚠️ 已恢复 $bdir 但版本实证异常: $inst(现场保留)"
      return 1
    fi
  fi
  log "rollback: ❌ 恢复失败(现场保留,不级联回滚)"
  return 1
}

cmd_verify() {
  local target="$1" version="$2" build_sha="${3:-}"
  resolve_target "$target" || return 1
  local exp="${version#v}"
  [ -z "$exp" ] && { log "verify: 需要指定 version"; return 1; }
  if [ -z "$build_sha" ]; then
    # 无构建产物 sha → 跳过检查 2(artifact 一致性需构建产物在场)
    local rc=0
    local inst; inst="$(rsh "$CUR_DEPLOY --version 2>/dev/null" || echo NOT_FOUND)"
    if echo "$inst" | grep "$exp" >/dev/null; then log "VERIFY: PASS (version: $inst)"; else log "VERIFY: FAIL (version: 期望 $exp 实际 $inst)"; rc=1; fi
    if rsh "test -x '$CUR_DEPLOY'" 2>/dev/null; then log "VERIFY: PASS (exec)"; else log "VERIFY: FAIL (exec)"; rc=1; fi
    log "VERIFY: SKIP (artifact: 未提供 build_sha256)"
    return $rc
  fi
  local rc=0
  remote_verify "$version" "$build_sha" || rc=1
  return $rc
}

cmd_status() {
  local target="${1:-}"
  local names=()
  if [ -n "$target" ]; then names=("$target"); else names=(); for e in "${TARGETS[@]}"; do IFS=: read -r n _ _ _ <<< "$e"; names+=("$n"); done; fi
  local n
  for n in "${names[@]}"; do
    resolve_target "$n" || continue
    local inst bline
    inst="$(rsh "$CUR_DEPLOY --version 2>/dev/null" || echo NOT_DEPLOYED)"
    bline="$(rsh "cd '$(dirname "$CUR_DEPLOY")' && ls -1 '$(basename "$CUR_DEPLOY").backups' 2>/dev/null | grep -v '^\.lock$' | tr '\n' ' '" || true)"
    echo "[$n] host=${CUR_HOST:-local} deploy=$CUR_DEPLOY"
    echo "    version: $inst"
    [ -n "$bline" ] && echo "    backups: $bline" || echo "    backups: (无)"
  done
  return 0
}

# ── 主分发 ──────────────────────────────────────────────────
main() {
  local sub="${1:-}"; shift || true
  case "$sub" in
    install)
      _t=""; _v=""; _ad=0
      for _a in "$@"; do
        if [ "$_a" = "--allow-dirty" ]; then _ad=1
        elif [ -z "$_t" ]; then _t="$_a"
        elif [ -z "$_v" ]; then _v="$_a"
        fi
      done
      [ -z "$_t" ] && usage; cmd_install "$_t" "$_v" "$_ad" ;;
    upgrade)
      _t=""; _v=""; _ad=0
      for _a in "$@"; do
        if [ "$_a" = "--allow-dirty" ]; then _ad=1
        elif [ -z "$_t" ]; then _t="$_a"
        elif [ -z "$_v" ]; then _v="$_a"
        fi
      done
      [ -z "$_t" ] && usage; cmd_upgrade "$_t" "$_v" "$_ad" ;;
    rollback) local t="${1:-}"; local v="${2:-}"; [ -z "$t" ] && usage; cmd_rollback "$t" "$v" ;;
    verify)   local t="${1:-}"; local v="${2:-}"; local bs="${3:-}"; [ -z "$t" ] && usage; cmd_verify "$t" "$v" "$bs" ;;
    status)   cmd_status "${1:-}" ;;
    -h|--help|help) usage 0 ;;
    *) usage 1 ;;
  esac
}

main "$@"