#!/usr/bin/env bash

DEPLOY_LIB_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
ROOT="$(cd "$DEPLOY_LIB_DIR/.." && pwd)"
COMPOSE_FILE="$ROOT/docker-compose.prod.yml"
ENV_FILE="$ROOT/.env.docker"
ENV_EXAMPLE_FILE="$ROOT/.env.docker.example"
BACKUP_DIR="$ROOT/backups"
DEPLOY_STATE_DIR="$ROOT/.deploy"
RELEASES_DIR="$DEPLOY_STATE_DIR/releases"
CURRENT_RELEASE_FILE="$DEPLOY_STATE_DIR/current-release.env"
PREVIOUS_RELEASE_FILE="$DEPLOY_STATE_DIR/previous-release.env"

DEPLOY_IMAGE_TAG="${DEPLOY_IMAGE_TAG:-}"
LAST_DB_BACKUP=""
LAST_UPLOADS_BACKUP=""

log() {
  printf '%s\n' "[$(basename "$0")] $*"
}

warn() {
  printf '%s\n' "警告: $*" >&2
}

die() {
  printf '%s\n' "错误: $*" >&2
  exit 1
}

require_command() {
  command -v "$1" >/dev/null 2>&1 || die "未找到命令: $1"
}

require_docker() {
  require_command docker
  docker compose version >/dev/null 2>&1 || die "未找到 Docker Compose v2，请确认可执行 docker compose"
}

validate_image_tag() {
  local tag="$1"
  printf '%s' "$tag" | grep -Eq '^[A-Za-z0-9_][A-Za-z0-9_.-]{0,127}$' ||
    die "镜像 tag 不合法: $tag"
}

set_deploy_image_tag() {
  local tag="$1"
  [ -n "$tag" ] || die "镜像 tag 不能为空"
  validate_image_tag "$tag"
  DEPLOY_IMAGE_TAG="$tag"
}

infer_image_tag_from_tar() {
  local image_tar="$1"
  local base tag

  base="$(basename "$image_tar")"
  tag="${base#thebet365-images-}"
  tag="${tag%.tar}"

  if [ "$tag" != "$base" ] && [ -n "$tag" ]; then
    printf '%s' "$tag"
  fi
}

compose() {
  local tag
  tag="$(current_image_tag)"
  IMAGE_TAG="$tag" docker compose -f "$COMPOSE_FILE" --env-file "$ENV_FILE" "$@"
}

ensure_env_file() {
  if [ -f "$ENV_FILE" ]; then
    return 0
  fi

  if [ ! -f "$ENV_EXAMPLE_FILE" ]; then
    die "未找到 .env.docker 或 .env.docker.example"
  fi

  cp "$ENV_EXAMPLE_FILE" "$ENV_FILE"
  warn "已从 .env.docker.example 创建 .env.docker"
  warn "请先修改 POSTGRES_PASSWORD、JWT_SECRET、短信配置和端口后再重新执行部署脚本"
  return 1
}

env_value() {
  local key="$1"
  local line
  line="$(grep -E "^[[:space:]]*${key}=" "$ENV_FILE" | tail -n 1 || true)"
  line="${line#*=}"
  line="${line%$'\r'}"
  line="${line%\"}"
  line="${line#\"}"
  line="${line%\'}"
  line="${line#\'}"
  printf '%s' "$line"
}

set_env_value() {
  local key="$1"
  local value="$2"
  local tmp

  tmp="$(mktemp)"
  if grep -Eq "^[[:space:]]*${key}=" "$ENV_FILE"; then
    awk -v key="$key" -v value="$value" '
      BEGIN { done = 0 }
      $0 ~ "^[[:space:]]*" key "=" {
        if (!done) {
          print key "=" value
          done = 1
        }
        next
      }
      { print }
      END {
        if (!done) print key "=" value
      }
    ' "$ENV_FILE" > "$tmp"
  else
    cp "$ENV_FILE" "$tmp"
    printf '\n%s=%s\n' "$key" "$value" >> "$tmp"
  fi
  mv "$tmp" "$ENV_FILE"
}

current_image_tag() {
  local tag="${DEPLOY_IMAGE_TAG:-}"

  if [ -z "$tag" ] && [ -f "$ENV_FILE" ]; then
    tag="$(env_value IMAGE_TAG)"
  fi

  tag="${tag:-latest}"
  validate_image_tag "$tag"
  printf '%s' "$tag"
}

persist_image_tag() {
  local tag
  tag="$(current_image_tag)"
  set_env_value IMAGE_TAG "$tag"
  log "当前发布镜像 tag: $tag"
}

validate_prod_env() {
  local allow_defaults="${1:-false}"
  local postgres_password jwt_secret seed_database bind_addr

  postgres_password="$(env_value POSTGRES_PASSWORD)"
  jwt_secret="$(env_value JWT_SECRET)"
  seed_database="$(env_value SEED_DATABASE)"
  bind_addr="$(env_value BIND_ADDR)"

  [ -n "$postgres_password" ] || die ".env.docker 缺少 POSTGRES_PASSWORD"
  [ -n "$jwt_secret" ] || die ".env.docker 缺少 JWT_SECRET"

  if [ "$postgres_password" = "thebet365" ]; then
    warn "POSTGRES_PASSWORD 仍是示例值 thebet365，生产环境建议尽快修改"
  fi
  if [ "$jwt_secret" = "change-me-in-production-use-long-random-string" ]; then
    warn "JWT_SECRET 仍是示例值，生产环境建议尽快修改"
  fi

  if [ "$seed_database" = "true" ]; then
    warn ".env.docker 中 SEED_DATABASE=true 会让 api 每次启动都执行 seed；生产建议设为 false，首次部署脚本会按需一次性 seed"
  fi

  if [ "${bind_addr:-127.0.0.1}" = "0.0.0.0" ]; then
    warn "BIND_ADDR=0.0.0.0 会让 player/admin 端口直接对外监听；生产建议经宝塔反代访问"
  fi

  if [ "$(env_value CHUANGLAN_ACCOUNT)" = "your_account" ] || [ "$(env_value CHUANGLAN_PASSWORD)" = "your_password" ]; then
    warn "创蓝短信账号仍是示例值，短信验证码功能上线前需要改为真实配置"
  fi
}

wait_for_service_health() {
  local service="$1"
  local timeout="${2:-120}"
  local start now container_id status

  start="$(date +%s)"
  while true; do
    container_id="$(compose ps -q "$service" 2>/dev/null || true)"
    if [ -n "$container_id" ]; then
      status="$(docker inspect -f '{{if .State.Health}}{{.State.Health.Status}}{{else}}{{.State.Status}}{{end}}' "$container_id" 2>/dev/null || true)"
      if [ "$status" = "healthy" ] || [ "$status" = "running" ]; then
        return 0
      fi
    fi

    now="$(date +%s)"
    if [ $((now - start)) -ge "$timeout" ]; then
      compose logs --tail=120 "$service" || true
      die "$service 在 ${timeout}s 内未就绪"
    fi
    sleep 2
  done
}

wait_for_stack_ready() {
  wait_for_service_health api 180
  wait_for_service_health player 120
  wait_for_service_health admin 120
}

start_infra() {
  log "启动 PostgreSQL / Redis"
  compose up -d postgres redis
  wait_for_service_health postgres 120
  wait_for_service_health redis 120
}

build_app_images() {
  log "构建 api / player / admin 镜像: $(current_image_tag)"
  compose build api player admin
}

load_image_tar() {
  local image_tar="$1"
  [ -f "$image_tar" ] || die "镜像包不存在: $image_tar"
  log "加载镜像包: $image_tar"
  docker load -i "$image_tar"
}

require_images_for_current_tag() {
  local tag
  tag="$(current_image_tag)"
  docker image inspect "thebet365-api:$tag" >/dev/null 2>&1 || die "缺少镜像: thebet365-api:$tag"
  docker image inspect "thebet365-player:$tag" >/dev/null 2>&1 || die "缺少镜像: thebet365-player:$tag"
  docker image inspect "thebet365-admin:$tag" >/dev/null 2>&1 || die "缺少镜像: thebet365-admin:$tag"
}

run_prisma_migrations() {
  log "执行 Prisma 迁移"
  compose run --rm --no-deps --entrypoint sh api -c 'cd /app/apps/api && npx prisma migrate deploy && npx prisma generate'
}

show_prisma_status() {
  log "检查 Prisma 迁移状态"
  compose exec -T api sh -c 'cd /app/apps/api && npx prisma migrate status'
}

safe_backup_prefix() {
  local prefix="${1:-manual}"
  prefix="$(printf '%s' "$prefix" | tr -c 'A-Za-z0-9_.-' '-')"
  prefix="${prefix:-manual}"
  printf '%s' "$prefix"
}

write_checksum() {
  local file="$1"
  local dir base

  dir="$(dirname "$file")"
  base="$(basename "$file")"
  if command -v sha256sum >/dev/null 2>&1; then
    (cd "$dir" && sha256sum "$base" > "$base.sha256")
  elif command -v shasum >/dev/null 2>&1; then
    (cd "$dir" && shasum -a 256 "$base" > "$base.sha256")
  else
    warn "未找到 sha256sum 或 shasum，跳过校验文件: $file"
    return 0
  fi
  log "校验文件完成: $file.sha256"
}

backup_database() {
  local prefix="${1:-manual}"
  local stamp backup_file

  require_command gzip
  mkdir -p "$BACKUP_DIR"
  prefix="$(safe_backup_prefix "$prefix")"
  stamp="$(date +%Y%m%d-%H%M%S)"
  backup_file="$BACKUP_DIR/thebet365-db-${prefix}-${stamp}.sql.gz"

  log "备份 PostgreSQL -> $backup_file"
  compose exec -T postgres pg_dump -U thebet365 -d thebet365 -F p | gzip -c > "$backup_file"
  write_checksum "$backup_file"
  LAST_DB_BACKUP="$backup_file"
  log "数据库备份完成: $backup_file"
}

backup_uploads() {
  local prefix="${1:-manual}"
  local stamp backup_file

  mkdir -p "$BACKUP_DIR"
  prefix="$(safe_backup_prefix "$prefix")"
  stamp="$(date +%Y%m%d-%H%M%S)"
  backup_file="$BACKUP_DIR/thebet365-uploads-${prefix}-${stamp}.tar.gz"

  log "备份 uploads 卷 -> $backup_file"
  compose exec -T api sh -c 'cd /app && mkdir -p uploads && tar -czf - uploads' > "$backup_file"
  write_checksum "$backup_file"
  LAST_UPLOADS_BACKUP="$backup_file"
  log "uploads 备份完成: $backup_file"
}

prune_old_backups() {
  local days
  days="$(env_value BACKUP_RETENTION_DAYS)"
  [ -n "$days" ] || return 0

  case "$days" in
    *[!0-9]*)
      warn "BACKUP_RETENTION_DAYS 不是数字，跳过自动清理: $days"
      return 0
      ;;
  esac

  [ "$days" -gt 0 ] || return 0
  [ -d "$BACKUP_DIR" ] || return 0
  log "清理 ${days} 天前的备份"
  find "$BACKUP_DIR" -type f \( -name '*.sql.gz' -o -name '*.tar.gz' -o -name '*.sha256' \) -mtime +"$days" -delete
}

admin_user_count() {
  compose exec -T postgres psql -U thebet365 -d thebet365 -tAc "select count(*) from users where username = 'admin';" 2>/dev/null | tr -d '[:space:]'
}

seed_production_if_missing_admin() {
  local count
  count="$(admin_user_count || true)"
  [ -n "$count" ] || die "无法检查 admin 用户，请确认数据库迁移已成功"

  if [ "$count" = "0" ]; then
    log "未发现 admin 用户，执行一次生产 seed"
    compose run --rm --no-deps --entrypoint sh -e SEED_MODE=production -e NODE_ENV=production api -c 'cd /app/apps/api && node dist/infrastructure/database/seed-cli.js'
  else
    log "已存在 admin 用户，跳过生产 seed"
  fi
}

current_release_tag() {
  if [ -f "$CURRENT_RELEASE_FILE" ]; then
    grep -E '^tag=' "$CURRENT_RELEASE_FILE" | tail -n 1 | cut -d= -f2- || true
  fi
}

record_release_state() {
  local source="${1:-deploy}"
  local tag stamp safe_tag manifest previous_tag

  tag="$(current_image_tag)"
  stamp="$(date -u +%Y%m%dT%H%M%SZ)"
  safe_tag="$(printf '%s' "$tag" | tr -c 'A-Za-z0-9_.-' '-')"
  manifest="$RELEASES_DIR/${stamp}-${safe_tag}.env"
  previous_tag="$(current_release_tag)"

  mkdir -p "$RELEASES_DIR"
  if [ -f "$CURRENT_RELEASE_FILE" ]; then
    cp "$CURRENT_RELEASE_FILE" "$PREVIOUS_RELEASE_FILE"
  fi

  {
    printf 'tag=%s\n' "$tag"
    printf 'previous_tag=%s\n' "$previous_tag"
    printf 'deployed_at=%s\n' "$stamp"
    printf 'source=%s\n' "$source"
    printf 'api_image=thebet365-api:%s\n' "$tag"
    printf 'player_image=thebet365-player:%s\n' "$tag"
    printf 'admin_image=thebet365-admin:%s\n' "$tag"
    [ -n "$LAST_DB_BACKUP" ] && printf 'db_backup=%s\n' "$LAST_DB_BACKUP"
    [ -n "$LAST_UPLOADS_BACKUP" ] && printf 'uploads_backup=%s\n' "$LAST_UPLOADS_BACKUP"
  } > "$manifest"

  cp "$manifest" "$CURRENT_RELEASE_FILE"
  log "发布状态已记录: $manifest"
}

print_stack_urls() {
  local player_port admin_port bind_addr tag
  player_port="$(env_value PLAYER_PORT)"
  admin_port="$(env_value ADMIN_PORT)"
  bind_addr="$(env_value BIND_ADDR)"
  tag="$(current_image_tag)"
  player_port="${player_port:-8082}"
  admin_port="${admin_port:-8081}"
  bind_addr="${bind_addr:-127.0.0.1}"

  printf '\n'
  printf '%s\n' "部署完成："
  printf '%s\n' "  镜像 tag: ${tag}"
  printf '%s\n' "  玩家端: http://${bind_addr}:${player_port}"
  printf '%s\n' "  管理端: http://${bind_addr}:${admin_port}"
  printf '%s\n' "  API: 仅在 Docker 网络内暴露，由 player/admin 容器和宝塔反代链路访问"
  printf '%s\n' "  状态: docker compose -f docker-compose.prod.yml --env-file .env.docker ps"
  printf '%s\n' "  当前发布: .deploy/current-release.env"
}
