#!/usr/bin/env bash
# apply-403-fix.sh — 幂等应用/回滚 dsh 403 修复补丁（方案 2）
#
# 背景：官方 dsh 把特权 /api 方法（settings.*、credentials.* 等）钉死在
# loopback，反向代理访问时即使配置了 --trusted-host 也返回 403。本脚本把
# 一行补丁打到运行时实际加载的全局 @deepseek-ai/dsh-client-connection 上，
# 让特权方法也使用配置的 trustedHosts 列表校验（trustedHosts 仍是
# DNS-rebinding 围栏，不是认证，语义与上游一致）。
#
# 用法：
#   apply-403-fix.sh            # 应用补丁（幂等，已应用则跳过）
#   apply-403-fix.sh --check    # 仅检查补丁状态，不修改
#   apply-403-fix.sh --revert   # 从备份回滚补丁
#   apply-403-fix.sh --help     # 帮助
#
# 说明：
#   - 通过 `readlink -f $(which dsh)` 定位全局 dsh 包根，不依赖硬编码路径
#   - 应用前自动备份为 lib/index.js.orig
#   - 补丁文件在工程内维护：patches/@deepseek-ai+dsh-client-connection@0.1.0-rc.7.patch
set -euo pipefail

SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
PATCH_FILE="$SCRIPT_DIR/patches/@deepseek-ai+dsh-client-connection@0.1.0-rc.7.patch"

# 期望补丁后特权检查行包含 trustedHosts（而非空列表）
PATCHED_MARKER='PRIVILEGED_METHODS.has(method) && !isTrustedApiRequest(request, trustedHosts)'
ORIGINAL_MARKER='PRIVILEGED_METHODS.has(method) && !isTrustedApiRequest(request, []))'

die() { echo "[apply-403-fix] 错误: $*" >&2; exit 1; }
info() { echo "[apply-403-fix] $*"; }

# ── 定位运行时实际加载的全局 dsh 包 ──────────────────────────────────────
DSH_BIN="$(command -v dsh || true)"
if [ -z "$DSH_BIN" ]; then
  die "未找到 dsh 命令。请确认已全局安装 @deepseek-ai/dsh。"
fi
DSH_REAL="$(readlink -f "$DSH_BIN")"   # .../lib/node_modules/@deepseek-ai/dsh/lib/bin.js
DSH_ROOT="$(dirname "$(dirname "$DSH_REAL")")"  # .../lib/node_modules/@deepseek-ai/dsh
CC_DIR="$DSH_ROOT/node_modules/@deepseek-ai/dsh-client-connection"
TARGET="$CC_DIR/lib/index.js"

if [ ! -f "$TARGET" ]; then
  die "未找到 $TARGET（dsh 依赖布局与预期不符，请检查 dsh 安装）。"
fi
if [ ! -f "$PATCH_FILE" ]; then
  die "未找到补丁文件 $PATCH_FILE。"
fi

# ── 检查状态 ──────────────────────────────────────────────────────────────
check_state() {
  if grep -qF "$PATCHED_MARKER" "$TARGET"; then
    echo "patched"
  elif grep -qF "$ORIGINAL_MARKER" "$TARGET"; then
    echo "unpatched"
  else
    echo "unknown"
  fi
}

# ── --check ───────────────────────────────────────────────────────────────
if [ "${1:-}" = "--check" ]; then
  case "$(check_state)" in
    patched)   info "已打补丁: $TARGET"; exit 0 ;;
    unpatched) info "未打补丁: $TARGET"; exit 0 ;;
    *)         die "无法识别 $TARGET 的状态（可能 dsh 版本已升级，补丁已过期）。" ;;
  esac
fi

# ── --revert ──────────────────────────────────────────────────────────────
if [ "${1:-}" = "--revert" ]; then
  if [ "$(check_state)" = "unpatched" ]; then
    info "当前未打补丁，无需回滚。"
    exit 0
  fi
  if [ ! -f "$TARGET.orig" ]; then
    die "没有备份文件 $TARGET.orig，无法回滚（可能不是本脚本打的补丁）。"
  fi
  cp "$TARGET.orig" "$TARGET"
  info "已从备份回滚: $TARGET"
  exit 0
fi

# ── --help / 未知参数 ────────────────────────────────────────────────────
if [ "${1:-}" = "--help" ] || [ "${1:-}" = "-h" ]; then
  grep '^#' "$0" | sed 's/^# \{0,1\}//'
  exit 0
fi
if [ $# -gt 0 ]; then
  die "未知参数: $*（用 --help 查看用法）"
fi

# ── 应用补丁（幂等） ─────────────────────────────────────────────────────
case "$(check_state)" in
  patched)
    info "已打补丁，跳过: $TARGET"
    exit 0
    ;;
  unknown)
    die "无法识别 $TARGET 的状态，拒绝修改。请人工检查（可能 dsh 已升级）。"
    ;;
esac

# 备份原始文件（仅在应用时创建，--revert 依赖它）
if [ ! -f "$TARGET.orig" ]; then
  cp "$TARGET" "$TARGET.orig"
  info "已备份原始文件: $TARGET.orig"
else
  info "备份已存在，保留: $TARGET.orig"
fi

# 在包根目录执行 patch -p1（补丁内路径为 lib/index.js）
if ! (cd "$CC_DIR" && patch -p1 --forward < "$PATCH_FILE"); then
  info "patch 应用失败，正在回滚本次修改…"
  if [ -f "$TARGET.orig" ]; then cp "$TARGET.orig" "$TARGET"; fi
  die "补丁应用失败，已恢复原状。请人工检查。"
fi

# 应用后校验
if [ "$(check_state)" = "patched" ]; then
  info "补丁已应用: $TARGET"
else
  die "补丁应用后校验失败，请人工检查 $TARGET。"
fi
info "提示: 需要重启 dsh（my_deepseek.sh stop && my_deepseek.sh）后生效。"
