#!/usr/bin/env bash
# ============================================================
# EchoChat 公网部署脚本（Phase 2e-2 Task 14）
# ------------------------------------------------------------
# 职责：
#   1. 校验 deploy/.env 内关键参数（ANNOUNCED_IP / 密码替换等）
#   2. 给出防火墙/安全组端口放通 checklist（本地用 nc 探测）
#   3. 启动 docker compose --profile public（含 media-server + coturn）
#
# 不做的事：
#   - 不替你改 iptables / ufw（不同云/OS 命令不一，避免误操作）
#   - 不替你改 hosts / DNS（域名解析请预先完成）
#
# 用法：
#   cp deploy/.env.public.example deploy/.env
#   vim deploy/.env    # 按 checklist 替换所有 _REPLACE_WITH_*_
#   ./scripts/deploy-public.sh
# ============================================================
set -uo pipefail

SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
ROOT_DIR="$(cd "$SCRIPT_DIR/.." && pwd)"
DEPLOY_DIR="$ROOT_DIR/deploy"
ENV_FILE="$DEPLOY_DIR/.env"

COLOR_GREEN='\033[0;32m'
COLOR_YELLOW='\033[0;33m'
COLOR_RED='\033[0;31m'
COLOR_CYAN='\033[0;36m'
COLOR_RESET='\033[0m'

log_info()  { printf "${COLOR_CYAN}[INFO]${COLOR_RESET}  %s\n" "$*"; }
log_ok()    { printf "${COLOR_GREEN}[OK]${COLOR_RESET}    %s\n" "$*"; }
log_warn()  { printf "${COLOR_YELLOW}[WARN]${COLOR_RESET}  %s\n" "$*"; }
log_err()   { printf "${COLOR_RED}[ERR]${COLOR_RESET}   %s\n" "$*"; }

# 计数器
ERR_COUNT=0
WARN_COUNT=0
err()   { log_err "$*";  ERR_COUNT=$((ERR_COUNT + 1)); }
warn()  { log_warn "$*"; WARN_COUNT=$((WARN_COUNT + 1)); }

# ------------------------------------------------------------
# Step 1：env 文件校验
# ------------------------------------------------------------
step_validate_env() {
  log_info "=== Step 1/4: 校验 deploy/.env ==="

  if [[ ! -f "$ENV_FILE" ]]; then
    err "deploy/.env 不存在"
    log_info "请先执行：cp deploy/.env.public.example deploy/.env 并填写关键参数"
    return 1
  fi

  # shellcheck disable=SC1090
  set -a
  source "$ENV_FILE"
  set +a

  # DEPLOY_MODE 必须是 public（防止误把 local 的 env 复制过来）
  if [[ "${DEPLOY_MODE:-}" != "public" ]]; then
    warn "deploy/.env 中 DEPLOY_MODE=\"${DEPLOY_MODE:-<unset>}\"，不是 public；确认你要走公网部署？"
  fi

  # MEDIASOUP_ANNOUNCED_IP 必填
  if [[ -z "${MEDIASOUP_ANNOUNCED_IP:-}" ]]; then
    err "MEDIASOUP_ANNOUNCED_IP 未设置，远端浏览器将无法建连 mediasoup"
    log_info "请填写服务器的公网 IP，例：MEDIASOUP_ANNOUNCED_IP=203.0.113.42"
  else
    log_ok "MEDIASOUP_ANNOUNCED_IP=${MEDIASOUP_ANNOUNCED_IP}"
  fi

  # 检查占位符没被替换
  local placeholders=(
    POSTGRES_PASSWORD
    REDIS_PASSWORD
    MINIO_ROOT_PASSWORD
    JWT_SECRET
    MEDIA_INTERNAL_TOKEN
    TURN_USERNAME
    TURN_PASSWORD
  )
  for var in "${placeholders[@]}"; do
    local val="${!var:-}"
    if [[ -z "$val" ]]; then
      # REDIS_PASSWORD 允许为空（仅内网访问时）
      if [[ "$var" == "REDIS_PASSWORD" ]]; then
        warn "$var 为空（仅当 Redis 不对公网暴露时可接受）"
      else
        err "$var 为空"
      fi
    elif [[ "$val" == _REPLACE_WITH_* ]]; then
      err "$var 仍是占位符（${val}），请替换为真实值"
    fi
  done

  # JWT_SECRET 长度
  if [[ -n "${JWT_SECRET:-}" && ${#JWT_SECRET} -lt 32 ]]; then
    warn "JWT_SECRET 长度仅 ${#JWT_SECRET} 字符，推荐 >= 64 字符（openssl rand -hex 32）"
  fi

  # Task 16 Nit（代码审查 2026-04-23）：REDIS_PASSWORD 与 redis.conf requirepass 联动校验
  # 背景：REDIS_PASSWORD 只影响 go-service / media-server 的连接侧，
  #       若 deploy/docker/redis/redis.conf 未设置 requirepass，Redis 本身会接受无密码连接（相当于裸奔）
  local redis_conf="$DEPLOY_DIR/docker/redis/redis.conf"
  if [[ -f "$redis_conf" ]]; then
    local has_requirepass
    has_requirepass=$(grep -E '^\s*requirepass\s+\S+' "$redis_conf" || true)
    if [[ -n "${REDIS_PASSWORD:-}" ]]; then
      if [[ -z "$has_requirepass" ]]; then
        err "REDIS_PASSWORD 已设置，但 $redis_conf 未启用 requirepass —— Redis 仍允许空密码登录，公网暴露时务必同步开启"
        log_info "建议：在 redis.conf 追加 requirepass \$REDIS_PASSWORD（或在 docker-compose command 中传入 --requirepass）"
      else
        log_ok "Redis requirepass 已在 redis.conf 启用，与 REDIS_PASSWORD 联动"
      fi
    else
      if [[ -n "$has_requirepass" ]]; then
        warn "redis.conf 启用了 requirepass，但 deploy/.env 的 REDIS_PASSWORD 为空 —— go-service/media-server 连接会失败"
      fi
    fi
  else
    warn "未找到 $redis_conf，跳过 Redis 密码联动校验"
  fi

  # TURN 开关一致性
  if [[ "${TURN_ENABLED:-false}" != "true" ]]; then
    warn "TURN_ENABLED=${TURN_ENABLED:-false}，公网部署下建议开启 TURN 作为对称 NAT fallback"
  fi

  if (( ERR_COUNT > 0 )); then
    log_err "env 校验失败：$ERR_COUNT 个错误，$WARN_COUNT 个警告"
    return 1
  fi
  log_ok "env 校验通过（$WARN_COUNT 个警告）"
}

# ------------------------------------------------------------
# Step 2：防火墙/端口占用检查（本机视角）
# ------------------------------------------------------------
# 说明：云安全组需要在云控制台放通，本脚本只能检测本机 iptables + 当前占用
step_check_ports() {
  log_info "=== Step 2/4: 本机端口占用检查 ==="

  local ports=(
    "8085/tcp|Go backend API"
    "3300/tcp|media-server (internal, 不建议暴露公网)"
    "40000/udp|mediasoup RTC (端口范围起点)"
    "40199/udp|mediasoup RTC (端口范围终点)"
  )

  if [[ "${TURN_ENABLED:-false}" == "true" ]]; then
    ports+=(
      "${TURN_LISTEN_PORT:-3478}/udp|coturn STUN/TURN"
      "${TURN_LISTEN_PORT:-3478}/tcp|coturn STUN/TURN"
      "${TURN_TLS_PORT:-5349}/tcp|coturn TLS"
    )
  fi

  for entry in "${ports[@]}"; do
    local port="${entry%%|*}"
    local desc="${entry##*|}"
    local port_num="${port%/*}"
    local proto="${port##*/}"
    if [[ "$proto" == "tcp" ]]; then
      if lsof -iTCP:"$port_num" -sTCP:LISTEN -n -P >/dev/null 2>&1; then
        warn "TCP:$port_num 已被占用（${desc}），compose 启动可能失败"
      else
        log_ok "TCP:$port_num 空闲（${desc}）"
      fi
    else
      # UDP 只做提示，不中断
      log_ok "UDP:$port_num 待 docker compose 绑定（${desc}）"
    fi
  done

  printf "\n${COLOR_YELLOW}⚠️  云安全组/iptables 放通清单${COLOR_RESET}（本脚本无法自动执行，请手动确认）：\n"
  printf "    TCP:8085                   Go backend API\n"
  printf "    UDP:40000-40199            mediasoup RTC 流量\n"
  printf "    TCP:40000-40199            mediasoup ICE-TCP fallback\n"
  if [[ "${TURN_ENABLED:-false}" == "true" ]]; then
    printf "    UDP:${TURN_LISTEN_PORT:-3478}                    coturn STUN/TURN\n"
    printf "    TCP:${TURN_LISTEN_PORT:-3478}                    coturn STUN/TURN\n"
    printf "    TCP:${TURN_TLS_PORT:-5349}                    coturn TLS\n"
    printf "    UDP:${TURN_MIN_PORT:-49160}-${TURN_MAX_PORT:-49200}            coturn 中继流量\n"
  fi
  echo ""
}

# ------------------------------------------------------------
# Step 3：Docker 环境自检
# ------------------------------------------------------------
step_check_docker() {
  log_info "=== Step 3/4: Docker 环境自检 ==="

  if ! command -v docker >/dev/null 2>&1; then
    err "未检测到 docker 命令"
    return 1
  fi
  log_ok "docker: $(docker --version)"

  if ! docker info >/dev/null 2>&1; then
    err "docker daemon 无法连接（sudo systemctl start docker?）"
    return 1
  fi
  log_ok "docker daemon 可访问"

  if ! docker-compose version >/dev/null 2>&1; then
    err "未检测到 docker-compose（需 Compose V2 plugin）"
    return 1
  fi
  log_ok "docker-compose: $(docker-compose version | head -n1)"
}

# ------------------------------------------------------------
# Step 4：启动 public profile
# ------------------------------------------------------------
step_launch() {
  log_info "=== Step 4/4: 启动 docker-compose (public profile) ==="

  if (( ERR_COUNT > 0 )); then
    log_err "前面校验有错误，拒绝启动（先修正后再跑脚本）"
    return 1
  fi

  cd "$DEPLOY_DIR"
  if [[ "${TURN_ENABLED:-false}" == "true" ]]; then
    log_info "TURN_ENABLED=true，使用 --profile public 启动（含 coturn）"
    docker-compose -f docker-compose.dev.yml --profile public up -d --build
  else
    log_info "TURN_ENABLED=false，跳过 coturn（仅默认服务）"
    docker-compose -f docker-compose.dev.yml up -d --build
  fi

  log_ok "启动指令已发出，等待 healthcheck（最多 180 秒）..."
  local waited=0
  while (( waited < 180 )); do
    local health
    health="$(docker inspect -f '{{.State.Health.Status}}' echochat-media-server 2>/dev/null || echo 'starting')"
    if [[ "$health" == "healthy" ]]; then
      log_ok "media-server healthy（$waited 秒）"
      break
    fi
    if [[ "$health" == "unhealthy" ]]; then
      log_err "media-server unhealthy，查看日志：docker logs echochat-media-server"
      return 1
    fi
    sleep 3
    waited=$((waited + 3))
  done

  printf "\n${COLOR_GREEN}=============== 公网部署完成 ===============${COLOR_RESET}\n"
  printf "  Go 后端 API:    http://${MEDIASOUP_ANNOUNCED_IP}:8085\n"
  printf "  media-server:   http://127.0.0.1:3300 (仅内网，勿对外暴露)\n"
  if [[ "${TURN_ENABLED:-false}" == "true" ]]; then
    printf "  coturn TURN:    ${MEDIASOUP_ANNOUNCED_IP}:${TURN_LISTEN_PORT:-3478}\n"
  fi
  printf "\n  查看状态:       docker compose -f deploy/docker-compose.dev.yml ps\n"
  printf "  查看日志:       docker compose -f deploy/docker-compose.dev.yml logs -f [service]\n"
  printf "  停止全栈:       ./scripts/stop.sh full\n"
  printf "${COLOR_GREEN}============================================${COLOR_RESET}\n"
}

main() {
  step_validate_env || exit 1
  step_check_ports   # Step 2，不会 return 1，只收集 warn
  step_check_docker || exit 1   # Step 3
  # 二次确认（至少暴露 1 个明显错误时已退出，这里只对警告放行）
  if [[ "${1:-}" != "--yes" ]] && (( WARN_COUNT > 0 )); then
    printf "\n${COLOR_YELLOW}上述 %d 个警告仅提示，不阻塞启动。继续部署？(y/N): ${COLOR_RESET}" "$WARN_COUNT"
    read -r confirm
    case "$confirm" in
      y|Y|yes|YES) ;;
      *) log_info "已取消部署"; exit 0 ;;
    esac
  fi
  step_launch
}

main "$@"
