#!/usr/bin/env bash
# sema cloud smoke 的 service 半场(§3d:装后即验+可随时手动跑;全绿才算"部署成功")。
# 两种用法:
#   本机 project 模式: smoke.sh --project=NAME            (全套:数据面直验+引擎)
#   远程 URL 模式:     smoke.sh --url=http://host:8090 --token=T   (引擎面:health/auth/真任务)
# 每项 OK/FAIL 一行;FAIL 带原因+建议(§3d-2 报错规范);exit 0=全绿。
set -u

PROJECT="" URL="" TOKEN=""
for a in "$@"; do case "$a" in
  --project=*) PROJECT="${a#*=}" ;;
  --url=*) URL="${a#*=}" ;;
  --token=*) TOKEN="${a#*=}" ;;
esac; done

FAILS=0
ok()   { printf '[OK  ] %s\n' "$1"; }
fail() { printf '[FAIL] %s — %s\n      ↳ 建议:%s\n' "$1" "$2" "$3"; FAILS=$((FAILS+1)); }

if [ -n "$PROJECT" ]; then
  ENVF="$HOME/.sema-up/$PROJECT/.env"
  [ -r "$ENVF" ] || { echo "找不到 $ENVF(先跑 sema-up.sh)"; exit 1; }
  . "$ENVF"
  TOKEN="${TOKEN:-${SERVICE_AUTH_TOKEN:-}}"
  # 引擎面只在带 agent 的档默认探(data-center 没有引擎,探 8090 必假红);--url 显式给了照跑
  case "${DEPLOY_PROFILE:-full-single}" in
    full-single|local-plus) URL="${URL:-http://127.0.0.1:${SERVER_PORT:-8090}}" ;;
  esac

  # 数据面直验按容器在场性闸:local-plus/外接 PG/外接 S3 时对应容器不存在,不该假红
  HAS_PG=false; docker ps --format '{{.Names}}' 2>/dev/null | grep -qx "${PROJECT}-pg-1" && HAS_PG=true
  HAS_S3=false; docker ps --format '{{.Names}}' 2>/dev/null | grep -qx "${PROJECT}-minio-1" && HAS_S3=true
  if $HAS_PG; then
  # ① PG:连通+durable 前提(SHOW 一个调优参数=配置真生效)
  SB=$(docker exec "${PROJECT}-pg-1" psql -U sema -d sema -tAc "SHOW shared_buffers" 2>/dev/null)
  [ -n "$SB" ] && ok "PG 连通(shared_buffers=$SB)" || fail "PG" "容器不可达或未就绪" "docker compose -p $PROJECT logs pg"
  # ② PG 读写往返(临时表,不留痕)
  RW=$(docker exec "${PROJECT}-pg-1" psql -U sema -d sema -tAc "CREATE TEMP TABLE _smoke(v int); INSERT INTO _smoke VALUES (7); SELECT v FROM _smoke" 2>/dev/null | tail -1)
  [ "$RW" = "7" ] && ok "PG 读写往返" || fail "PG 读写" "写入/读回失败" "看 PG 日志与磁盘空间"
  fi
  if $HAS_S3; then
  # ③ S3:put/get/rm 往返(§3c 外接校验同款动作)
  # --entrypoint sh:minio/mc 镜像 entrypoint 是 mc,直接给 sh -c 会拼成 "mc sh -c"(真机验出);
  # 哨兵判定(cat 回读值核对后 echo),不依赖各 mc 子命令的输出行为
  S3RT=$(docker run --rm --entrypoint sh --network "${PROJECT}_default" -e MC_HOST_x="http://sema:${MINIO_PASSWORD}@minio:9000" "${MC_IMAGE:-minio/mc:latest}" -c 'echo smoke > /tmp/s && mc cp /tmp/s x/blobs/_smoke >/dev/null && [ "$(mc cat x/blobs/_smoke)" = smoke ] && mc rm x/blobs/_smoke >/dev/null && echo S3_ROUNDTRIP_OK' 2>/dev/null | tail -1)
  [ "$S3RT" = "S3_ROUNDTRIP_OK" ] && ok "S3 put/get/rm 往返" || fail "S3" "对象读写失败" "docker compose -p $PROJECT logs minio"
  # ③b 三个约定桶逐个点名(data-center 验收:ls 行数≠桶名对——错桶也会凑数)
  BKTS=$(docker run --rm --entrypoint sh --network "${PROJECT}_default" -e MC_HOST_x="http://sema:${MINIO_PASSWORD}@minio:9000" "${MC_IMAGE:-minio/mc:latest}" -c 'for b in workspaces blobs backups; do mc ls "x/$b" >/dev/null 2>&1 && printf "%s " "$b"; done' 2>/dev/null)
  [ "$BKTS" = "workspaces blobs backups " ] && ok "桶就绪(workspaces/blobs/backups)" || fail "桶" "缺桶:应有 workspaces/blobs/backups,实有 ${BKTS:-无}" "重跑 sema-up.sh(建桶幂等)"
  fi
  # 外接数据面(--pg-url/--s3-endpoint):有 endpoint 就远程直验(证书不校验)
  if [ -n "${AGENT_PG_HOST:-}" ] && ! $HAS_PG && [ "${DEPLOY_PROFILE:-}" != "local-plus" ]; then
    EPG=$(docker run --rm "${PG_IMAGE:-postgres:17}" psql "postgres://${AGENT_PG_USER:-sema}:${PG_PASSWORD}@${AGENT_PG_HOST}:${AGENT_PG_PORT:-5432}/${AGENT_PG_DATABASE:-sema}" -tAc 'select 1' 2>/dev/null | tail -1)
    [ "$EPG" = "1" ] && ok "外接 PG 连通(${AGENT_PG_HOST})" || fail "外接 PG" "select 1 失败" "核对端点/凭据/网络(容器出网)"
  fi
  if [ -n "${AGENT_MINIO_ENDPOINT:-}" ] && ! $HAS_S3 && [ "${DEPLOY_PROFILE:-}" != "local-plus" ]; then
    ES3=$(docker run --rm --entrypoint sh -e MC_HOST_x="$(printf '%s' "$AGENT_MINIO_ENDPOINT" | sed "s#://#://${AGENT_MINIO_USER:-sema}:${MINIO_PASSWORD}@#")" "${MC_IMAGE:-minio/mc:latest}" -c 'mc --insecure ls x/ >/dev/null 2>&1 && echo OK' 2>/dev/null | tail -1)
    [ "$ES3" = "OK" ] && ok "外接 S3 连通(${AGENT_MINIO_ENDPOINT})" || fail "外接 S3" "ls 失败" "核对 endpoint/密钥(--insecure 已忽略证书)"
  fi
  # ④a 配置中心(registry=true 时)
  if [ "${REGISTRY_ENABLED:-false}" = "true" ]; then
    RC=$(curl -m5 -s -o /dev/null -w '%{http_code}' "http://127.0.0.1:${REGISTRY_PORT:-3000}/api/healthz")
    [ "$RC" = "200" ] && ok "配置中心 healthz(store 可达)" || fail "配置中心" "healthz HTTP $RC(200=store 可达;503=store 挂)" "docker compose -p $PROJECT logs registry"
  fi
  # ④b Gitea(git=provision 时):healthz + token 真 API 往返
  if [ "${GIT_MODE:-none}" = "provision" ]; then
    GC=$(curl -m5 -s -o /dev/null -w '%{http_code}' "http://127.0.0.1:${GIT_HTTP_PORT:-3300}/api/healthz")
    [ "$GC" = "200" ] && ok "Gitea healthz" || fail "Gitea" "healthz HTTP $GC" "docker compose -p $PROJECT logs git"
    if [ -n "${GIT_ADMIN_TOKEN:-}" ]; then
      GU=$(curl -m5 -s -H "Authorization: token $GIT_ADMIN_TOKEN" "http://127.0.0.1:${GIT_HTTP_PORT:-3300}/api/v1/user" | python3 -c 'import json,sys
try: print(json.load(sys.stdin).get("login",""))
except Exception: print("")' 2>/dev/null)
      [ "$GU" = "${GIT_ADMIN_USER:-sema}" ] && ok "Gitea API token(user=$GU)" || fail "Gitea token" "token 验证失败" "token 可能被手动吊销;删 .env 的 GIT_ADMIN_TOKEN 行后重跑 sema-up.sh 再生成"
    fi
  fi
fi

if [ -n "$URL" ]; then
  # ④ /health
  H=$(curl -m5 -s "$URL/health" | head -c 200)
  case "$H" in *'"status":"ok"'*) ok "/health($URL)" ;; *) fail "/health" "无响应或非 ok:${H:-空}" "服务没起/端口不对/防火墙;docker logs 或 systemctl 看进程" ;; esac
  if [ -n "$TOKEN" ]; then
    # ⑤ auth 门:无 token 必须 401(部署安全底线)
    C=$(curl -m5 -s -o /dev/null -w '%{http_code}' -X POST "$URL/v1/tasks" -H "Content-Type: application/json" -d '{}')
    [ "$C" = "401" ] && ok "auth 门(无 token=401)" || fail "auth 门" "无 token 返回 $C(应 401)" "SERVICE_AUTH_TOKEN 未生效=公网裸奔,立刻查 env"
    # ⑥ 真任务 1-turn(同时是模型面探针:402=余额,401/403=key,超时=网关不通——欠费实验收编)
    R=$(curl -m 120 -s -X POST "$URL/v1/tasks" -H "Authorization: Bearer $TOKEN" -H "Content-Type: application/json" -d '{"objective":"请只回答:ok"}')
    ST=$(printf '%s' "$R" | python3 -c 'import json,sys
try: d=json.load(sys.stdin); t=d.get("terminal") or {}; print(t.get("kind",""), "|", str((t.get("code") or "") + " " + (d.get("error") or d.get("result") or ""))[:120])
except Exception: print("parse-fail |", "")' 2>/dev/null)
    case "$ST" in
      completed*) ok "真任务 1-turn(模型面通)" ;;
      *nsufficient*|*402*) fail "真任务" "模型 API 余额耗尽(402)" "充值或换 MODEL_API_KEY;余额恢复后重跑 smoke" ;;
      *401*|*403*) fail "真任务" "模型 API key 无效" "检查 MODEL_API_KEY" ;;
      *) fail "真任务" "terminal=${ST:-无响应}" "docker logs 看 done 帧 terminal.message(引擎会全文上流)" ;;
    esac
  fi
fi

[ $FAILS -eq 0 ] && { echo "✅ smoke 全绿"; exit 0; } || { echo "⛔ $FAILS 项失败"; exit 1; }
