#!/usr/bin/env bash
# compose-ctl.sh — gadmin 本地全栈 docker-compose 控制脚本
#
# 用法见 `./compose-ctl.sh -h`

set -euo pipefail
cd "$(dirname "$0")"

# ─── 项目命名（pod / 镜像 prefix 都用这个） ─────────────────────
# PROJECT_NAME 默认取项目目录的 basename，例如本项目为 "gadmin-test"。
# - 镜像： <PROJECT_NAME>-{codegen,web,nest-server,temporal-worker}:local
# - pod ： <PROJECT_NAME>-pod  （仅 podman-compose 有 pod 概念）
# 通过 export COMPOSE_PROJECT_NAME 让 docker-compose.yml 中的
# ${COMPOSE_PROJECT_NAME} 变量插值到正确的值。
PROJECT_NAME="$(basename "$(pwd)")"
export COMPOSE_PROJECT_NAME="$PROJECT_NAME"
POD_NAME="${PROJECT_NAME}-pod"
CODEGEN_IMAGE="${PROJECT_NAME}-codegen:local"

# ─── 最低推荐版本（低于会打 warning 但仍尝试执行） ──────────────
MIN_DOCKER="20.10"
MIN_DOCKER_COMPOSE_V2="2.0.0"
MIN_DOCKER_COMPOSE_V1="1.29.0"
MIN_PODMAN="4.0.0"
MIN_PODMAN_COMPOSE="1.0.7"

# 从命令输出中抽取第一个 X.Y(.Z) 形式的版本号
extract_semver() {
  printf '%s' "$1" | grep -oE '[0-9]+\.[0-9]+(\.[0-9]+)?' | head -n1
}

# version_lt "1.0.6" "1.0.7" → 0 (true) 当 $1 < $2
version_lt() {
  [ "$1" = "$2" ] && return 1
  [ "$(printf '%s\n%s\n' "$1" "$2" | sort -V | head -n1)" = "$1" ]
}

warn_if_old() {
  local name="$1" actual="$2" minimum="$3"
  if [ -z "$actual" ]; then
    echo "[compose-ctl] WARN: 无法检测 $name 版本（最低推荐 $minimum）" >&2
    return
  fi
  if version_lt "$actual" "$minimum"; then
    echo "[compose-ctl] WARN: $name $actual < 最低推荐 $minimum" >&2
    echo "[compose-ctl]       可能症状：profiles 不生效 / service_completed_successfully 被忽略 /" >&2
    echo "[compose-ctl]       depends_on 顺序错乱 / 多阶段构建失败。建议升级。" >&2
  fi
}

# ─── 运行时探测 ─────────────────────────────────────────────────
# shell alias (alias docker=podman) 不会传到 bash 脚本里，所以这里要自己找。
# DOCKER_CLI / COMPOSE_CLI 用数组以支持 "docker compose" 这种带空格的命令。
DOCKER_CLI=()
COMPOSE_CLI=()
DOCKER_VER=""
COMPOSE_VER=""
COMPOSE_KIND=""   # 用于挑选最低推荐版本

detect_runtime() {
  if command -v docker >/dev/null 2>&1; then
    DOCKER_CLI=(docker)
    DOCKER_VER=$(extract_semver "$(docker --version 2>/dev/null)")
    if docker compose version >/dev/null 2>&1; then
      COMPOSE_CLI=(docker compose)
      COMPOSE_VER=$(extract_semver "$(docker compose version 2>/dev/null)")
      COMPOSE_KIND="docker-compose-v2"
    elif command -v docker-compose >/dev/null 2>&1; then
      COMPOSE_CLI=(docker-compose)
      COMPOSE_VER=$(extract_semver "$(docker-compose --version 2>/dev/null)")
      # v2 standalone 的版本号也是 2.x，v1 是 1.x
      case "$COMPOSE_VER" in
        2.*) COMPOSE_KIND="docker-compose-v2" ;;
        *)   COMPOSE_KIND="docker-compose-v1" ;;
      esac
    fi
  elif command -v podman >/dev/null 2>&1; then
    DOCKER_CLI=(podman)
    DOCKER_VER=$(extract_semver "$(podman --version 2>/dev/null)")
    if command -v podman-compose >/dev/null 2>&1; then
      COMPOSE_CLI=(podman-compose)
      COMPOSE_VER=$(extract_semver "$(podman-compose --version 2>/dev/null | head -n1)")
      COMPOSE_KIND="podman-compose"
    elif command -v docker-compose >/dev/null 2>&1; then
      COMPOSE_CLI=(docker-compose)
      COMPOSE_VER=$(extract_semver "$(docker-compose --version 2>/dev/null)")
      case "$COMPOSE_VER" in
        2.*) COMPOSE_KIND="docker-compose-v2" ;;
        *)   COMPOSE_KIND="docker-compose-v1" ;;
      esac
    fi
  fi

  if [ ${#DOCKER_CLI[@]} -eq 0 ]; then
    echo "ERROR: 未找到 docker 或 podman" >&2
    echo "提示：alias docker=podman 不会传到 bash 脚本，需要真实安装 docker 或 podman 二进制" >&2
    exit 127
  fi
  if [ ${#COMPOSE_CLI[@]} -eq 0 ]; then
    echo "ERROR: 找到了 ${DOCKER_CLI[*]} 但没找到 compose 命令" >&2
    echo "提示：装 docker-compose 或 podman-compose" >&2
    exit 127
  fi

  echo "[compose-ctl] runtime: ${DOCKER_CLI[*]} ${DOCKER_VER:-?} | compose: ${COMPOSE_CLI[*]} ${COMPOSE_VER:-?}"

  # podman-compose 1.0.7+ 支持 --in-pod=NAME，让所有服务跑在同一个自定义命名 pod 里。
  # docker-compose 没有 pod 概念，跳过。
  COMPOSE_GLOBAL_ARGS=()
  if [ "$COMPOSE_KIND" = "podman-compose" ]; then
    COMPOSE_GLOBAL_ARGS+=(--in-pod="$POD_NAME")
  fi

  # 版本检查（只 warn 不 fail）
  case "${DOCKER_CLI[0]}" in
    docker) warn_if_old "docker"          "$DOCKER_VER"  "$MIN_DOCKER" ;;
    podman) warn_if_old "podman"          "$DOCKER_VER"  "$MIN_PODMAN" ;;
  esac
  case "$COMPOSE_KIND" in
    docker-compose-v2) warn_if_old "docker compose"  "$COMPOSE_VER" "$MIN_DOCKER_COMPOSE_V2" ;;
    docker-compose-v1) warn_if_old "docker-compose"  "$COMPOSE_VER" "$MIN_DOCKER_COMPOSE_V1" ;;
    podman-compose)    warn_if_old "podman-compose"  "$COMPOSE_VER" "$MIN_PODMAN_COMPOSE" ;;
  esac
}

print_help() {
  cat <<'EOF'
compose-ctl.sh — gadmin-test 本地全栈 docker-compose 控制脚本

用法:
  ./compose-ctl.sh [up]        起服务（默认）。codegen 镜像不存在才构建，否则跳过。
  ./compose-ctl.sh --rebuild   强制重建 codegen 后起服务。
                               config/ui/** 或 config/prisma/** 改动后用。
  ./compose-ctl.sh --reset     清数据卷 + 重建 codegen + 起服务（核武器）。
                               业务数据、temporal 历史全部清空，回到种子数据状态。
  ./compose-ctl.sh --down      停服务（保留数据）。
  ./compose-ctl.sh -h, --help  显示本帮助。

运行时:
  脚本会自动探测 docker / podman，自动选择 compose 命令。
  优先级: docker compose > docker-compose > podman-compose

  最低推荐版本（低于会打 warning，不阻断执行）：
    docker            >= 20.10
    docker compose v2 >= 2.0    (插件形式 / 独立 v2 二进制)
    docker-compose v1 >= 1.29   (legacy)
    podman            >= 4.0
    podman-compose    >= 1.0.7

示例:
  首次拉代码后:    ./compose-ctl.sh
  改了 prisma:    ./compose-ctl.sh --rebuild
  数据库脏了:     ./compose-ctl.sh --reset
  下班关掉:       ./compose-ctl.sh --down
EOF
}

print_post_up_guide() {
  cat <<'EOF'

━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
  服务已启动 ✓   首次启动需等 30~60s 让 temporal-server 完成 schema 初始化
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━

访问入口:
  Web 前端          http://localhost:3000
  Server API        http://localhost:8000          (健康检查 /health/live)
  Temporal Web UI   http://localhost:8080
  Temporal gRPC     localhost:7233                 (worker / @temporalio/client)
  PostgreSQL        localhost:5432                 (用户 kavenma / 密码 kavenma)

库划分 (PG 内):
  gadmin_demo            业务库 (server + worker, Prisma 管理)
  temporal               Temporal 元数据库
  temporal_visibility    Temporal Visibility 库 (PG-only)

常用命令:
  查看所有服务状态        docker compose ps
  跟某个服务日志          docker compose logs -f <service>
                          (service: postgres / temporal-server / temporal-ui /
                           temporal-worker / server / server-init / web)
  跟全部日志              docker compose logs -f
  重启某服务              docker compose restart <service>
  进容器排查              docker compose exec <service> sh

停止 / 重置:
  停服务 (保留数据)        ./compose-ctl.sh --down
  config 改了重建          ./compose-ctl.sh --rebuild
  清空数据回到种子状态     ./compose-ctl.sh --reset

提示:
  · 前端开发请用 cd web && yarn dev (本 compose 的 web 是 nginx 静态镜像，无热更新)
  · server-init 是一次性容器，跑完 push:db + seed 后退出 (Exited 0) 是正常的
EOF
}

build_codegen() {
  echo "[compose-ctl] building codegen image ($CODEGEN_IMAGE) ..."
  "${COMPOSE_CLI[@]}" "${COMPOSE_GLOBAL_ARGS[@]}" --profile codegen build codegen
}

up_services() {
  echo "[compose-ctl] starting services (project=$PROJECT_NAME, pod=$POD_NAME) ..."
  "${COMPOSE_CLI[@]}" "${COMPOSE_GLOBAL_ARGS[@]}" up -d --build
  print_post_up_guide
}

# ─── main ──────────────────────────────────────────────────────
# 帮助命令不需要运行时，先处理
case "${1:-up}" in
  -h|--help)
    print_help
    exit 0
    ;;
esac

detect_runtime

case "${1:-up}" in
  up)
    if "${DOCKER_CLI[@]}" image inspect "$CODEGEN_IMAGE" >/dev/null 2>&1; then
      echo "[compose-ctl] codegen image exists, skip build (use --rebuild to force)"
    else
      build_codegen
    fi
    up_services
    ;;
  --rebuild|-r)
    build_codegen
    # podman-compose 1.x 不会因镜像 ID 变化自动重建容器（docker-compose v2 会）。
    # --rebuild 既然是为"改了配置/Dockerfile/代码"准备的，强制 recreate 才能让新镜像生效。
    echo "[compose-ctl] (rebuild) force-recreating containers ..."
    "${COMPOSE_CLI[@]}" "${COMPOSE_GLOBAL_ARGS[@]}" up -d --build --force-recreate
    print_post_up_guide
    ;;
  --reset)
    "${COMPOSE_CLI[@]}" "${COMPOSE_GLOBAL_ARGS[@]}" down -v
    build_codegen
    up_services
    ;;
  --down)
    "${COMPOSE_CLI[@]}" "${COMPOSE_GLOBAL_ARGS[@]}" down
    ;;
  *)
    echo "unknown option: $1" >&2
    echo "" >&2
    print_help >&2
    exit 1
    ;;
esac
