#!/usr/bin/env bash
# =============================================================================
# sign.sh — FlexUI 仓库签名配置备份 / 打包 / 恢复工具
#
# 背景:
#   签名材料(ai-agent/sign/*)与各 build-profile.json5 中的 signingConfigs
#   属于敏感信息,**不得提交到 git**(已 gitignore + CLAUDE.md 说明)。
#   本脚本用于:删除前备份、打包携带到其他机器、以及一键恢复签名配置。
#
# 用法:
#   ./sign.sh backup    备份当前签名材料 + 各 build-profile.json5 的 signingConfigs 片段
#   ./sign.sh pack      将备份目录与本脚本打包为 zip(拷贝到其他机器一键恢复)
#   ./sign.sh restore   从备份目录恢复签名材料与 signingConfigs 配置
#
# 约定:
#   - 备份目录 = <仓库根>/sign-backup/(已 gitignore,切勿提交)
#   - restore 恢复的 build-profile.json5 修改属于本地配置,请勿提交(见 CLAUDE.md)
#   - 依赖 bash + awk(Git Bash / macOS / Linux 均自带)
# =============================================================================
set -euo pipefail

REPO_ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
BACKUP_DIR="$REPO_ROOT/sign-backup"
SIGN_SRC="$REPO_ROOT/ai-agent/sign"
BUILD_PROFILE_FRAG_DIR="$BACKUP_DIR/build-profile"

die() {
  echo "错误: $*" >&2
  exit 1
}

# --- 发现仓库内所有含 signingConfigs 的 build-profile.json5 -----------------
find_build_profiles() {
  find "$REPO_ROOT" -name build-profile.json5 \
    -not -path "$REPO_ROOT/.git/*" \
    -not -path "*/node_modules/*" \
    -not -path "*/oh_modules/*" \
    -not -path "*/build/*" \
    -not -path "$BACKUP_DIR/*" \
    2>/dev/null |
    while read -r f; do
      if [ -f "$f" ] && grep -q '"signingConfigs"' "$f" 2>/dev/null; then
        echo "$f"
      fi
    done
}

# --- awk:提取 "signingConfigs": [...] 块(从 key 行到方括号配平) ----------
extract_block() {
  awk '
    /"signingConfigs"/ { inblock = 1; depth = 0 }
    inblock {
      line = $0
      nb = gsub(/\[/, "[", line)
      line = $0
      nc = gsub(/\]/, "]", line)
      depth += nb - nc
      print
      if (depth <= 0) { exit }
    }
  ' "$1"
}

# --- awk:删除 "signingConfigs": [...] 块(其余行原样输出) ------------------
strip_block() {
  awk '
    /"signingConfigs"/ { inblock = 1; depth = 0 }
    !inblock { print; next }
    {
      line = $0
      nb = gsub(/\[/, "[", line)
      line = $0
      nc = gsub(/\]/, "]", line)
      depth += nb - nc
      if (depth <= 0) { inblock = 0 }
    }
  ' "$1"
}

# --- awk:将片段插入到 "app": { 之后(文件须已 strip 过) -------------------
insert_block() { # $1=片段文件 $2=已去掉旧块的 build-profile.json5
  awk -v frag="$1" '
    BEGIN {
      while ((getline l < frag) > 0) block = block l "\n"
      close(frag)
    }
    /^[[:space:]]*"app"[[:space:]]*:[[:space:]]*\{/ && !inserted {
      print
      printf "%s", block
      inserted = 1
      next
    }
    { print }
    END { if (!inserted) exit 3 }
  ' "$2"
}

# --- 括号配平校验({ } 数量一致)-------------------------------------------
check_balanced() {
  local f="$1" o c
  o=$(tr -cd '{' < "$f" | wc -c)
  c=$(tr -cd '}' < "$f" | wc -c)
  [ "$o" = "$c" ]
}

# --- 行尾风格 / 末尾换行处理(保证恢复后与原文件字节一致)-------------------
# 说明: 不同平台的 awk 对 CRLF 处理不一致(MSYS gawk 文本模式会剥掉 \r),
#       因此手术过程统一用 LF,最后按目标文件的原始风格还原。
has_cr() { tr -cd '\r' < "$1" | grep -q .; }          # 文件含 \r → 视为 CRLF 风格
has_trailing_nl() {                                    # 文件末尾是否有换行
  tail -c 1 "$1" | od -An -tx1 | tr -d ' \n' | grep -q '0a$'
}
lf_to_crlf_file() { awk '{ printf "%s\r\n", $0 }' "$1"; }  # LF → CRLF(全行转换)
strip_trailing_nl() {                                  # 去掉结果末尾的换行
  awk '{ if (NR > 1) print prev; prev = $0 }
       END { if (NR > 0) { gsub(/\r$/, "", prev); printf "%s", prev } }' "$1"
}

# ============================== backup ======================================
cmd_backup() {
  mkdir -p "$BACKUP_DIR"
  echo "==> 备份签名材料"
  if [ -d "$SIGN_SRC" ]; then
    mkdir -p "$BACKUP_DIR/sign"
    cp -a "$SIGN_SRC/." "$BACKUP_DIR/sign/"
    echo "    已备份: $SIGN_SRC -> $BACKUP_DIR/sign/"
  else
    echo "    [警告] 签名材料目录不存在: $SIGN_SRC(跳过材料备份)"
  fi

  echo "==> 备份 signingConfigs 配置片段"
  local n=0
  while read -r f; do
    [ -n "$f" ] || continue
    local rel dest
    rel="${f#"$REPO_ROOT"/}"
    dest="$BUILD_PROFILE_FRAG_DIR/$rel"
    mkdir -p "$(dirname "$dest")"
    extract_block "$f" | tr -d '\r' > "$dest"   # 片段统一存 LF,恢复时按目标文件风格还原
    echo "    已备份: $rel"
    n=$((n + 1))
  done < <(find_build_profiles)
  if [ "$n" -eq 0 ]; then
    echo "    [警告] 未发现任何含 signingConfigs 的 build-profile.json5"
  fi

  cat > "$BACKUP_DIR/MANIFEST.txt" <<EOF
FlexUI 签名配置备份
时间: $(date '+%Y-%m-%d %H:%M:%S')
签名材料: $SIGN_SRC
signingConfigs 片段数: $n
备份目录: $BACKUP_DIR
恢复方式: ./sign.sh restore
EOF
  echo "==> 备份完成 -> $BACKUP_DIR"
  echo "    注意: 该目录已 gitignore,请勿提交;需要带走时执行 ./sign.sh pack"
}

# =============================== pack =======================================
cmd_pack() {
  [ -d "$BACKUP_DIR" ] || die "备份不存在: $BACKUP_DIR,请先执行 ./sign.sh backup"
  local ts zip_name zip_path ok=0
  ts=$(date +%Y%m%d-%H%M%S)
  zip_name="flexui-sign-config-$ts.zip"
  zip_path="$BACKUP_DIR/$zip_name"

  if [ "$ok" -eq 0 ] && command -v zip >/dev/null 2>&1; then
    (cd "$REPO_ROOT" && zip -qr "$zip_path" sign-backup sign.sh) && ok=1
  fi
  if [ "$ok" -eq 0 ] && command -v python >/dev/null 2>&1; then
    (cd "$REPO_ROOT" && python - "$zip_path" <<'PY') && ok=1
import os, sys, zipfile
with zipfile.ZipFile(sys.argv[1], 'w', zipfile.ZIP_DEFLATED) as z:
    for item in ('sign-backup', 'sign.sh'):
        if os.path.isdir(item):
            for dp, _dn, fn in os.walk(item):
                for f in fn:
                    p = os.path.join(dp, f)
                    z.write(p, p)
        else:
            z.write(item, item)
PY
  fi
  if [ "$ok" -eq 0 ] && command -v python3 >/dev/null 2>&1; then
    (cd "$REPO_ROOT" && python3 - "$zip_path" <<'PY') && ok=1
import os, sys, zipfile
with zipfile.ZipFile(sys.argv[1], 'w', zipfile.ZIP_DEFLATED) as z:
    for item in ('sign-backup', 'sign.sh'):
        if os.path.isdir(item):
            for dp, _dn, fn in os.walk(item):
                for f in fn:
                    p = os.path.join(dp, f)
                    z.write(p, p)
        else:
            z.write(item, item)
PY
  fi
  if [ "$ok" -eq 0 ] && command -v powershell >/dev/null 2>&1; then
    (cd "$REPO_ROOT" && powershell -NoProfile -Command "Compress-Archive -Path 'sign-backup','sign.sh' -DestinationPath '$zip_path' -Force") && ok=1
  fi
  [ "$ok" -eq 1 ] || die "打包失败:请安装 zip / python / powershell 后重试"

  echo "==> 打包完成: $zip_path"
  echo "    其他机器恢复: 将 zip 拷贝到仓库根目录解压,然后执行 ./sign.sh restore"
}

# ============================== restore =====================================
cmd_restore() {
  [ -d "$BACKUP_DIR" ] || die "备份不存在: $BACKUP_DIR(请先 ./sign.sh backup,或从 zip 解压)"

  echo "==> 恢复签名材料"
  if [ -d "$BACKUP_DIR/sign" ]; then
    mkdir -p "$SIGN_SRC"
    cp -a "$BACKUP_DIR/sign/." "$SIGN_SRC/"
    echo "    已恢复: $BACKUP_DIR/sign/ -> $SIGN_SRC"
  else
    echo "    [警告] 备份中没有签名材料目录,跳过"
  fi

  echo "==> 恢复 signingConfigs 配置"
  local n=0
  while read -r frag; do
    [ -n "$frag" ] || continue
    local rel target tmp1 tmp2
    rel="${frag#"$BUILD_PROFILE_FRAG_DIR"/}"
    target="$REPO_ROOT/$rel"
    if [ ! -f "$target" ]; then
      echo "    [警告] 目标文件不存在,跳过: $rel"
      continue
    fi
    tmp1="$target.sign-restore-tmp.$$"
    tmp2="$target.sign-restore-tmp2.$$"
    rm -f "$tmp1" "$tmp2"
    # 记录目标文件的行尾风格与末尾换行,恢复后按原样还原(保证 git 无差异)
    local orig_crlf=false orig_nl=true
    if has_cr "$target"; then orig_crlf=true; fi
    if ! has_trailing_nl "$target"; then orig_nl=false; fi
    strip_block "$target" | tr -d '\r' > "$tmp1"
    if ! insert_block "$frag" "$tmp1" > "$tmp2"; then
      echo "    [错误] 未找到 \"app\": { 插入点,跳过: $rel"
      rm -f "$tmp1" "$tmp2"
      continue
    fi
    if [ "$orig_crlf" = true ]; then
      lf_to_crlf_file "$tmp2" > "$tmp2.eol" && mv "$tmp2.eol" "$tmp2"
    fi
    if [ "$orig_nl" = false ]; then
      strip_trailing_nl "$tmp2" > "$tmp2.eol" && mv "$tmp2.eol" "$tmp2"
    fi
    if ! check_balanced "$tmp2"; then
      echo "    [错误] 恢复后括号不平衡,跳过: $rel"
      rm -f "$tmp1" "$tmp2"
      continue
    fi
    cp "$tmp2" "$target" # cp 保留目标文件原有 mode/inode,避免 git mode 变化
    rm -f "$tmp1" "$tmp2"
    echo "    已恢复: $rel"
    n=$((n + 1))
  done < <(find "$BUILD_PROFILE_FRAG_DIR" -type f -name 'build-profile.json5' 2>/dev/null)
  if [ "$n" -eq 0 ]; then
    echo "    [警告] 备份中没有 signingConfigs 片段"
  fi

  echo "==> 恢复完成。"
  echo "    提示: 恢复后 build-profile.json5 的修改属于本地签名配置,请勿提交(见 CLAUDE.md)"
}

# ============================== main ========================================
case "${1:-}" in
  backup) cmd_backup ;;
  pack) cmd_pack ;;
  restore) cmd_restore ;;
  *) echo "用法: $0 {backup|pack|restore}" >&2; exit 1 ;;
esac