#!/bin/bash
# verify-workshop.sh — 自檢（唔准 curl|bash：個檔要落地＋配 sha256sum -c 先跑）。
# 用法：bash verify-workshop.sh   （印 pass/fail；尾行總結，有硬失敗 exit 1）
set -uo pipefail
HERE="$(cd "$(dirname "$0")" && pwd)"
. "$HERE/env.sh" 2>/dev/null || { echo "❌ 冇 env.sh"; exit 1; }
WATCHER="$ARENA_HOME/scripts/watcher.py"
FAIL=0; say(){ printf '%s\n' "$*"; }; ok(){ say "✅ $*"; }; bad(){ say "❌ $*"; FAIL=$((FAIL+1)); }

say "========== 0. 檔案完整性（sha256sum -c）=========="
if [ -f "$HERE/sha256sums.txt" ]; then ( cd "$HERE" && sha256sum -c sha256sums.txt >/dev/null 2>&1 ) && ok "sha256 全過" || bad "sha256 有唔對（跑 bootstrap.sh 重拉）"; else bad "冇 sha256sums.txt"; fi

say "========== 1. watcher.py 版本 =========="
MD5_PRISTINE=1ca46931b51912e70094a161f3c06f25   # v093 線：原版 v0.93（＝主持 8530 釘嘅值；canonical）
MD5_HARD=52208202b2654ff2e9bae5048e80347d      # v093 線：＋3 個 hardening hunk
MD5_DISPLAY=7011c3fe443b01ed5478d27ecab1fb36   # v093 線：＋DISPLAY 動態（主持 8568）
MD5_BOTH=0edcda0bdb365dd2e1eacb30f30d680f      # v093 線：＋兩者（單一合併 patch --all）
MD5_V094_BASE=cb11d45f639722650e3ebe156897da51 # v094 線：新 live（v0.93＋召回＋ARENA_PW）——同 v093 線唔准混套
MD5_V094_HARD=fb89906b189f331b0c3f33c9bbaa5e0c     # v094 線＋hardening
MD5_V094_DISPLAY=02e47043f057b16aa6e827abe8dc7482  # v094 線＋DISPLAY
MD5_V094_BOTH=18427ef9ff5e86032a66adaf8a8d0ea6     # v094 線＋兩者
if [ -f "$WATCHER" ]; then
  M=$(md5sum "$WATCHER" | awk '{print $1}'); BASE="${WATCHER%.py}"
  case "$M" in
    "$MD5_PRISTINE") ok "md5 $M（v093 線原版，canonical）" ;;
    "$MD5_HARD")    [ -f "$BASE.hardening" ] && { ok "md5 $M（v093 線：已套 hardening；紀錄 $BASE.hardening）"; grep -q "$M" "$BASE.hardening" || bad "紀錄同 md5 唔一致"; } || bad "md5 係 v093 硬化版但冇紀錄（跑 bash hardening-apply.sh 重做）" ;;
    "$MD5_DISPLAY") [ -f "$BASE.display" ] && { ok "md5 $M（v093 線：已套 DISPLAY 動態；紀錄 $BASE.display）"; grep -q "$M" "$BASE.display" || bad "紀錄同 md5 唔一致"; } || bad "md5 係 v093 DISPLAY 版但冇紀錄" ;;
    "$MD5_BOTH")    [ -f "$BASE.hardening+display" ] && { ok "md5 $M（v093 線：hardening＋DISPLAY）"; grep -q "$M" "$BASE.hardening+display" || bad "紀錄同 md5 唔一致"; } || bad "md5 係 v093 兩者合一版但冇紀錄" ;;
    "$MD5_V094_BASE")    ok "md5 $M（v094 線原版：v0.93＋召回＋ARENA_PW；hardening-apply.sh 會自動認呢個基底）" ;;
    "$MD5_V094_HARD")    [ -f "$BASE.hardening" ] && { ok "md5 $M（v094 線：已套 hardening）"; grep -q "$M" "$BASE.hardening" || bad "紀錄同 md5 唔一致"; } || bad "md5 係 v094 硬化版但冇紀錄" ;;
    "$MD5_V094_DISPLAY") [ -f "$BASE.display" ] && { ok "md5 $M（v094 線：已套 DISPLAY 動態）"; grep -q "$M" "$BASE.display" || bad "紀錄同 md5 唔一致"; } || bad "md5 係 v094 DISPLAY 版但冇紀錄" ;;
    "$MD5_V094_BOTH")    [ -f "$BASE.hardening+display" ] && { ok "md5 $M（v094 線：hardening＋DISPLAY）"; grep -q "$M" "$BASE.hardening+display" || bad "紀錄同 md5 唔一致"; } || bad "md5 係 v094 兩者合一版但冇紀錄" ;;
    *) bad "md5 $M 唔係任何認可狀態（v093 四態／v094 四態）" ;;
  esac
else bad "冇 $WATCHER（跑 setup.sh：會 install 去標準位置）"; fi

say "========== 2. X display =========="
G=$(DISPLAY=$DISPLAY xdotool getdisplaygeometry 2>/dev/null || echo "")
W=${G%% *}; H=${G##* }
if [ -n "$G" ] && [ "${W:-0}" -ge 1408 ] && [ "${H:-0}" -ge 2700 ]; then ok "DISPLAY $DISPLAY = $G（絕對下限 1408x2700 OK）"; else bad "DISPLAY $DISPLAY = '$G'（硬下限 1408x2700；1408＝外框硬鎖寬、2700＝3x900）"; fi
[ "$G" = "${SCREEN_W} ${SCREEN_H}" ] && ok "同 env.sh 一致（${SCREEN_W}x${SCREEN_H}）" || say "ℹ️  X=$G vs env ${SCREEN_W}x${SCREEN_H}（只要 ≥ 硬下限就唔算 fail）"
[ "${SCREEN_W:-0}" -ge 1408 ] && [ $(( 3 * 900 )) -le "${SCREEN_H:-0}" ] && ok "算術：SCREEN_W≥1408 ∧ 3x900≤SCREEN_H" || bad "算術唔過：SCREEN_W=${SCREEN_W}（要 ≥1408）、SCREEN_H=${SCREEN_H}（要 ≥2700）"

say "========== 3. watcher processes =========="
N=0; for s in $SEATS; do p="$ARENA_HOME/run/watcher-$s.pid"; [ -f "$p" ] && kill -0 "$(cat "$p")" 2>/dev/null && N=$((N+1)); done
[ "$N" -eq "$(echo $SEATS | wc -w)" ] && ok "$N／$(echo $SEATS | wc -w) 個 watcher 在生（PID 檔）" || bad "$N／$(echo $SEATS | wc -w) 個 watcher 在生（缺＝睇 logs/watcher-*.console.log）"

say "========== 4. browser profiles =========="
for s in ${SEATS:-}; do [ -d "$ARENA_HOME/local-profiles/$s" ] && ok "profile $s" || bad "profile $s 冇（watcher 第一次跑會自建）"; done

say "========== 5. VNC stack（mode=${VNC_MODE:-xvnc}；ss 查 listening socket；唔用 pgrep，免 self-match）=========="
ss -ltn 2>/dev/null | grep -qE "[:.]$RFB_PORT\b" && ok "VNC RFB :$RFB_PORT（mode=${VNC_MODE:-xvnc}）" || bad "VNC RFB :$RFB_PORT 唔 listening（睇 logs/xvnc.log 或 logs/x11vnc.log）"
ss -ltn 2>/dev/null | grep -qE "[:.]$WS_PORT\b" && ok "websockify :$WS_PORT（127.0.0.1）" || bad "websockify :$WS_PORT 唔 listening"
if [ -f "$ARENA_HOME/run/tunnel.pid" ] && kill -0 "$(cat "$ARENA_HOME/run/tunnel.pid")" 2>/dev/null; then ok "ssh tunnel PID 在生（remote :$TUNNEL_PORT）"; else bad "ssh tunnel 唔在生（run/tunnel.pid）"; fi
ss -ltn 2>/dev/null | grep -q ":0.0.0.0:$WS_PORT\|:::$WS_PORT" && bad "websockify 綁咗 0.0.0.0（要 127.0.0.1）" || ok "websockify 冇綁 0.0.0.0"

say "========== 5b. env 自撞檢查（同一主機內；A6/8606 硬 gate 口徑）=========="
[ -n "${RFB_PORT:-}" ] && [ -n "${WS_PORT:-}" ] && [ -n "${TUNNEL_PORT:-}" ] && ok "三個 port 都有值（$RFB_PORT／$WS_PORT／$TUNNEL_PORT）" || bad "RFB/WS/TUNNEL 有 port 未設"
[ "${RFB_PORT:-0}" -ne "${WS_PORT:-0}" ] && [ "${RFB_PORT:-0}" -ne "${TUNNEL_PORT:-0}" ] && [ "${WS_PORT:-0}" -ne "${TUNNEL_PORT:-0}" ] \
  && ok "同一主機 RFB≠WS≠TUNNEL（自撞檢查）" || bad "同一主機 port 自撞：RFB=$RFB_PORT WS=$WS_PORT TUNNEL=$TUNNEL_PORT"
[ -n "${VNC_PATH:-}" ] && ok "VNC_PATH 有值（$VNC_PATH）" || bad "VNC_PATH 未設"
if [ -f "$ARENA_HOME/seats.conf" ]; then
  DUP=$(awk '!/^#/ && NF>=5 {print $2}' "$ARENA_HOME/seats.conf" | sort | uniq -d | tr '\n' ' ')
  [ -z "$DUP" ] && ok "本機 seats.conf profile 無重複" || bad "本機 seats.conf profile 重複：$DUP（跨 Muse 都要唯一）"
fi

say "========== 6. 開機自起 =========="
crontab -l 2>/dev/null | grep -q 'start-all.sh' && ok "@reboot 已裝" || bad "冇 @reboot（跑 install-boot.sh；呢部機 systemd 用唔到）"
[ -f "$ARENA_HOME/run/keep.pid" ] && kill -0 "$(cat "$ARENA_HOME/run/keep.pid")" 2>/dev/null && ok "keep loop 在生" || bad "keep loop 唔在生（跑 start-all.sh）"

say "========== 7. RAM =========="
free -h | head -2
A=$(free -m | awk '/^Mem:/{print $7}'); [ "${A:-0}" -lt "$RAM_MIN_MB" ] && bad "可用 RAM ${A}MB < ${RAM_MIN_MB}MB（keep 會輪流重啟 browser）" || ok "可用 RAM ${A}MB"

say "========== 8. viewport（要人手睇一眼）=========="
say "請人手確認：browser content 係咪 1400x900（外框 1408x900）。"

say "========== 9. Browser 排位（pass/fail）=========="
i=0; MISS=0
for w in $(DISPLAY=$DISPLAY xdotool search --onlyvisible --name "${WINDOW_NAME:-Arena}" 2>/dev/null); do
  POS=$(DISPLAY=$DISPLAY xdotool getwindowgeometry "$w" 2>/dev/null | awk '/Position/{print $2}')
  y=${POS#*,}
  if [ "$y" = "$((i*900))" ]; then ok "window $w y=$y"; else bad "window $w y=$y（期望 $((i*900))）"; MISS=1; fi
  i=$((i+1))
done
[ "$i" -eq 0 ] && bad "搵唔到 browser window（搵嘅 WM_NAME 子串＝${WINDOW_NAME:-Arena}，同 watcher 一致）"
say ""
say "========== 總結：硬失敗 $FAIL 項 =========="
[ "$FAIL" -eq 0 ] && say "驗證完畢（全過）" || say "驗證完畢（有 $FAIL 項要修）"
exit $([ "$FAIL" -eq 0 ] && echo 0 || echo 1)
