#!/usr/bin/env bash
set -euo pipefail

# ================== 通用日志函数 ==================

log_time() {
  date '+%Y-%m-%d %H:%M:%S'
}

log_info() {
  # 所有 INFO 走 stderr，避免污染命令替换结果
  echo "[INFO] $(log_time) $*" >&2
}

log_warn() {
  echo "[WARN] $(log_time) $*" >&2
}

log_error() {
  echo "[ERROR] $(log_time) $*" >&2
}

# ================== 配置 & 依赖检查 ==================

API_HOST="api.qiniu.com"        # 只域名，不带 schema
API_SCHEME="https"

QINIU_ACCESS_KEY="${QINIU_ACCESS_KEY:-}"
QINIU_SECRET_KEY="${QINIU_SECRET_KEY:-}"

if [[ -z "$QINIU_ACCESS_KEY" || -z "$QINIU_SECRET_KEY" ]]; then
  log_error "请先在环境变量 QINIU_ACCESS_KEY / QINIU_SECRET_KEY 中配置七牛密钥."
  exit 1
fi

for cmd in curl openssl jq; do
  if ! command -v "$cmd" >/dev/null 2>&1; then
    log_error "缺少依赖命令: $cmd  (请先安装 $cmd)"
    exit 1
  fi
done

if [[ $# -ne 1 ]]; then
  echo "用法: $0 /opt/cert/某个目录" >&2
  exit 1
fi

CERT_DIR="$1"

if [[ ! -d "$CERT_DIR" ]]; then
  log_error "证书目录不存在: $CERT_DIR"
  exit 1
fi

FULLCHAIN="${CERT_DIR}/fullchain.pem"
PRIVKEY="${CERT_DIR}/privkey.pem"

if [[ ! -f "$FULLCHAIN" || ! -f "$PRIVKEY" ]]; then
  log_error "找不到 fullchain.pem 或 privkey.pem: $FULLCHAIN 或 $PRIVKEY"
  exit 1
fi

# ================== 从证书解析域名 ==================
# 1. 优先取 subject 里的 CN
# 2. 取不到再从 Subject Alternative Name 里取第一个 DNS:xxx

extract_domain_from_cert() {
  local cert_file="$1"

  log_info "尝试从证书中解析域名: ${cert_file}"

  local subject cn san_line san_domain

  # 尝试解析 CN
  set +e
  subject=$(openssl x509 -in "$cert_file" -noout -subject 2>/dev/null)
  local ret1=$?
  set -e
  if [[ $ret1 -eq 0 && -n "$subject" ]]; then
    # 兼容两种格式：
    # subject= CN = example.com, ...
    # subject= /C=XX/.../CN=example.com
    cn=$(echo "$subject" | sed -n 's/.*CN *= *\([^,\/]*\).*/\1/p' | head -n1)
    if [[ -n "$cn" ]]; then
      log_info "从 subject 中解析到 CN: $cn"
      printf '%s\n' "$cn"
      return 0
    fi
  fi

  # 尝试从 SAN 里解析第一个 DNS
  log_info "CN 解析失败，尝试从 Subject Alternative Name 里解析 DNS"

  set +e
  san_line=$(openssl x509 -in "$cert_file" -noout -text 2>/dev/null \
    | sed -n '/Subject Alternative Name/,+1p' | tail -n1)
  local ret2=$?
  set -e

  if [[ $ret2 -eq 0 && -n "$san_line" ]]; then
    # 例： " DNS:test.aikrai.xyz, DNS:*.aikrai.xyz"
    san_domain=$(echo "$san_line" | sed -n 's/.*DNS:\([^, ]*\).*/\1/p' | head -n1)
    if [[ -n "$san_domain" ]]; then
      log_info "从 SAN 中解析到 DNS: $san_domain"
      printf '%s\n' "$san_domain"
      return 0
    fi
  fi

  log_warn "无法从证书中解析域名，将回退到目录名作为域名"
  basename "$CERT_DIR"
}

# 启动日志 + 域名解析
log_info "脚本启动，目标证书目录: ${CERT_DIR}"
DOMAIN="$(extract_domain_from_cert "$FULLCHAIN")"
log_info "证书解析到域名: ${DOMAIN}"

# ================== QBox 管理凭证生成（历史版算法，适用于 CDN SSL / 域名接口） ==================
# 文档算法（历史管理凭证）：
# signingStr = "<path>?<query>\n"        # 无 body 或非 x-www-form-urlencoded
# 或（仅当 Content-Type=application/x-www-form-urlencoded 才必须包括 body）：
# signingStr = "<path>?<query>\n<body>"
#
# 这里我们的 Content-Type = application/json，所以**不包含 body**，只签 path/query。

qbox_token() {
  local path="$1"       # 例如 "/sslcert" 或 "/domain" 或 "/domain/xxx/httpsconf"
  local rawquery="$2"   # 例如 "limit=1000&marker=xxx" 或 ""

  local signing_str="$path"
  if [[ -n "$rawquery" ]]; then
    signing_str="${signing_str}?${rawquery}"
  fi
  signing_str="${signing_str}"$'\n'

  local sign
  sign=$(printf '%s' "$signing_str" \
    | openssl dgst -binary -sha1 -hmac "$QINIU_SECRET_KEY" \
    | base64 | tr '+/' '-_')

  printf '%s:%s' "$QINIU_ACCESS_KEY" "$sign"
}

# ================== 上传证书 ==================

upload_cert() {
  local domain="$1"
  local priv_file="$2"
  local chain_file="$3"

  log_info "===== START 上传证书 ====="

  local ts cert_name
  ts=$(date +%s)
  cert_name="${domain}-${ts}"
  log_info "证书备注名: ${cert_name}"
  log_info "privkey 文件: ${priv_file}"
  log_info "fullchain 文件: ${chain_file}"

  local pri_raw ca_raw
  pri_raw=$(cat "$priv_file")
  ca_raw=$(cat "$chain_file")

  local pri_len ca_len
  pri_len=${#pri_raw}
  ca_len=${#ca_raw}
  log_info "私钥长度: ${pri_len} 字符（内容已脱敏，不打印明文）"
  log_info "证书链长度: ${ca_len} 字符（内容已脱敏，不打印明文）"

  # 真正请求体（带完整 pem）
  local body
  body=$(jq -n \
    --arg name "$cert_name" \
    --arg cn "" \
    --arg pri "$pri_raw" \
    --arg ca "$ca_raw" \
    '{name:$name, common_name:$cn, pri:$pri, ca:$ca}')

  # 日志里用脱敏版
  local log_body
  log_body=$(jq -n \
    --arg name "$cert_name" \
    --arg cn "" \
    --arg priLen "$pri_len" \
    --arg caLen "$ca_len" \
    '{name:$name, common_name:$cn, pri_len:$priLen, ca_len:$caLen}')
  log_info "上传证书请求体(脱敏): ${log_body}"

  local path="/sslcert"
  local rawquery=""

  # 注意：签名里不包含 body
  local token
  token=$(qbox_token "$path" "$rawquery")

  local url="${API_SCHEME}://${API_HOST}${path}"
  log_info "上传证书请求 URL: ${url}"
  log_info "上传证书请求头: Authorization: QBox <token省略>, Content-Type: application/json"

  local resp
  set +e
  resp=$(curl -sS -X POST "$url" \
      -H "Content-Type: application/json" \
      -H "Authorization: QBox ${token}" \
      -d "$body")
  local curl_exit=$?
  set -e

  if [[ $curl_exit -ne 0 ]]; then
    log_error "curl 调用上传证书接口失败，退出码: ${curl_exit}"
    log_error "curl 响应(可能为空): ${resp}"
    exit 1
  fi

  log_info "上传证书响应体: ${resp}"

  local cert_id
  cert_id=$(echo "$resp" | jq -r '.certID // empty')

  if [[ -z "$cert_id" || "$cert_id" == "null" ]]; then
    log_error "从响应中解析 certID 失败，响应体: ${resp}"
    exit 1
  fi

  log_info "上传证书成功，新证书 certID = ${cert_id}"
  log_info "===== END 上传证书 ====="
  printf '%s\n' "$cert_id"
}

# ================== 更新单个域名的证书 ==================

update_domain_cert() {
  local domain_name="$1"
  local cert_id="$2"

  log_info "----- START 更新单个域名证书 -----"
  log_info "准备更新域名: ${domain_name}，目标证书 certID: ${cert_id}"

  local path="/domain/${domain_name}/httpsconf"
  local rawquery=""

  local body
  body=$(jq -n \
    --arg certId "$cert_id" \
    '{certId:$certId, forceHttps:false, http2Enable:true}')

  local url="${API_SCHEME}://${API_HOST}${path}"
  log_info "更新证书请求 URL: ${url}"
  log_info "更新证书请求体: ${body}"

  local token
  token=$(qbox_token "$path" "$rawquery")

  local resp
  set +e
  resp=$(curl -sS -X PUT "$url" \
      -H "Content-Type: application/json" \
      -H "Authorization: QBox ${token}" \
      -d "$body")
  local curl_exit=$?
  set -e

  if [[ $curl_exit -ne 0 ]]; then
    log_error "curl 调用更新域名 ${domain_name} 证书失败，退出码: ${curl_exit}"
    log_error "curl 响应(可能为空): ${resp}"
    return 1
  fi

  log_info "更新域名 ${domain_name} 证书响应体: ${resp}"
  log_info "----- END 更新单个域名证书 -----"
}

# ================== 遍历域名列表并匹配 ==================
# GET /domain?limit=&marker= 获取全部 CDN 域名列表，并根据证书域名匹配

bind_cert_to_all_matched_domains() {
  local pattern_domain="$1"
  local cert_id="$2"

  log_info "===== START 遍历 & 绑定证书 ====="
  log_info "匹配规则域名: ${pattern_domain}，要绑定的 certID: ${cert_id}"

  local marker=""
  local limit=1000
  local any_matched="false"
  local page=0

  while :; do
    page=$((page + 1))

    local rawquery
    if [[ -n "$marker" ]]; then
      rawquery="limit=${limit}&marker=${marker}"
    else
      rawquery="limit=${limit}"
    fi

    local path="/domain"
    local token
    token=$(qbox_token "$path" "$rawquery")

    local url="${API_SCHEME}://${API_HOST}${path}?${rawquery}"

    log_info "请求第 ${page} 页域名列表, URL: ${url}"
    log_info "请求头: Authorization: QBox <token省略>, Content-Type: application/x-www-form-urlencoded"

    local resp
    set +e
    resp=$(curl -sS -X GET "$url" \
        -H 'Content-Type: application/x-www-form-urlencoded' \
        -H "Authorization: QBox ${token}")
    local curl_exit=$?
    set -e

    if [[ $curl_exit -ne 0 ]]; then
      log_error "curl 调用获取域名列表失败，退出码: ${curl_exit}"
      log_error "curl 响应(可能为空): ${resp}"
      break
    fi

    log_info "域名列表响应(第 ${page} 页)原始体: ${resp}"

    local error_code
    error_code=$(echo "$resp" | jq -r '.error_code // empty')
    if [[ -n "$error_code" ]]; then
      log_warn "获取域名列表接口返回错误(已忽略): error_code=${error_code}, resp=${resp}"
      break
    fi

    local domains_count
    domains_count=$(echo "$resp" | jq '.domains | length')
    log_info "第 ${page} 页返回域名数量: ${domains_count}"

    local domain
    for domain in $(echo "$resp" | jq -r '.domains[].name'); do
      local match="false"

      if [[ "$pattern_domain" == \*.* ]]; then
        # 泛域名 *.example.com 匹配所有 xxx.example.com
        local suffix="${pattern_domain#*.}"  # 去掉 "*."
        if [[ "$domain" == *".${suffix}" ]]; then
          match="true"
        fi
      else
        if [[ "$domain" == "$pattern_domain" ]]; then
          match="true"
        fi
      fi

      if [[ "$match" == "true" ]]; then
        any_matched="true"
        log_info "命中匹配域名: ${domain}，准备更新证书..."
        if ! update_domain_cert "$domain" "$cert_id"; then
          log_error "更新域名 ${domain} 证书时出错，已记录日志，继续处理下一个域名。"
        fi
      fi
    done

    marker=$(echo "$resp" | jq -r '.marker // empty')
    log_info "第 ${page} 页处理结束，下一页 marker: ${marker:-<无>}"

    [[ -z "$marker" ]] && break
  done

  if [[ "$any_matched" != "true" ]]; then
    log_warn "未找到任何匹配 ${pattern_domain} 的 CDN 域名, 本次仅完成证书上传."
  fi

  log_info "===== END 遍历 & 绑定证书 ====="
}

# ================== main ==================

main() {
  log_info "===== START 整体流程 ====="
  log_info "使用证书目录: $CERT_DIR"
  log_info "目标域名: $DOMAIN"

  local cert_id
  cert_id=$(upload_cert "$DOMAIN" "$PRIVKEY" "$FULLCHAIN")

  log_info "开始为所有匹配域名绑定证书 certID=${cert_id}"
  bind_cert_to_all_matched_domains "$DOMAIN" "$cert_id"

  log_info "===== END 整体流程，全部完成 ====="
}

main "$@"
