#!/usr/bin/env bash
# Open API 接入验收脚本
#
# 对接方（官网下载）：验签自检 + 真实环境 HTTP 冒烟（需 openssl/jq/curl/python3）
# 仓库内运行：额外做 Go 单测 / merchant 编译 / 路由注册检查
#
# 用法:
#   chmod +x anti_open_api_acceptance.sh
#   ACCESS_KEY_ID=xxx ACCESS_KEY_SECRET=yyy ./anti_open_api_acceptance.sh
#   BASE_URL=https://mc.weige.pub ACCESS_KEY_ID=xxx ACCESS_KEY_SECRET=yyy \
#     PRODUCT_ID=1001 ./anti_open_api_acceptance.sh
#
# 可选环境变量:
#   BASE_URL           默认 https://mc.weige.pub
#   ACCESS_KEY_ID      商户后台「防伪设置」AccessKeyID
#   ACCESS_KEY_SECRET  AccessKeySecret
#   PRODUCT_ID         若设置则尝试创建批次（会扣额度，慎用）
#   SKIP_WRITE=1       仅只读探测（列表），不创建/修改/删除
#   KEEP_FIXTURES=1    保留冒烟创建的经销商/门店，不清理

set -euo pipefail

SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
IWALL=""
if [[ -d "$SCRIPT_DIR/../iwall/common/logic/antiOpen" ]]; then
  IWALL="$(cd "$SCRIPT_DIR/../iwall" && pwd)"
fi
BASE_URL="${BASE_URL:-https://mc.weige.pub}"
BASE_URL="${BASE_URL%/}"

GREEN='\033[0;32m'
RED='\033[0;31m'
YELLOW='\033[0;33m'
BLUE='\033[0;34m'
NC='\033[0m'

pass=0
fail=0
skip=0
# 命令替换 $(open_curl)/(sign_get) 会进子 shell，HTTP 状态写入文件再读回
OPEN_HTTP_CODE=""
OPEN_HTTP_CODE_FILE="$(mktemp)"
trap 'rm -f "$OPEN_HTTP_CODE_FILE"' EXIT

ok() { echo -e "${GREEN}PASS${NC} $*"; pass=$((pass + 1)); }
bad() { echo -e "${RED}FAIL${NC} $*"; fail=$((fail + 1)); }
warn() { echo -e "${YELLOW}SKIP${NC} $*"; skip=$((skip + 1)); }
section() { echo ""; echo -e "${BLUE}== $* ==${NC}"; }

read_open_http_code() {
  OPEN_HTTP_CODE="$(tr -d '[:space:]' <"$OPEN_HTTP_CODE_FILE" 2>/dev/null || true)"
}

need_cmd() {
  if ! command -v "$1" >/dev/null 2>&1; then
    bad "缺少命令: $1"
    return 1
  fi
  return 0
}

body_sha256() {
  openssl dgst -sha256 -hex 2>/dev/null | awk '{print $NF}'
}

hmac_sha256() {
  local secret="$1"
  openssl dgst -sha256 -hmac "$secret" -hex 2>/dev/null | awk '{print $NF}'
}

open_sign() {
  local method="$1" path="$2" ts="$3" nonce="$4" body_file="${5:-}"
  local body_hash
  if [[ -n "$body_file" && -f "$body_file" ]]; then
    body_hash="$(body_sha256 <"$body_file")"
  else
    body_hash="$(printf '' | body_sha256)"
  fi
  local canon
  canon="$(printf '%s\n%s\n%s\n%s\n%s' "$(printf '%s' "$method" | tr '[:lower:]' '[:upper:]')" "$path" "$ts" "$nonce" "$body_hash")"
  printf '%s' "$canon" | hmac_sha256 "$ACCESS_KEY_SECRET"
}

urlencode() {
  python3 -c 'import sys,urllib.parse; print(urllib.parse.quote(sys.argv[1]))' "$1"
}

open_curl() {
  local method="$1" path="$2" body="${3:-}"
  local ts nonce sig tmp hdrs
  ts="$(date +%s)"
  nonce="n${RANDOM}${RANDOM}"
  tmp="$(mktemp)"
  hdrs="$(mktemp)"
  if [[ -n "$body" ]]; then
    printf '%s' "$body" >"$tmp"
  else
    : >"$tmp"
  fi
  sig="$(open_sign "$method" "$path" "$ts" "$nonce" "$tmp")"
  local args=(-sS -D "$hdrs" -o - -X "$method"
    -H "Content-Type: application/json"
    -H "X-Access-Key: $ACCESS_KEY_ID"
    -H "X-Timestamp: $ts"
    -H "X-Nonce: $nonce"
    -H "X-Signature: $sig"
    "${BASE_URL}${path}")
  if [[ -n "$body" ]]; then
    args+=(--data-binary @"$tmp")
  fi
  local resp
  resp="$(curl "${args[@]}")"
  awk '/^HTTP/{code=$2} END{print code}' "$hdrs" >"$OPEN_HTTP_CODE_FILE"
  rm -f "$tmp" "$hdrs"
  printf '%s' "$resp"
}

json_code() {
  printf '%s' "$1" | jq -r '.code // empty' 2>/dev/null || true
}

expect_biz_ok() {
  local label="$1" resp="$2"
  local code
  read_open_http_code
  code="$(json_code "$resp")"
  # 以业务 code 为准；HTTP 码仅作辅助（子 shell 场景下已写入文件）
  if [[ "$code" == "0" || "$code" == "200" ]]; then
    if [[ -n "$OPEN_HTTP_CODE" && "$OPEN_HTTP_CODE" != "200" ]]; then
      bad "$label http=$OPEN_HTTP_CODE body=${resp:0:200}"
      return 1
    fi
    ok "$label"
    return 0
  fi
  if [[ -n "$OPEN_HTTP_CODE" && "$OPEN_HTTP_CODE" != "200" ]]; then
    bad "$label http=$OPEN_HTTP_CODE body=${resp:0:200}"
    return 1
  fi
  bad "$label biz_code=$code msg=$(printf '%s' "$resp" | jq -r '.msg // empty' 2>/dev/null) body=${resp:0:240}"
  return 1
}

# ===================== 1) 离线 =====================
section "离线 · 依赖命令"
need_cmd openssl || true
need_cmd jq || true
need_cmd curl || true
need_cmd python3 || true
deps_ok=1
for c in openssl jq curl python3; do
  command -v "$c" >/dev/null 2>&1 || deps_ok=0
done
if [[ "$deps_ok" -eq 1 ]]; then
  ok "openssl/jq/curl/python3 可用"
fi
if [[ -n "$IWALL" ]]; then
  if need_cmd go; then
    ok "检测到仓库源码，将执行 Go 离线检查"
  fi
else
  warn "独立运行模式：跳过 Go 单测/编译/路由检查（对接方无需源码）"
fi

section "离线 · 验签算法自检（对照固定向量）"
FIXED_SECRET='test-secret'
FIXED_BODY='{"a":1}'
FIXED_TS='1700000000'
FIXED_NONCE='n1'
FIXED_WANT_HASH='015abd7f5cc57a2dd94b7590f04ad8084273905ee33ec5cebeae62276a97f862'
FIXED_WANT_SIG='65580fd2ee1c01abc2fc49ff04dd858eb7077c6a50694849ebf26a8f02062333'
got_hash="$(printf '%s' "$FIXED_BODY" | body_sha256)"
tmpf="$(mktemp)"; printf '%s' "$FIXED_BODY" >"$tmpf"
_saved_secret="${ACCESS_KEY_SECRET:-}"
ACCESS_KEY_SECRET="$FIXED_SECRET"
got_sig="$(open_sign POST /v1/open/batches "$FIXED_TS" "$FIXED_NONCE" "$tmpf")"
ACCESS_KEY_SECRET="$_saved_secret"
rm -f "$tmpf"
if [[ "$got_hash" == "$FIXED_WANT_HASH" && "$got_sig" == "$FIXED_WANT_SIG" ]]; then
  ok "HMAC-SHA256 规范串与固定向量一致"
else
  bad "验签向量不匹配 hash=$got_hash sig=$got_sig"
fi

if [[ -n "$IWALL" ]]; then
  section "离线 · antiOpen 单测"
  if (cd "$IWALL" && go test ./common/logic/antiOpen/ -count=1 >/dev/null); then
    ok "go test ./common/logic/antiOpen"
  else
    bad "go test antiOpen"
  fi

  section "离线 · merchant 编译"
  if (cd "$IWALL" && go build -o /dev/null ./apps/http/merchant/...); then
    ok "merchant compile"
  else
    bad "merchant compile"
  fi

  section "离线 · Open 路由注册检查"
  ROUTES="$IWALL/apps/http/merchant/internal/handler/routes.go"
  required_paths=(
    '/open/batches'
    '/open/batches/:id'
    '/open/batches/:id/status'
    '/open/batches/:id/code-file'
    '/open/batches/:id/channel-bind'
    '/open/batches/:id/channel-reallocate'
    '/open/channel-reallocates'
    '/open/dealers'
    '/open/dealers/shops'
    '/open/shops'
  )
  missing=0
  for p in "${required_paths[@]}"; do
    if ! grep -Fq "\"$p\"" "$ROUTES"; then
      bad "路由缺失: $p"
      missing=1
    fi
  done
  if [[ "$missing" -eq 0 ]]; then
    ok "Open 路由均已注册 (${#required_paths[@]} 条)"
  fi

  if grep -q '"open"' "$IWALL/apps/http/merchant/internal/handler/route_registry.go" \
    && grep -q 'OpenAuthMiddleware' "$IWALL/apps/http/merchant/internal/svc/serviceContext.go"; then
    ok "OpenAuth 中间件已挂载"
  else
    bad "OpenAuth 中间件未挂载"
  fi
fi

# ===================== 2) 在线 =====================
section "在线 · 环境凭证"
if [[ -z "${ACCESS_KEY_ID:-}" || -z "${ACCESS_KEY_SECRET:-}" ]]; then
  warn "未设置 ACCESS_KEY_ID / ACCESS_KEY_SECRET，跳过真实 HTTP 冒烟"
  warn "示例: ACCESS_KEY_ID=... ACCESS_KEY_SECRET=... $0"
else
  ok "BASE_URL=$BASE_URL  AccessKey=${ACCESS_KEY_ID:0:6}***"

  section "在线 · 错误签名应失败"
  bad_resp="$(
    ts="$(date +%s)"; nonce="bad$RANDOM"
    curl -sS -X GET "${BASE_URL}/v1/open/dealers?page=1&page_size=1" \
      -H "Content-Type: application/json" \
      -H "X-Access-Key: $ACCESS_KEY_ID" \
      -H "X-Timestamp: $ts" \
      -H "X-Nonce: $nonce" \
      -H "X-Signature: deadbeef" || true
  )"
  bad_code="$(json_code "$bad_resp")"
  if [[ -n "$bad_code" && "$bad_code" != "0" && "$bad_code" != "200" ]]; then
    ok "错误签名被拒绝 (code=$bad_code)"
  else
    bad "错误签名未被拒绝: ${bad_resp:0:200}"
  fi

  section "在线 · 只读列表"
  # 签名 PATH 不含 query
  sign_get() {
    local path_no_query="$1" query="$2"
    local ts nonce sig tmp hdrs resp
    ts="$(date +%s)"; nonce="n$RANDOM$RANDOM"; tmp="$(mktemp)"; hdrs="$(mktemp)"; : >"$tmp"
    sig="$(open_sign GET "$path_no_query" "$ts" "$nonce" "$tmp")"
    resp="$(curl -sS -D "$hdrs" -o - -X GET "${BASE_URL}${path_no_query}${query}" \
      -H "Content-Type: application/json" \
      -H "X-Access-Key: $ACCESS_KEY_ID" \
      -H "X-Timestamp: $ts" \
      -H "X-Nonce: $nonce" \
      -H "X-Signature: $sig")"
    awk '/^HTTP/{code=$2} END{print code}' "$hdrs" >"$OPEN_HTTP_CODE_FILE"
    rm -f "$tmp" "$hdrs"
    printf '%s' "$resp"
  }

  resp="$(sign_get /v1/open/dealers '?page=1&page_size=5')"
  expect_biz_ok "GET /v1/open/dealers" "$resp" || true

  resp="$(sign_get /v1/open/shops '?page=1&page_size=5')"
  expect_biz_ok "GET /v1/open/shops" "$resp" || true

  resp="$(sign_get /v1/open/batches '?page=1&page_size=5')"
  expect_biz_ok "GET /v1/open/batches" "$resp" || true

  SUFFIX="$(date +%s)-$RANDOM"
  if [[ "${SKIP_WRITE:-0}" == "1" ]]; then
    warn "SKIP_WRITE=1，跳过写操作冒烟"
  else
    section "在线 · 经销商/门店增删改查冒烟"
    D_NAME="OpenAPI验收经销商-$SUFFIX"
    S_NAME="OpenAPI验收门店-$SUFFIX"

    resp="$(open_curl POST /v1/open/dealers "{\"name\":\"$D_NAME\",\"contact\":\"qa\",\"phone\":\"13800000000\",\"status\":2}")"
    if expect_biz_ok "POST /v1/open/dealers" "$resp"; then
      dealer_id="$(printf '%s' "$resp" | jq -r '.data.id // empty')"
      if [[ -n "$dealer_id" ]]; then ok "经销商 id=$dealer_id"; else bad "经销商响应无 id"; fi
    fi

    resp="$(open_curl PUT /v1/open/dealers "{\"name\":\"$D_NAME\",\"contact\":\"qa2\",\"phone\":\"13900000000\",\"status\":2}")"
    expect_biz_ok "PUT /v1/open/dealers" "$resp" || true

    enc_d="$(urlencode "$D_NAME")"
    resp="$(sign_get /v1/open/dealers "?page=1&page_size=20&name=$enc_d")"
    if expect_biz_ok "GET /v1/open/dealers?name=..." "$resp"; then
      hit="$(printf '%s' "$resp" | jq -r --arg n "$D_NAME" '[.data.list[]? | select(.name==$n)] | length')"
      if [[ "${hit:-0}" -ge 1 ]]; then ok "经销商列表命中新建项"; else bad "经销商列表未命中"; fi
    fi

    resp="$(open_curl POST /v1/open/shops "{\"dealer_name\":\"$D_NAME\",\"name\":\"$S_NAME\",\"province\":\"广东省\",\"city\":\"广州市\",\"district\":\"天河区\",\"address\":\"验收路1号\",\"status\":2}")"
    if expect_biz_ok "POST /v1/open/shops" "$resp"; then
      shop_id="$(printf '%s' "$resp" | jq -r '.data.id // empty')"
      if [[ -n "$shop_id" ]]; then ok "门店 id=$shop_id"; else bad "门店响应无 id"; fi
    fi

    resp="$(open_curl PUT /v1/open/shops "{\"dealer_name\":\"$D_NAME\",\"name\":\"$S_NAME\",\"province\":\"广东省\",\"city\":\"广州市\",\"district\":\"越秀区\",\"address\":\"验收路2号\",\"status\":2}")"
    expect_biz_ok "PUT /v1/open/shops" "$resp" || true

    resp="$(sign_get /v1/open/dealers/shops "?dealer_name=$enc_d&page=1&page_size=20")"
    if expect_biz_ok "GET /v1/open/dealers/shops" "$resp"; then
      hit="$(printf '%s' "$resp" | jq -r --arg n "$S_NAME" '[.data.list[]? | select(.name==$n)] | length')"
      if [[ "${hit:-0}" -ge 1 ]]; then ok "经销商门店列表命中"; else bad "经销商门店列表未命中"; fi
    fi

    resp="$(sign_get /v1/open/shops "?dealer_name=$enc_d&page=1&page_size=20")"
    expect_biz_ok "GET /v1/open/shops?dealer_name=..." "$resp" || true

    if [[ "${KEEP_FIXTURES:-0}" == "1" ]]; then
      warn "KEEP_FIXTURES=1，保留 $D_NAME / $S_NAME"
    else
      resp="$(open_curl DELETE /v1/open/shops "{\"dealer_name\":\"$D_NAME\",\"name\":\"$S_NAME\"}")"
      expect_biz_ok "DELETE /v1/open/shops" "$resp" || true
      resp="$(open_curl DELETE /v1/open/dealers "{\"name\":\"$D_NAME\"}")"
      expect_biz_ok "DELETE /v1/open/dealers" "$resp" || true
    fi
  fi

  if [[ -n "${PRODUCT_ID:-}" && "${SKIP_WRITE:-0}" != "1" ]]; then
    section "在线 · 创建批次（PRODUCT_ID=$PRODUCT_ID，会扣额度）"
    resp="$(open_curl POST /v1/open/batches "{\"product_id\":$PRODUCT_ID,\"plan_qty\":1,\"batch_name\":\"OpenAPI验收-$SUFFIX\"}")"
    if expect_biz_ok "POST /v1/open/batches" "$resp"; then
      batch_id="$(printf '%s' "$resp" | jq -r '.data.id // empty')"
      ok "批次 id=$batch_id（请自行在后台关注生成/清理）"
      if [[ -n "$batch_id" ]]; then
        resp="$(open_curl GET "/v1/open/batches/$batch_id")"
        expect_biz_ok "GET /v1/open/batches/:id" "$resp" || true
      fi
    fi
  else
    warn "未设置 PRODUCT_ID，跳过创建批次（避免误扣额度）"
  fi
fi

section "汇总"
echo "通过: $pass  失败: $fail  跳过: $skip"
if [[ "$fail" -ne 0 ]]; then
  echo -e "${RED}Open API 验收未达标${NC}"
  exit 1
fi
echo -e "${GREEN}Open API 验收通过${NC}"
if [[ -z "${ACCESS_KEY_ID:-}" || -z "${ACCESS_KEY_SECRET:-}" ]]; then
  echo ""
  echo "尚未执行真实 HTTP 冒烟。请用商户后台 AccessKey 再跑一遍："
  echo "  ACCESS_KEY_ID=你的ID ACCESS_KEY_SECRET=你的Secret \\"
  echo "    BASE_URL=https://mc.weige.pub ./anti_open_api_acceptance.sh"
fi
