#!/usr/bin/env bash
# Fetch the mgt binary and license records from the fixed remote source,
# replace the binary in an existing SafeLine container, and apply the DB
# updates. This script intentionally makes no backups.
set -Eeuo pipefail

CONTAINER="${CONTAINER:-safeline-mgt}"
PG_CONTAINER="${PG_CONTAINER:-safeline-pg}"
PG_USER="${PG_USER:-safeline-ce}"
PG_DB="${PG_DB:-safeline-ce}"
MGT_PORT="${MGT_PORT:-9443}"
MGT_URL="https://dl.naixi.net/waf/mgt"
LICENSE_URL="https://dl.naixi.net/waf/db/license.json"
MANAGER_INFO_URL="https://dl.naixi.net/waf/db/manager_info.json"

MGT_TMP=""
cleanup() { [ -z "$MGT_TMP" ] || rm -f "$MGT_TMP"; }
trap cleanup EXIT

c_ok()   { printf '\033[1;32m%s\033[0m\n' "$*"; }
c_warn() { printf '\033[1;33m%s\033[0m\n' "$*"; }
c_err()  { printf '\033[1;31m%s\033[0m\n' "$*" >&2; }
die()    { c_err "[ERROR] $*"; exit 1; }

read_input() {
  if [ -t 0 ]; then
    read -r "$@"
  elif (exec </dev/tty) 2>/dev/null; then
    read -r "$@" < /dev/tty
  else
    read -r "$@"
  fi
}

usage() {
  cat <<'EOF'
用法：
  bash waf.sh apply
  bash waf.sh status

可覆盖环境变量：
  CONTAINER, PG_CONTAINER, PG_USER, PG_DB, MGT_PORT,
  MGT_SHA256
EOF
}

require_root() { [ "$(id -u)" = 0 ] || die "请以 root 身份运行"; }
require_docker() { command -v docker >/dev/null 2>&1 || die "未找到 docker"; }

container_exists() { docker inspect "$1" >/dev/null 2>&1; }
container_running() { [ "$(docker inspect -f '{{.State.Running}}' "$1" 2>/dev/null || true)" = true ]; }

health_ok() {
  command -v curl >/dev/null 2>&1 || return 0
  curl -ksS --max-time 4 "https://127.0.0.1:$MGT_PORT/api/open/health" 2>/dev/null \
    | grep -q '"ok"'
}

wait_health() {
  local i
  command -v curl >/dev/null 2>&1 || return 0
  printf '等待 mgt 健康检查'
  for i in $(seq 1 60); do
    if health_ok; then printf '\n'; return 0; fi
    printf '.'
    sleep 2
  done
  printf '\n'
  return 1
}

fetch_mgt() {
  local dst="$1"
  rm -f "$dst"
  curl -fsSL --retry 2 --connect-timeout 15 --max-time 120 -o "$dst" "$MGT_URL" \
    || return 1
  [ "$(od -An -t x1 -N4 "$dst" | tr -d ' \n')" = 7f454c46 ] || return 1
  local size hash expected
  size="$(wc -c < "$dst")"
  [ "$size" -gt 1000000 ] || return 1
  hash="$(sha256sum "$dst" | awk '{print $1}')"
  expected="${MGT_SHA256:-}"
  if [ -n "$expected" ] && [ "$hash" != "$expected" ]; then
    return 1
  fi
  c_ok "远程 mgt 已获取：$MGT_URL ($size bytes, sha256=$hash)"
}

parse_args() {
  if [ "$#" -eq 0 ]; then
    if [ -t 0 ] || (exec </dev/tty) 2>/dev/null; then
      interactive_menu
      exit $?
    fi
    usage
    exit 2
  fi
  ACTION="${1:-}"
  [ -n "$ACTION" ] || { usage; exit 2; }
  shift || true
  while [ "$#" -gt 0 ]; do
    case "$1" in
      -h|--help) usage; exit 0 ;;
      *) die "未知参数：$1" ;;
    esac
  done
}

check_containers() {
  container_exists "$CONTAINER" || die "未找到容器：$CONTAINER"
  container_exists "$PG_CONTAINER" || die "未找到 PostgreSQL 容器：$PG_CONTAINER"
  container_running "$CONTAINER" || die "容器未运行：$CONTAINER"
  container_running "$PG_CONTAINER" || die "容器未运行：$PG_CONTAINER"
}

replace_binary() {
  local binary="$1"
  local remote="/tmp/mgt.patched.$$"
  if ! docker cp "$binary" "$CONTAINER:$remote"; then return 1; fi
  if ! docker exec "$CONTAINER" sh -c "chmod 0755 '$remote' && mv -f '$remote' /app/mgt"; then return 1; fi
  c_ok "已替换 $CONTAINER:/app/mgt"
}

run_sql_file() {
  local file="$1" remote="/tmp/safeline-db-command.$$.$RANDOM.sql" rc=0
  if ! docker cp "$file" "$PG_CONTAINER:$remote"; then return 1; fi
  docker exec "$PG_CONTAINER" psql -X -v ON_ERROR_STOP=1 --single-transaction \
    -U "$PG_USER" -d "$PG_DB" -f "$remote" || rc=$?
  docker exec "$PG_CONTAINER" rm -f "$remote" >/dev/null 2>&1 || true
  return "$rc"
}

fetch_remote_file() {
  local url="$1" dst="$2"
  rm -f "$dst"
  if curl -fsSL --retry 2 --connect-timeout 15 --max-time 60 -o "$dst" "$url"; then
    # Reject HTML/error pages even when a proxy reports HTTP 200.
    [ "$(sed -n 's/^[[:space:]]*\({\).*/\1/p' "$dst" | head -1)" = "{" ] && return 0
  fi
  return 1
}

run_db_files() {
  command -v base64 >/dev/null 2>&1 || die "未找到 base64 命令"
  command -v curl >/dev/null 2>&1 || die "未找到 curl 命令"
  local tmpdir lic_file mgr_file lic_b64 mgr_b64 sql
  tmpdir="$(mktemp -d)"
  lic_file="$tmpdir/license.json"
  mgr_file="$tmpdir/manager_info.json"
  if ! fetch_remote_file "$LICENSE_URL" "$lic_file"; then
    rm -rf "$tmpdir"
    die "远程获取 $LICENSE_URL 失败"
  fi
  if ! fetch_remote_file "$MANAGER_INFO_URL" "$mgr_file"; then
    rm -rf "$tmpdir"
    die "远程获取 $MANAGER_INFO_URL 失败"
  fi
  c_ok "已获取远程数据库文件：$LICENSE_URL 和 $MANAGER_INFO_URL"
  lic_b64="$(tr -d '\r\n' < "$lic_file" | base64 -w0)"
  mgr_b64="$(tr -d '\r\n' < "$mgr_file" | base64 -w0)"
  [ -n "$lic_b64" ] || { rm -rf "$tmpdir"; die "远程 license.json 为空"; }
  [ -n "$mgr_b64" ] || { rm -rf "$tmpdir"; die "远程 manager_info.json 为空"; }
  sql="$(mktemp)"
  {
    printf "INSERT INTO mgt_options(key,value) VALUES('license', convert_from(decode('%s','base64'),'UTF8'))\n" "$lic_b64"
    printf "  ON CONFLICT (key) DO UPDATE SET value=EXCLUDED.value, updated_at=now();\n"
    printf "INSERT INTO mgt_options(key,value) VALUES('manager_info', convert_from(decode('%s','base64'),'UTF8'))\n" "$mgr_b64"
    printf "  ON CONFLICT (key) DO UPDATE SET value=EXCLUDED.value, updated_at=now();\n"
  } > "$sql"
  local rc=0
  if run_sql_file "$sql"; then rc=0; else rc=$?; fi
  rm -f "$sql"
  rm -rf "$tmpdir"
  return "$rc"
}

run_db_sql() {
  run_db_files || return $?
  c_ok "数据库命令已提交：$PG_DB"
}

frontend_patched() {
  docker exec "$CONTAINER" sh -c \
    "grep -R -q 'const K=!1;' /app/static/assets/*.js" 2>/dev/null
}

patch_frontend() {
  local tag
  if ! docker exec "$CONTAINER" sh -c '
    found=1
    for f in /app/static/assets/*.js; do
      [ -f "$f" ] || continue
      if grep -Eq "const K=!1;" "$f"; then
        found=0
        continue
      fi
      if grep -Eq "prompt_type.*river_warning" "$f"; then
        sed -i -E "s/const K=[^;]*prompt_type[^;]*river_warning[^;]*;/const K=!1;/g" "$f"
        found=0
      fi
    done
    exit "$found"
  '; then
    return 1
  fi

  # Force a fresh main-bundle request so an immutable browser cache cannot
  # keep showing the pre-patch authorization warning.
  tag="$(date +%Y%m%d%H%M%S)"
  docker exec "$CONTAINER" sh -c \
    "sed -i -E 's#\\?v=[0-9]{8}[0-9a-z]*##g; s#(src|href)=\"(/assets/[A-Za-z0-9_.-]+\\.js)\"#\\1=\"\\2?v=$tag\"#g' /app/static/index.html"
  frontend_patched || return 1
  c_ok "前端授权告警已修补并刷新资源版本（tag=$tag）"
}

apply_changes() {
  require_root; require_docker; check_containers
  MGT_TMP="$(mktemp)"
  if ! fetch_mgt "$MGT_TMP"; then
    die "远程获取 mgt 失败：$MGT_URL"
  fi
  replace_binary "$MGT_TMP" || die "替换二进制失败"
  run_db_sql || die "数据库命令失败；当前二进制保持已替换状态"
  patch_frontend || die "前端授权告警修补失败"
  docker restart "$CONTAINER" >/dev/null
  wait_health || c_warn "健康检查超时，请查看：docker logs --tail 200 $CONTAINER"
  c_ok "替换和数据库命令执行完成"
}

show_status() {
  require_docker
  echo "mgt container : $CONTAINER ($(container_running "$CONTAINER" && echo running || echo stopped))"
  echo "postgres      : $PG_CONTAINER ($(container_running "$PG_CONTAINER" && echo running || echo stopped))"
  echo "mgt source    : $MGT_URL"
  echo "license source: $LICENSE_URL"
  echo "manager source: $MANAGER_INFO_URL"
  echo "frontend patch: $(frontend_patched && echo applied || echo missing)"
  echo "health        : $(health_ok && echo ok || echo unavailable)"
}

interactive_menu() {
  local choice
  while true; do
    printf '\n================ SafeLine mgt 替换工具 ================\n'
    printf '  1) 远程获取 mgt 和数据库文件并执行\n'
    printf '  2) 查看状态\n'
    printf '  0) 退出\n'
    printf '请选择 [0-2]: '
    if ! read_input choice; then printf '\n'; return 0; fi
    case "$choice" in
      1)
        apply_changes
        ;;
      2)
        show_status ;;
      0) return 0 ;;
      *) c_warn "无效选择，请输入 0-2" ;;
    esac
  done
}

parse_args "$@"
case "$ACTION" in
  apply)    apply_changes ;;
  status)   show_status ;;
  *)        usage; exit 2 ;;
esac
