#!/bin/bash
#
# 客户对外用量接口 —— 账单汇总数据拉取到本地 JSON 文件
#
# 对应接口 /open-apis/usages/billing，按天/按月/按小时汇总。
# 返回已汇总数据：每个时间段一行，各指标（token/金额等）为列。
# 输入时间段（可选 period 粒度、可选项目过滤），脚本自动分页拉取【全部】汇总记录，
# 合并成一份 JSON 数组写到本地，使用者无需关心分页和切分。
# 接口单次时间窗上限 180 天、单次最多 100 页 × 100 条 = 10000 条，脚本会自动切分。
#
# 用法示例：
#   SOPH_API_KEY=xxx BEGIN_TIME=2026-08-01 END_TIME=2026-09-01 ./curl_export_usage_billing.sh
#   SOPH_API_KEY=xxx BEGIN_TIME=2026-08-01 END_TIME=2026-09-01 PERIOD=hour PROJECT_ID=887 ./curl_export_usage_billing.sh
#
# 环境变量：
#   SOPH_BASE_URL  网关基地址（默认生产环境 www.sophnet.com）
#   SOPH_API_KEY   apikey（必填，不提供默认值）
#   BEGIN_TIME     起始时间（选填，默认 30 天前；ISO 如 2026-09-01T00:00:00，或纯日期 2026-09-01 按当天 00:00:00）
#   END_TIME       结束时间（选填，默认当前；纯日期自动按当天 23:59:59）
#   PERIOD         聚合粒度 hour | day | month（默认 day）
#   PROJECT_ID     可选，指定项目过滤
#   OUTPUT_FILE    输出文件路径（默认 usage_billing_<开始时间>_to_<结束时间>.json）
#
# 依赖：curl、jq、GNU date

SOPH_BASE_URL="${SOPH_BASE_URL:-https://www.sophnet.com/api/open-apis}"
SOPH_API_KEY="${SOPH_API_KEY:-}"
BEGIN_TIME="${BEGIN_TIME:-}"
END_TIME="${END_TIME:-}"
PERIOD="${PERIOD:-day}"
PROJECT_ID="${PROJECT_ID:-}"
OUTPUT_FILE="${OUTPUT_FILE:-}"

SIZE=100        # 单页条数（接口上限）
MAX_PAGE=100    # 最大页码（接口上限）
LIMIT=$((SIZE * MAX_PAGE))          # 单次最多 10000 条
MAX_SPAN=$((180 * 24 * 3600))       # 接口单次时间窗上限 180 天

GREEN='\033[0;32m'; RED='\033[0;31m'; YELLOW='\033[1;33m'; CYAN='\033[0;36m'; NC='\033[0m'
info()  { echo -e "${CYAN}[INFO]${NC} $1"; }
ok()    { echo -e "${GREEN}[OK]${NC} $1"; }
warn()  { echo -e "${YELLOW}[WARN]${NC} $1"; }
err()   { echo -e "${RED}[ERR]${NC} $1" >&2; }

# ---------- 参数校验 ----------
if [ -z "$SOPH_API_KEY" ]; then err "未设置 SOPH_API_KEY"; exit 1; fi
if ! command -v jq >/dev/null 2>&1; then err "未安装 jq"; exit 1; fi
if ! command -v curl >/dev/null 2>&1; then err "未安装 curl"; exit 1; fi
if ! date -d '@0' '+%s' >/dev/null 2>&1; then err "需要 GNU date（-d 支持），macOS 请 brew install coreutils"; exit 1; fi
case "$PERIOD" in
  hour|day|month) ;;
  *) err "PERIOD 仅支持 hour/day/month，收到: $PERIOD"; exit 1 ;;
esac

# ---------- 时间默认 / 填充 ----------
if [ -z "$END_TIME" ]; then END_TIME="$(date '+%Y-%m-%dT%H:%M:%S')"; fi
if [ -z "$BEGIN_TIME" ]; then BEGIN_TIME="$(date -d '30 days ago' '+%Y-%m-%dT%H:%M:%S')"; fi
if [[ "$BEGIN_TIME" != *T* ]]; then BEGIN_TIME="${BEGIN_TIME}T00:00:00"; fi
if [[ "$END_TIME"   != *T* ]]; then END_TIME="${END_TIME}T23:59:59"; fi

# 时间参数需传 UTC 毫秒格式（如 2026-09-01T00:00:00.000Z），脚本自动把本地时间转成该格式
RAW_BEGIN="$BEGIN_TIME"; RAW_END="$END_TIME"
BEGIN_TIME=$(date -u -d "@$(date -d "$BEGIN_TIME" +%s)" '+%Y-%m-%dT%H:%M:%S.000Z') || { err "BEGIN_TIME 解析失败：${RAW_BEGIN}"; exit 1; }
END_TIME=$(date -u -d "@$(date -d "$END_TIME" +%s)" '+%Y-%m-%dT%H:%M:%S.000Z') || { err "END_TIME 解析失败：${RAW_END}"; exit 1; }

if [ -z "$OUTPUT_FILE" ]; then
  # 文件名带时间段，便于一眼看出导的是哪段
  FILE_BEGIN="${RAW_BEGIN//:/-}"; FILE_BEGIN="${FILE_BEGIN/T/_}"
  FILE_END="${RAW_END//:/-}";     FILE_END="${FILE_END/T/_}"
  OUTPUT_FILE="usage_billing_${FILE_BEGIN}_to_${FILE_END}.json"
fi

info "Base URL : ${SOPH_BASE_URL}"
info "period   : ${PERIOD}"
info "时间段   : ${RAW_BEGIN} ~ ${RAW_END}（已按本地时区转 UTC 拉取）"
[ -n "$PROJECT_ID" ] && info "projectId: ${PROJECT_ID}"
info "输出文件 : ${OUTPUT_FILE}"
echo ""

# ---------- 临时累积区（每行一条 JSON，最后合并成数组）----------
TMP_DIR=$(mktemp -d)
trap 'rm -rf "$TMP_DIR"' EXIT
ROWS="$TMP_DIR/rows.jsonl"
touch "$ROWS"      # 保证空结果时 jq -s 也有文件可读
FETCHED_TOTAL=0
TRUNCATED=0

# ---------- 请求封装 ----------
request() {  # $1 = 完整 path（含 query）
  local url="$1" resp code body status msg
  resp=$(curl -sS --connect-timeout 5 --max-time 60 -w $'\n%{http_code}' \
      -H "Authorization: Bearer ${SOPH_API_KEY}" "${SOPH_BASE_URL}${url}") || {
    err "请求失败（网络/连接错误）：${url}"; return 1
  }
  code=$(printf '%s\n' "$resp" | tail -n1)
  body=$(printf '%s\n' "$resp" | sed '$d')
  if ! printf '%s' "$body" | jq -e . >/dev/null 2>&1; then
    err "响应非 JSON（HTTP ${code}）：${body}"; return 1
  fi
  status=$(printf '%s' "$body" | jq -r '.status // "null"')
  if [ "$status" != "0" ]; then
    msg=$(printf '%s' "$body" | jq -r '.message // ""')
    err "接口报错：status=${status} msg=${msg}"; return 1
  fi
  RESP_BODY="$body"
}

build_billing_url() {  # $1=page $2=size $3=begin $4=end
  local url="/usages/billing?period=${PERIOD}&page=$1&size=$2&beginTime=$3&endTime=$4"
  [ -n "$PROJECT_ID" ] && url="${url}&projectId=${PROJECT_ID}"
  printf '%s' "$url"
}

# ---------- 探测某时间段总条数 ----------
probe_total() {  # $1=begin $2=end
  local url
  url=$(build_billing_url 0 1 "$1" "$2")
  request "$url" || return 1
  printf '%s' "$RESP_BODY" | jq -r '.result.totalElements // 0'
}

# ---------- 翻页拉取某时间段全部汇总记录，追加到 ROWS ----------
fetch_pages() {  # $1=begin $2=end
  local begin="$1" end="$2" page=0 total=0 fetched=0 page_count url
  while [ "$page" -lt "$MAX_PAGE" ]; do
    url=$(build_billing_url "$page" "$SIZE" "$begin" "$end")
    request "$url" || exit 1
    if [ "$page" -eq 0 ]; then
      total=$(printf '%s' "$RESP_BODY" | jq -r '.result.totalElements // 0')
    fi
    page_count=$(printf '%s' "$RESP_BODY" | jq -r '.result.content | length')
    [ "$page_count" -eq 0 ] && break
    printf '%s' "$RESP_BODY" | jq -c '.result.content[]' >> "$ROWS"
    fetched=$((fetched + page_count))
    FETCHED_TOTAL=$((FETCHED_TOTAL + page_count))
    printf '\r  已拉取 %d 条' "$FETCHED_TOTAL"
    [ "$fetched" -ge "$total" ] && break
    page=$((page + 1))
  done
  if [ "$fetched" -lt "$total" ]; then
    TRUNCATED=1
  fi
}

# ---------- 递归：先按 180 天时间窗上限对半切，再按 10000 条上限对半切 ----------
fetch_range() {  # $1=begin $2=end
  local begin="$1" end="$2" total be ee me mid

  be=$(date -d "$begin" +%s) || { err "时间解析失败：${begin}"; exit 1; }
  ee=$(date -d "$end"   +%s) || { err "时间解析失败：${end}"; exit 1; }

  if [ $((ee - be)) -gt "$MAX_SPAN" ]; then
    me=$(( (be + ee) / 2 ))
    mid=$(date -u -d "@$me" '+%Y-%m-%dT%H:%M:%S.000Z')
    fetch_range "$begin" "$mid"
    fetch_range "$mid" "$end"
    return
  fi

  total=$(probe_total "$begin" "$end") || { err "探测总条数失败（${begin} ~ ${end}），已终止"; exit 1; }
  if [ "$total" -le "$LIMIT" ]; then
    fetch_pages "$begin" "$end"
    return
  fi

  if [ $((ee - be)) -le 1 ]; then
    warn "时间片 ${begin} ~ ${end} 内数据超过 ${LIMIT} 条且已无法再切，该片仅拉前 ${LIMIT} 条"
    fetch_pages "$begin" "$end"
    return
  fi

  me=$(( (be + ee) / 2 ))
  mid=$(date -u -d "@$me" '+%Y-%m-%dT%H:%M:%S.000Z')
  fetch_range "$begin" "$mid"
  fetch_range "$mid" "$end"
}

# ---------- 主流程 ----------
fetch_range "$BEGIN_TIME" "$END_TIME"
echo ""

jq -s '.' "$ROWS" > "$OUTPUT_FILE"

echo ""
if [ "$TRUNCATED" -eq 1 ]; then
  warn "有极端时间段被截断（单秒数据超过 ${LIMIT} 条），文件可能不完整，请核对。"
fi
ok "共拉取 ${FETCHED_TOTAL} 条汇总记录，已写入：${OUTPUT_FILE}"
echo "  预览开头："
head -c 500 "$OUTPUT_FILE"
echo ""
exit 0
