#!/usr/bin/env bash
# OpenIM + LiveKit 测试环境自测脚本
# 覆盖：管理员令牌、注册两个测试账号、签发用户令牌（登录）、
#       互发文字/语音/文件消息、消息回查、LiveKit 一对一房间双端进房与媒体发布订阅。
# 用法: ./scripts/selftest.sh [服务器内网IP]
set -uo pipefail
cd "$(dirname "$0")/.."

# ---------- 参数与公共函数 ----------
env_get() { grep -E "^$1=" .env | head -1 | cut -d= -f2- | tr -d '"'; }

IP="${1:-$(env_get SERVER_IP)}"
IP="${IP:-127.0.0.1}"
API="http://${IP}:10002"
SECRET="$(env_get OPENIM_SECRET)"; SECRET="${SECRET:-openIM123}"
MINIO_AK="$(env_get MINIO_ACCESS_KEY_ID)"; MINIO_AK="${MINIO_AK:-root}"
MINIO_SK="$(env_get MINIO_SECRET_ACCESS_KEY)"; MINIO_SK="${MINIO_SK:-openIM123}"
MINIO_PORT="$(env_get MINIO_PORT)"; MINIO_PORT="${MINIO_PORT:-10005}"
LK_KEY="$(env_get LIVEKIT_API_KEY)"
LK_SECRET="$(env_get LIVEKIT_API_SECRET)"
LK_PORT="$(env_get LIVEKIT_PORT)"; LK_PORT="${LK_PORT:-17880}"
LK_HTTP="http://${IP}:${LK_PORT}"
LK_WS="ws://${IP}:${LK_PORT}"
LK_CLI_IMAGE="livekit/livekit-cli:v2.18.2"
MC_IMAGE="minio/mc:latest"

FAILED=0
pass() { echo "  [通过] $1"; }
fail() { echo "  [失败] $1"; FAILED=1; }
note() { echo "  [提示] $1"; }

# 优先用宿主机 curl；没有则在容器里跑 curl
# OpenIM 所有 API 都要求 operationID 请求头（链路追踪用，每次请求唯一即可）
new_opid() { echo "selftest-$(date +%s%N 2>/dev/null || date +%s)-$RANDOM"; }
if command -v curl >/dev/null 2>&1; then
  http_post() { curl -s -m 15 -X POST "$1" -H 'Content-Type: application/json' -H "operationID: $(new_opid)" ${TOKEN:+-H "token: $TOKEN"} -d "$2"; }
else
  note "宿主机无 curl，使用容器化 curl"
  http_post() { docker run --rm --network host curlimages/curl:8.10.1 -s -m 15 -X POST "$1" -H 'Content-Type: application/json' -H "operationID: $(new_opid)" ${TOKEN:+-H "token: $TOKEN"} -d "$2"; }
fi

json_ok() { echo "$1" | tr -d ' \n' | grep -q '"errCode":0'; }
json_field() { echo "$1" | sed -n "s/.*\"$2\":\"\([^\"]*\)\".*/\1/p" | head -1; }

echo "==============================================="
echo " OpenIM 自测  (API: $API)"
echo "==============================================="

# ---------- 1. 管理员令牌 ----------
echo "1. 获取管理员令牌（/auth/get_admin_token）"
TOKEN=""
resp=$(http_post "$API/auth/get_admin_token" "{\"secret\":\"${SECRET}\",\"userID\":\"imAdmin\"}")
if json_ok "$resp" && [ -n "$(json_field "$resp" token)" ]; then
  TOKEN=$(json_field "$resp" token)
  pass "管理员令牌获取成功"
else
  fail "管理员令牌获取失败: $resp"
  echo "OpenIM API 不可用，终止自测。"; exit 1
fi

# ---------- 2. 注册两个测试账号 ----------
echo "2. 注册测试账号 test001 / test002（/user/user_register）"
resp=$(http_post "$API/user/user_register" "{\"secret\":\"${SECRET}\",\"users\":[{\"userID\":\"test001\",\"nickname\":\"测试一号\"},{\"userID\":\"test002\",\"nickname\":\"测试二号\"}]}")
if json_ok "$resp"; then
  pass "两个测试账号注册成功"
else
  # 重复执行脚本时账号已存在，属于正常情况
  note "注册接口返回非 0（通常是账号已存在，重复自测属正常）: $(echo "$resp" | head -c 200)"
fi

# ---------- 3. 签发用户令牌（登录） ----------
echo "3. 签发用户令牌（/auth/get_user_token，platformID=5）"
resp=$(http_post "$API/auth/get_user_token" "{\"secret\":\"${SECRET}\",\"platformID\":5,\"userID\":\"test001\"}")
T1=$(json_field "$resp" token)
resp=$(http_post "$API/auth/get_user_token" "{\"secret\":\"${SECRET}\",\"platformID\":5,\"userID\":\"test002\"}")
T2=$(json_field "$resp" token)
if [ -n "$T1" ] && [ -n "$T2" ]; then
  pass "test001 / test002 均拿到用户令牌（登录链路可用）"
else
  fail "用户令牌签发失败"
fi

# ---------- 4. 互发文字消息 ----------
MARK="selftest-$(date +%s)"
echo "4. test001 -> test002 发送文字消息（/msg/send_msg）"
resp=$(http_post "$API/msg/send_msg" "{\"sendID\":\"test001\",\"recvID\":\"test002\",\"senderNickname\":\"测试一号\",\"senderPlatformID\":5,\"content\":{\"content\":\"自测文字 ${MARK}\"},\"contentType\":101,\"sessionType\":1}")
if json_ok "$resp"; then pass "文字消息发送成功"; else fail "文字消息发送失败: $(echo "$resp" | head -c 200)"; fi

echo "   test002 -> test001 回复文字消息"
resp=$(http_post "$API/msg/send_msg" "{\"sendID\":\"test002\",\"recvID\":\"test001\",\"senderNickname\":\"测试二号\",\"senderPlatformID\":5,\"content\":{\"content\":\"收到 ${MARK}\"},\"contentType\":101,\"sessionType\":1}")
if json_ok "$resp"; then pass "回复消息发送成功"; else fail "回复消息发送失败: $(echo "$resp" | head -c 200)"; fi

# ---------- 5. 上传文件到 MinIO 并发送语音/文件消息 ----------
echo "5. 上传附件到 MinIO 并发送语音、文件消息"
mkdir -p .selftest
echo "openim selftest file ${MARK}" > .selftest/selftest-file.txt
head -c 4096 /dev/urandom 2>/dev/null > .selftest/selftest-voice.amr || printf 'fake-amr-%s' "$MARK" > .selftest/selftest-voice.amr
FILE_SIZE=$(wc -c < .selftest/selftest-file.txt | tr -d ' ')
VOICE_SIZE=$(wc -c < .selftest/selftest-voice.amr | tr -d ' ')

MCHOST="http://${MINIO_AK}:${MINIO_SK}@${IP}:${MINIO_PORT}"
mc_run() { docker run --rm --network host -v "$PWD/.selftest:/data" -e "MC_HOST_local=${MCHOST}" --entrypoint mc "$MC_IMAGE" "$@"; }

if mc_run cp "/data/selftest-file.txt" "local/openim/selftest/${MARK}/" >/dev/null 2>&1 \
&& mc_run cp "/data/selftest-voice.amr" "local/openim/selftest/${MARK}/" >/dev/null 2>&1; then
  FILE_URL=$(mc_run share download --expire 168h "local/openim/selftest/${MARK}/selftest-file.txt" 2>/dev/null | grep -o 'http[^ ]*' | head -1)
  VOICE_URL=$(mc_run share download --expire 168h "local/openim/selftest/${MARK}/selftest-voice.amr" 2>/dev/null | grep -o 'http[^ ]*' | head -1)
  if [ -n "$FILE_URL" ] && [ -n "$VOICE_URL" ]; then
    pass "附件已上传 MinIO 并生成访问链接"

    resp=$(http_post "$API/msg/send_msg" "{\"sendID\":\"test001\",\"recvID\":\"test002\",\"senderNickname\":\"测试一号\",\"senderPlatformID\":5,\"content\":{\"uuid\":\"\",\"soundPath\":\"\",\"sourceUrl\":\"${VOICE_URL}\",\"dataSize\":${VOICE_SIZE},\"duration\":3},\"contentType\":103,\"sessionType\":1}")
    if json_ok "$resp"; then pass "语音消息发送成功"; else fail "语音消息发送失败: $(echo "$resp" | head -c 200)"; fi

    resp=$(http_post "$API/msg/send_msg" "{\"sendID\":\"test001\",\"recvID\":\"test002\",\"senderNickname\":\"测试一号\",\"senderPlatformID\":5,\"content\":{\"filePath\":\"\",\"uuid\":\"\",\"sourceUrl\":\"${FILE_URL}\",\"fileName\":\"selftest-file.txt\",\"fileSize\":${FILE_SIZE}},\"contentType\":105,\"sessionType\":1}")
    if json_ok "$resp"; then pass "文件消息发送成功"; else fail "文件消息发送失败: $(echo "$resp" | head -c 200)"; fi
  else
    fail "MinIO 生成下载链接失败"
  fi
else
  fail "附件上传 MinIO 失败（检查 minio 容器与 ${IP}:${MINIO_PORT} 连通性）"
fi

# ---------- 6. 消息回查 ----------
echo "6. 消息落库回查（/msg/search_msg）"
sleep 2
resp=$(http_post "$API/msg/search_msg" "{\"sendID\":\"test001\",\"recvID\":\"test002\",\"contentType\":0,\"sendTime\":\"\",\"sessionType\":1,\"pagination\":{\"pageNumber\":1,\"showNumber\":20}}")
if json_ok "$resp" && echo "$resp" | grep -q "$MARK"; then
  pass "文字消息已落库可回查"
else
  fail "消息回查未找到刚发送的内容: $(echo "$resp" | head -c 200)"
fi
if echo "$resp" | grep -q "selftest-voice.amr"; then pass "语音消息已落库可回查"; else fail "语音消息回查未找到"; fi
if echo "$resp" | grep -q "selftest-file.txt"; then pass "文件消息已落库可回查"; else fail "文件消息回查未找到"; fi

# ---------- 7. LiveKit 一对一语音房间 ----------
echo "==============================================="
echo " LiveKit 自测  (信令: $LK_WS)"
echo "==============================================="
ROOM="selftest-$(date +%s)"
echo "7. 两名用户进入同一房间，主叫发布音频，验证双向可见"

cleanup_lk() { docker rm -f lk-caller lk-callee >/dev/null 2>&1 || true; }
trap cleanup_lk EXIT
cleanup_lk

lk() { docker run --rm --network host "$LK_CLI_IMAGE" --url "$1" --api-key "$LK_KEY" --api-secret "$LK_SECRET" "${@:2}"; }

# 主叫发布仓库内置的 30 秒 opus 测试音频（--publish-demo 只有视频轨，这里用真实音频）
docker run -d --name lk-caller --network host -v "$PWD/scripts/assets:/data:ro" "$LK_CLI_IMAGE" \
  --url "$LK_WS" --api-key "$LK_KEY" --api-secret "$LK_SECRET" \
  room join --identity caller --publish /data/test-tone.ogg "$ROOM" >/dev/null 2>&1
# 被叫端必须显式开自动订阅（livekit-cli v2 的 --auto-subscribe 默认为 false）
docker run -d --name lk-callee --network host "$LK_CLI_IMAGE" \
  --url "$LK_WS" --api-key "$LK_KEY" --api-secret "$LK_SECRET" \
  room join --identity callee --auto-subscribe "$ROOM" >/dev/null 2>&1

sleep 10
plist=$(lk "$LK_HTTP" room participants list "$ROOM" 2>&1)
if echo "$plist" | grep -q "caller" && echo "$plist" | grep -q "callee"; then
  pass "双方已进入同一房间（房间: $ROOM）"
else
  fail "房间成员不符，list 输出: $(echo "$plist" | head -c 300)"
fi

# 主叫应已发布音频轨（kind=AUDIO）
if echo "$plist" | grep -qi "AUDIO"; then
  pass "主叫音频轨道已发布"
else
  note "未从成员列表确认音频轨道（可能输出格式差异），请人工核对: lk room participants list"
fi

# 被叫端日志应出现 track subscribed（媒体链路打通的直接证据）
if docker logs lk-callee 2>&1 | grep -qi "track subscribed"; then
  pass "被叫端已订阅到主叫的音频轨道（一对一语音链路打通）"
else
  fail "被叫端日志未见轨道订阅记录: $(docker logs lk-callee 2>&1 | tail -5)"
fi
cleanup_lk
trap - EXIT

# ---------- 汇总 ----------
echo "==============================================="
if [ "$FAILED" = "0" ]; then
  echo " 自测全部通过 ✔"
  echo " 客户端接入参数："
  echo "   OpenIM API : $API"
  echo "   OpenIM WS  : ws://${IP}:10001"
  echo "   LiveKit WS : $LK_WS  (key: $LK_KEY)"
else
  echo " 自测存在失败项，请根据上方 [失败] 排查（docker compose logs -f openim-server / livekit）"
  exit 1
fi
