#!/bin/bash
# 把 npm pack 出来的 tgz 发到私有通道（POS-46 第 2 期，约定见 PR 说明）：
#   sha256 → statement「aaden-cli\n<版本>\n<sha256>\n」→ KMS ECDSA_SHA_256 签名 → 上传 releases/<版本>/ → 写清单（新版本先灰度）
# 用法：publish-private.sh <tgz> <版本>     环境变量：BUCKET、CHANNEL（默认 stable）、KMS_KEY_ID（KMS 密钥 ARN）
#   ROLLOUT_DEVICES（默认 9020,9021）、ROLLOUT_PERCENT（默认 10）、PROMOTE_AFTER_HOURS（默认 24，云端到时间自动当全量）
# 设备不直接读桶：由云端 /device-cli/latest 按清单决定版本、发 10 分钟预签名链接；设备用写死的公钥验签。
set -euo pipefail
TGZ=${1:?tgz}; VERSION=${2:?版本}
: "${BUCKET:?}" "${KMS_KEY_ID:?}"
CHANNEL=${CHANNEL:-stable}
[[ "$VERSION" =~ ^[0-9]+\.[0-9]+\.[0-9]+$ ]] || { echo "只发 x.y.z 正式版（预发布走 npm next）：$VERSION"; exit 1; }
WORK=$(mktemp -d); trap 'rm -rf "$WORK"' EXIT
# shellcheck source=manifest-s3.sh
. "$(dirname "$0")/manifest-s3.sh"

SHA=$(sha256sum "$TGZ" | cut -d' ' -f1); SIZE=$(stat -c %s "$TGZ")
printf 'aaden-cli\n%s\n%s\n' "$VERSION" "$SHA" > "$WORK/statement"
aws kms sign --key-id "$KMS_KEY_ID" --message "fileb://$WORK/statement" --message-type RAW \
  --signing-algorithm ECDSA_SHA_256 --query Signature --output text > "$WORK/sig.b64"
# 用 KMS 的公钥当场验一遍，签名不对就不发（公钥和设备里写死的是同一把）
aws kms get-public-key --key-id "$KMS_KEY_ID" --query PublicKey --output text | base64 -d > "$WORK/pub.der"
openssl pkey -pubin -inform DER -in "$WORK/pub.der" -out "$WORK/pub.pem"
base64 -d < "$WORK/sig.b64" > "$WORK/sig.der"
openssl dgst -sha256 -verify "$WORK/pub.pem" -signature "$WORK/sig.der" "$WORK/statement" >/dev/null || { echo "签名自检没过"; exit 1; }
echo "签名自检通过；公钥 sha256 $(sha256sum "$WORK/pub.der" | cut -c1-16)"

# 先看清单：比 current 低的版本连包都不传（decide 里写清单前还会再查一次，防两次发布并发）
manifest_get "$CHANNEL" "$WORK/pre.json"
python3 - "$WORK/pre.json" "$VERSION" <<'PY'
import json, sys
cur = (json.load(open(sys.argv[1])).get('current') or {}).get('version')
vt = lambda v: tuple(int(x) for x in v.split('.'))
if cur and vt(sys.argv[2]) < vt(cur):
    sys.exit('拒绝：%s 比清单里的 current %s 低（要回到旧版请用 rollback）' % (sys.argv[2], cur))
PY

KEY="releases/$VERSION/aaden-cli-$VERSION.tgz"
# 同一版本只传一次：已经有了就核对 sha256，不一样就拒绝（同号不同内容等于偷换包）
if HEAD=$(aws s3api head-object --bucket "$BUCKET" --key "$KEY" --output json 2>/dev/null); then
  OLD=$(printf '%s' "$HEAD" | python3 -c 'import json,sys; print(json.load(sys.stdin).get("Metadata",{}).get("sha256",""))')
  [ "$OLD" = "$SHA" ] || { echo "s3://$BUCKET/$KEY 已存在且 sha256 不同（$OLD），拒绝覆盖"; exit 1; }
  echo "包已在桶里（sha256 一致），不重复上传"
else
  aws s3api put-object --bucket "$BUCKET" --key "$KEY" --body "$TGZ" --content-type application/gzip \
    --metadata "sha256=$SHA" --if-none-match '*' --output json >/dev/null
  echo "已上传 s3://$BUCKET/$KEY（$SIZE 字节，sha256 ${SHA:0:16}）"
fi

decide(){
  python3 - "$WORK/old.json" "$WORK/new.json" "$VERSION" "$KEY" "$SHA" "$SIZE" "$WORK/statement" "$WORK/sig.b64" <<'PY'
import json, os, sys, datetime
src, dst, version, key, sha, size, stmt, sig = sys.argv[1:9]
m = json.load(open(src))
now = datetime.datetime.now(datetime.timezone.utc).isoformat(timespec='seconds').replace('+00:00', 'Z')
strip = lambda e: {k: v for k, v in e.items() if k not in ('rollout', 'rolloutStartedAt', 'promoteAfterHours')}
cur = m.get('current')
if cur and cur.get('version') == version and cur.get('sha256') == sha:
    print('清单里已经是这一版，不改'); sys.exit(0)
# 不往回发：比 current 低的版本拒绝（回到旧版走 rollout.sh rollback，它用的是清单里原来签过名的 previous）
vt = lambda v: tuple(int(x) for x in v.split('.'))
if cur and cur.get('version') and vt(version) < vt(cur['version']):
    sys.exit('拒绝：%s 比清单里的 current %s 低（要回到旧版请用 rollback）' % (version, cur['version']))
devices = [int(x) for x in os.environ.get('ROLLOUT_DEVICES', '9020,9021').split(',') if x.strip()]
entry = {'version': version, 'key': key, 'sha256': sha, 'size': int(size),
         'statement': open(stmt).read(), 'sig': open(sig).read().strip(), 'publishedAt': now,
         'rollout': {'devices': devices, 'percent': int(os.environ.get('ROLLOUT_PERCENT', '10'))},
         'rolloutStartedAt': now, 'promoteAfterHours': int(os.environ.get('PROMOTE_AFTER_HOURS', '24'))}
out = {'schema': 1, 'channel': os.environ.get('CHANNEL', 'stable'), 'generatedAt': now, 'current': entry}
# 旧 current 挪进 previous（灰度外的设备继续拿它）；同一版本重发就沿用原来的 previous
if cur and cur.get('version') != version:
    out['previous'] = strip(cur)
elif m.get('previous'):
    out['previous'] = m['previous']
json.dump(out, open(dst, 'w'), indent=2, ensure_ascii=False)
print('current -> %s（灰度 devices=%s percent=%s，%s 小时后云端当全量）；previous -> %s' % (
    version, devices, entry['rollout']['percent'], entry['promoteAfterHours'], (out.get('previous') or {}).get('version')))
PY
}
with_manifest "$CHANNEL" decide
