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

CHANNEL="${1:-test}"

BASE_URL="https://files.shenqianqian.com/btcproxy/vps"

ROOT="/opt/btcproxy"
SHARED="$ROOT/shared"
RELEASES="$ROOT/releases"
CURRENT="$ROOT/current"

CONTAINER="btcproxy"
RUNTIME_IMAGE="btcproxy-btcproxy:latest"
ROLLBACK_IMAGE="btcproxy-rollback:previous"

case "$CHANNEL" in
  test|stable) ;;
  *)
    echo "用法: btcproxy-update test|stable" >&2
    exit 2
    ;;
esac

if [ "$(id -u)" != "0" ]; then
  echo "必须使用 root 执行"
  exit 3
fi

for cmd in curl python3 sha256sum tar docker awk; do
  command -v "$cmd" >/dev/null || {
    echo "缺少命令: $cmd"
    exit 4
  }
done

docker compose version >/dev/null

test -f "$SHARED/config.yaml" || { echo "缺少 $SHARED/config.yaml"; exit 5; }
test -f "$SHARED/.env" || { echo "缺少 $SHARED/.env"; exit 5; }
test -d "$SHARED/tls" || { echo "缺少 $SHARED/tls"; exit 5; }

mkdir -p "$RELEASES"

TMP="$(mktemp -d)"
trap 'rm -rf "$TMP"' EXIT

echo
echo "========================================"
echo " BTCProxy Updater V2"
echo " Channel: $CHANNEL"
echo "========================================"

echo
echo "===== 1. 获取 manifest ====="

curl -fsSL --retry 3 "$BASE_URL/$CHANNEL/manifest.json" -o "$TMP/manifest.json"

mapfile -t META < <(
python3 - "$TMP/manifest.json" <<'PY'
import json, sys
d = json.load(open(sys.argv[1]))
print(d["version"])
print(d["commit"])
print(d["release"])
PY
)

VERSION="${META[0]}"
COMMIT="${META[1]}"
RELEASE_URL="${META[2]}"

[[ "$VERSION" =~ ^[0-9A-Za-z._-]+$ ]] || { echo "非法版本号: $VERSION"; exit 6; }
[[ "$COMMIT" =~ ^[0-9a-f]{40}$ ]] || { echo "非法 commit: $COMMIT"; exit 6; }

echo "目标版本 : $VERSION"
echo "目标 Commit: $COMMIT"

echo
echo "===== 2. 判断 CPU 架构 ====="

case "$(uname -m)" in
  x86_64|amd64) ARCH="amd64" ;;
  aarch64|arm64) ARCH="arm64" ;;
  *) echo "不支持的架构: $(uname -m)"; exit 7 ;;
esac

echo "Architecture: $ARCH"

echo
echo "===== 3. 获取 release.json ====="

curl -fsSL --retry 3 "$RELEASE_URL" -o "$TMP/release.json"

mapfile -t ART < <(
python3 - "$TMP/release.json" "$ARCH" <<'PY'
import json, sys
d = json.load(open(sys.argv[1]))
arch = "linux-" + sys.argv[2]
a = d["artifacts"][arch]
print(d["version"])
print(d["commit"])
print(a["filename"])
print(a["sha256"])
PY
)

RELEASE_VERSION="${ART[0]}"
RELEASE_COMMIT="${ART[1]}"
FILE="${ART[2]}"
SHA="${ART[3]}"

if [ "$RELEASE_VERSION" != "$VERSION" ]; then
  echo "manifest 与 release.json 版本不一致"
  exit 8
fi

if [ "$RELEASE_COMMIT" != "$COMMIT" ]; then
  echo "manifest 与 release.json commit 不一致"
  exit 8
fi

DOWNLOAD_URL="${RELEASE_URL%/release.json}/$FILE"

echo
echo "===== 4. 下载 release ====="
echo "$DOWNLOAD_URL"

curl -fL --retry 3 "$DOWNLOAD_URL" -o "$TMP/$FILE"

echo
echo "===== 5. SHA256 校验 ====="

printf '%s  %s\n' "$SHA" "$TMP/$FILE" | sha256sum -c -

echo
echo "===== 6. 检查压缩包路径安全 ====="

python3 - "$TMP/$FILE" <<'PY'
import sys
import tarfile
from pathlib import PurePosixPath

with tarfile.open(sys.argv[1], "r:gz") as tf:
    for m in tf.getmembers():
        p = PurePosixPath(m.name)
        if p.is_absolute() or ".." in p.parts:
            raise SystemExit("unsafe archive path: " + m.name)

print("ARCHIVE_PATH_OK")
PY

TARGET="$RELEASES/$VERSION"

validate_binary() {
  local BIN="$1"
  local EXPECT_VERSION="$2"
  local EXPECT_COMMIT="$3"

  test -x "$BIN" || return 1

  local GOT_VERSION
  local GOT_COMMIT

  GOT_VERSION="$("$BIN" -version 2>/dev/null | awk -F= '$1=="version"{print $2}')"
  GOT_COMMIT="$("$BIN" -version 2>/dev/null | awk -F= '$1=="commit"{print $2}')"

  [ "$GOT_VERSION" = "$EXPECT_VERSION" ] &&
  [ "$GOT_COMMIT" = "$EXPECT_COMMIT" ]
}

echo
echo "===== 7. 准备本地 release ====="

if [ -d "$TARGET" ]; then
  if validate_binary "$TARGET/btcproxy" "$VERSION" "$COMMIT"; then
    echo "本地已有正确 release：$TARGET"
  else
    BAD="$RELEASES/.invalid-$VERSION-$(date +%s)"
    echo "发现损坏或错误的本地 release"
    echo "移动到: $BAD"
    mv "$TARGET" "$BAD"
  fi
fi

if [ ! -d "$TARGET" ]; then
  STAGE="$RELEASES/.incoming-$VERSION-$$"

  rm -rf "$STAGE"
  mkdir -p "$STAGE"

  tar -xzf "$TMP/$FILE" -C "$STAGE"

  echo
  echo "===== 包内 btcproxy SHA256 ====="

  (
    cd "$STAGE"
    sha256sum -c btcproxy.sha256
  )

  if ! validate_binary "$STAGE/btcproxy" "$VERSION" "$COMMIT"; then
    echo "解压后的二进制版本/commit 不正确"
    rm -rf "$STAGE"
    exit 9
  fi

  mv "$STAGE" "$TARGET"
fi

echo
echo "===== 8. 配置 runtime links ====="

ln -sfn ../../shared/config.yaml "$TARGET/config.yaml"
ln -sfn ../../shared/.env "$TARGET/.env"

rm -rf "$TARGET/tls"
ln -s ../../shared/tls "$TARGET/tls"

cp -f "$TARGET/docker-compose.example.yml" "$TARGET/docker-compose.yml"

echo
echo "===== 9. 检查 Compose ====="

(
  cd "$TARGET"
  docker compose -p btcproxy config >/dev/null
)

echo "COMPOSE_CONFIG_OK"

echo
echo "===== 10. 当前运行状态 ====="

OLD_TARGET="$(readlink -f "$CURRENT" 2>/dev/null || true)"

if [ -n "$OLD_TARGET" ]; then
  echo "Current release: $OLD_TARGET"
else
  echo "Current release: unknown"
fi

RUNNING_VERSION="$(docker exec "$CONTAINER" /app/btcproxy -version 2>/dev/null | awk -F= '$1=="version"{print $2}' || true)"
RUNNING_COMMIT="$(docker exec "$CONTAINER" /app/btcproxy -version 2>/dev/null | awk -F= '$1=="commit"{print $2}' || true)"

echo "Running version: ${RUNNING_VERSION:-none}"
echo "Running commit : ${RUNNING_COMMIT:-none}"

if [ "$RUNNING_VERSION" = "$VERSION" ] &&
   [ "$RUNNING_COMMIT" = "$COMMIT" ]; then
  cd "$ROOT"
  ln -sfn "releases/$VERSION" current

  echo
  echo "========================================"
  echo "已经运行目标版本，无需升级"
  echo "Version: $VERSION"
  echo "Commit : $COMMIT"
  echo "========================================"

  exit 0
fi

echo
echo "===== 11. 构建独立 candidate image ====="

BUILD_CONTEXT="$TMP/docker-build"
mkdir -p "$BUILD_CONTEXT"

cp "$TARGET/btcproxy" "$BUILD_CONTEXT/btcproxy"
cp "$TARGET/Dockerfile" "$BUILD_CONTEXT/Dockerfile"

CANDIDATE_IMAGE="btcproxy-candidate:$VERSION"

docker build   --no-cache   --progress=plain   -t "$CANDIDATE_IMAGE"   "$BUILD_CONTEXT"

echo
echo "===== 12. 启动前验证 candidate image ====="

IMAGE_INFO="$(docker run --rm --entrypoint /app/btcproxy "$CANDIDATE_IMAGE" -version)"

echo "$IMAGE_INFO"

IMAGE_VERSION="$(printf '%s\n' "$IMAGE_INFO" | awk -F= '$1=="version"{print $2}')"
IMAGE_COMMIT="$(printf '%s\n' "$IMAGE_INFO" | awk -F= '$1=="commit"{print $2}')"

if [ "$IMAGE_VERSION" != "$VERSION" ] ||
   [ "$IMAGE_COMMIT" != "$COMMIT" ]; then
  echo "Candidate image 身份验证失败"
  echo "当前运行容器没有被修改"
  exit 10
fi

echo "CANDIDATE_IMAGE_OK"

echo
echo "===== 13. 保存旧 image ====="

OLD_IMAGE_ID="$(docker inspect -f '{{.Image}}' "$CONTAINER" 2>/dev/null || true)"

if [ -z "$OLD_IMAGE_ID" ]; then
  echo "无法取得当前 btcproxy container image"
  echo "为了保证可回滚，拒绝继续"
  exit 11
fi

docker tag "$OLD_IMAGE_ID" "$ROLLBACK_IMAGE"

echo "Rollback image: $OLD_IMAGE_ID"

rollback() {
  set +e

  echo
  echo "!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!"
  echo " 新版本验证失败，自动回滚"
  echo "!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!"

  docker rm -f "$CONTAINER" >/dev/null 2>&1 || true
  docker tag "$ROLLBACK_IMAGE" "$RUNTIME_IMAGE"

  if [ -n "$OLD_TARGET" ] && [ -d "$OLD_TARGET" ]; then
    ln -sfn "$OLD_TARGET" "$CURRENT"

    (
      cd "$OLD_TARGET"
      docker compose -p btcproxy up -d --no-build --force-recreate
    )
  else
    echo "无法确定旧 release 目录"
    echo "Rollback image 已保留为:"
    echo "$ROLLBACK_IMAGE"
    return 1
  fi

  sleep 2

  echo
  echo "===== 回滚后的版本 ====="

  docker exec "$CONTAINER" /app/btcproxy -version || true
}

echo
echo "===== 14. 切换 runtime image ====="

docker tag "$CANDIDATE_IMAGE" "$RUNTIME_IMAGE"

echo
echo "===== 15. 删除旧容器 ====="

docker rm -f "$CONTAINER" >/dev/null 2>&1 || true

echo
echo "===== 16. 切换 current ====="

cd "$ROOT"
ln -sfn "releases/$VERSION" current

echo "current -> $(readlink -f current)"

echo
echo "===== 17. 启动新版本 ====="

if ! (
  cd "$TARGET"
  docker compose -p btcproxy up -d --no-build --force-recreate
); then
  rollback
  exit 20
fi

echo
echo "===== 18. Runtime 健康检查 ====="

OK=0

for i in $(seq 1 30); do
  RUNNING="$(docker inspect -f '{{.State.Running}}' "$CONTAINER" 2>/dev/null || true)"
  ACTUAL_VERSION="$(docker exec "$CONTAINER" /app/btcproxy -version 2>/dev/null | awk -F= '$1=="version"{print $2}' || true)"
  ACTUAL_COMMIT="$(docker exec "$CONTAINER" /app/btcproxy -version 2>/dev/null | awk -F= '$1=="commit"{print $2}' || true)"

  if [ "$RUNNING" = "true" ] &&
     [ "$ACTUAL_VERSION" = "$VERSION" ] &&
     [ "$ACTUAL_COMMIT" = "$COMMIT" ]; then
    OK=1
    break
  fi

  sleep 1
done

if [ "$OK" != "1" ]; then
  echo "新版本 runtime 身份验证失败"
  rollback
  exit 21
fi

echo
echo "===== 19. Web listener 检查 ====="

HTTP_CODE="$(curl -sS -o "$TMP/http.out" -w '%{http_code}' http://127.0.0.1:8080/api/version || true)"

case "$HTTP_CODE" in
  200|401)
    echo "Web API HTTP $HTTP_CODE : OK"
    ;;
  *)
    echo "Web API HTTP $HTTP_CODE"
    echo "执行回滚"
    rollback
    exit 22
    ;;
esac

echo
echo "========================================"
echo " BTCProxy 升级成功"
echo "========================================"
echo
echo "Channel : $CHANNEL"
echo "Version : $VERSION"
echo "Commit  : $COMMIT"
echo "Current : $(readlink -f "$CURRENT")"
echo

docker exec "$CONTAINER" /app/btcproxy -version

echo
echo "===== Docker ====="

(
  cd "$TARGET"
  docker compose -p btcproxy ps
)

echo
echo "===== 最近日志 ====="

docker logs --tail 30 "$CONTAINER"
