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

# Mac 本机 Docker 开发环境启动脚本。
# 用法：
#   bash deploy/mac-dev.sh
#   bash deploy/mac-dev.sh --reset-db

PROJECT_ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
ENV_FILE="${PROJECT_ROOT}/.env"
ENV_EXAMPLE="${PROJECT_ROOT}/.env.mac-docker.example"
COMPOSE_FILE="${PROJECT_ROOT}/docker-compose.dev.yml"
BACKEND_DATA_DIR="${PROJECT_ROOT}/apps/backend/data"
READY_TIMEOUT="${READY_TIMEOUT:-120}"
HEALTH_URL="${DEV_HEALTH_URL:-http://localhost/health/ready}"
RESET_DB=false
RESET_DB_CONFIRM="${RESET_DB_CONFIRM:-}"
COMPOSE_CMD=(docker compose -f "${COMPOSE_FILE}")

log() {
  printf '\033[1;34m[mac-dev]\033[0m %s\n' "$*"
}

warn() {
  printf '\033[1;33m[mac-dev]\033[0m %s\n' "$*"
}

fail() {
  printf '\033[1;31m[mac-dev]\033[0m %s\n' "$*" >&2
  exit 1
}

print_usage() {
  cat <<'EOF'
用法：
  bash deploy/mac-dev.sh
  bash deploy/mac-dev.sh --reset-db

参数：
  --reset-db    删除 dev Compose PostgreSQL 数据卷 postgres_dev_data，并用当前迁移重新初始化数据库。
  --yes         跳过 --reset-db 的交互确认，也可使用环境变量 RESET_DB_CONFIRM=1。
  -h, --help    显示帮助。

环境变量：
  READY_TIMEOUT  等待容器和健康检查的秒数，默认 120。
  DEV_HEALTH_URL 本机健康检查地址，默认 http://localhost/health/ready。

注意：
  --reset-db 会删除本机开发库里的订单、任务、后台用户、审计日志和迁移记录。
  它不会删除 apps/backend/data 下的平台 JSON 配置、MinIO 文件数据和 node_modules 缓存 volume。
EOF
}

parse_args() {
  while (($# > 0)); do
    case "$1" in
      --reset-db)
        RESET_DB=true
        ;;
      --yes)
        RESET_DB_CONFIRM=1
        ;;
      -h|--help)
        print_usage
        exit 0
        ;;
      *)
        fail "未知参数：$1。执行 bash deploy/mac-dev.sh --help 查看用法。"
        ;;
    esac
    shift
  done
}

read_env_value() {
  local key="$1"
  awk -F= -v key="${key}" '
    $0 !~ /^[[:space:]]*#/ && $1 == key {
      value = substr($0, index($0, "=") + 1)
      gsub(/^[[:space:]]+|[[:space:]]+$/, "", value)
      gsub(/^"|"$/, "", value)
      gsub(/^'\''|'\''$/, "", value)
      print value
      exit
    }
  ' "${ENV_FILE}"
}

read_effective_env_value() {
  local key="$1"
  local default_value="$2"
  local value="${!key:-}"

  if [[ -z "${value}" ]]; then
    value="$(read_env_value "${key}" || true)"
  fi

  printf '%s\n' "${value:-${default_value}}"
}

ensure_docker() {
  command -v docker >/dev/null 2>&1 || fail "未找到 docker，请先安装并启动 Docker Desktop for Mac。"
  docker compose version >/dev/null 2>&1 || fail "未找到 docker compose 插件，请升级 Docker Desktop。"
  command -v curl >/dev/null 2>&1 || fail "未找到 curl，请先安装 curl。"
}

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

  [[ -f "${ENV_EXAMPLE}" ]] || fail "未找到本机开发环境模板：${ENV_EXAMPLE}"
  cp "${ENV_EXAMPLE}" "${ENV_FILE}"
  log "未找到 .env，已从 .env.mac-docker.example 生成本机开发配置。"
}

validate_env_file() {
  [[ "${READY_TIMEOUT}" =~ ^[1-9][0-9]*$ ]] || fail "READY_TIMEOUT 必须是正整数秒数，当前值：${READY_TIMEOUT}"

  validate_database_url_for_dev_compose
}

validate_database_url_for_dev_compose() {
  local database_url
  database_url="$(read_env_value DATABASE_URL)"

  if [[ -z "${database_url}" ]]; then
    return
  fi

  if [[ "${database_url}" == *"@localhost:"* \
    || "${database_url}" == *"@127.0.0.1:"* \
    || "${database_url}" == *"@[::1]:"* ]]; then
    fail ".env 的 DATABASE_URL 当前指向本机地址。Docker dev 后端容器内的 localhost 不是 postgres 容器，请改为 postgres 服务名。"
  fi
}

validate_reset_database_target() {
  local database_url
  database_url="$(read_env_value DATABASE_URL)"

  if [[ -n "${database_url}" && "${database_url}" != *"@postgres:"* ]]; then
    fail "--reset-db 只支持重置本脚本管理的 dev Compose PostgreSQL。当前 DATABASE_URL 未指向 postgres 服务，请手动确认外部数据库后再处理。"
  fi
}

confirm_reset_database() {
  if [[ "${RESET_DB}" != "true" ]]; then
    return
  fi

  validate_reset_database_target

  warn "即将重置 Mac 本机开发 PostgreSQL 数据库：会删除订单、任务、后台用户、审计日志和所有迁移记录。"
  warn "不会删除 apps/backend/data 下的平台 JSON 配置，也不会删除 node_modules 缓存 volume。"

  if [[ "${RESET_DB_CONFIRM}" == "1" ]]; then
    warn "检测到 RESET_DB_CONFIRM=1 或 --yes，跳过交互确认。"
    return
  fi

  if [[ ! -t 0 ]]; then
    fail "当前不是交互式终端。若确认要重置数据库，请执行：RESET_DB_CONFIRM=1 bash deploy/mac-dev.sh --reset-db"
  fi

  local answer
  printf '请输入 RESET 确认重置数据库：'
  read -r answer
  [[ "${answer}" == "RESET" ]] || fail "未确认重置数据库，已停止。"
}

ensure_data_dir() {
  mkdir -p "${BACKEND_DATA_DIR}"
}

check_compose_config() {
  log "检查 Docker Compose dev 配置"
  "${COMPOSE_CMD[@]}" config >/dev/null || fail "Docker Compose dev 配置校验失败。"
}

resolve_compose_project_name() {
  local project_name
  project_name="$(read_env_value COMPOSE_PROJECT_NAME || true)"

  if [[ -z "${project_name}" ]]; then
    project_name="$(basename "${PROJECT_ROOT}")"
  fi

  printf '%s\n' "${project_name}"
}

resolve_postgres_volume_name() {
  local container_id volume_name project_name expected_volume

  container_id="$("${COMPOSE_CMD[@]}" ps -aq postgres 2>/dev/null | head -n 1 || true)"
  if [[ -n "${container_id}" ]]; then
    volume_name="$(
      docker inspect -f '{{range .Mounts}}{{if eq .Destination "/var/lib/postgresql/data"}}{{if eq .Type "volume"}}{{.Name}}{{end}}{{end}}{{end}}' "${container_id}" 2>/dev/null || true
    )"
    if [[ -n "${volume_name}" ]]; then
      printf '%s\n' "${volume_name}"
      return
    fi
  fi

  project_name="$(resolve_compose_project_name)"
  expected_volume="${project_name}_postgres_dev_data"
  if docker volume inspect "${expected_volume}" >/dev/null 2>&1; then
    printf '%s\n' "${expected_volume}"
    return
  fi

  volume_name="$(
    docker volume ls --format '{{.Name}}' \
      | awk '$0 ~ /(^|_)postgres_dev_data$/ { print; exit }' || true
  )"
  if [[ -n "${volume_name}" ]]; then
    printf '%s\n' "${volume_name}"
    return
  fi

  printf '%s\n' "${expected_volume}"
}

reset_database() {
  if [[ "${RESET_DB}" != "true" ]]; then
    return
  fi

  local volume_name
  volume_name="$(resolve_postgres_volume_name)"

  warn "开始重置本机开发数据库，将删除 Docker volume：${volume_name}"
  "${COMPOSE_CMD[@]}" stop web frontend backend postgres >/dev/null 2>&1 || true
  "${COMPOSE_CMD[@]}" rm -f -s postgres >/dev/null 2>&1 || true

  if docker volume inspect "${volume_name}" >/dev/null 2>&1; then
    docker volume rm "${volume_name}" >/dev/null || fail "删除 PostgreSQL 开发数据卷失败：${volume_name}"
    log "已删除 PostgreSQL 开发数据卷：${volume_name}"
    return
  fi

  warn "未找到 PostgreSQL 开发数据卷：${volume_name}，将按空库继续启动。"
}

wait_for_service() {
  local service="$1"
  local label="$2"
  local attempt container_id status health_status

  log "等待 ${label} 就绪"

  for ((attempt = 1; attempt <= READY_TIMEOUT; attempt++)); do
    container_id="$("${COMPOSE_CMD[@]}" ps -q "${service}" 2>/dev/null || true)"
    if [[ -n "${container_id}" ]]; then
      status="$(docker inspect -f '{{.State.Status}}' "${container_id}" 2>/dev/null || true)"
      health_status="$(docker inspect -f '{{if .State.Health}}{{.State.Health.Status}}{{end}}' "${container_id}" 2>/dev/null || true)"

      if [[ -n "${health_status}" && "${health_status}" == "healthy" ]]; then
        log "${label} 已就绪"
        return
      fi

      if [[ -z "${health_status}" && "${status}" == "running" ]]; then
        log "${label} 已启动"
        return
      fi

      if [[ "${status}" == "exited" || "${status}" == "dead" ]]; then
        break
      fi
    fi

    sleep 1
  done

  print_diagnostics
  fail "${label} 启动超时，请根据上面的日志排查。"
}

start_dev_stack() {
  log "启动 Mac 本机 Docker 开发环境"
  if "${COMPOSE_CMD[@]}" up -d --build; then
    wait_for_service postgres "PostgreSQL"
    wait_for_service minio "MinIO"
    wait_for_service backend "后端"
    wait_for_service frontend "前端"
    wait_for_service web "Web 入口"
    return
  fi

  print_diagnostics
  fail "Mac 本机 Docker 开发环境启动失败，请根据上面的日志排查。"
}

run_database_migrations() {
  log "执行数据库迁移"
  if "${COMPOSE_CMD[@]}" exec -T backend npm run db:migrate; then
    log "数据库迁移完成"
    return
  fi

  print_diagnostics
  fail "数据库迁移失败，请根据上面的日志排查。"
}

wait_for_health() {
  log "等待开发入口健康检查：${HEALTH_URL}"

  for ((attempt = 1; attempt <= READY_TIMEOUT; attempt++)); do
    if curl -fsS "${HEALTH_URL}" >/dev/null 2>&1; then
      log "开发环境已就绪"
      return
    fi
    sleep 1
  done

  print_diagnostics
  fail "开发环境已启动但健康检查未通过，请根据日志排查。"
}

print_applied_migrations() {
  log "已应用数据库迁移"

  local sql migrations
  sql='SELECT COALESCE(filename, name) FROM schema_migrations ORDER BY COALESCE(filename, name);'

  if migrations="$("${COMPOSE_CMD[@]}" exec -T postgres sh -lc 'psql -U "$POSTGRES_USER" -d "$POSTGRES_DB" -Atc "$1"' sh "${sql}" 2>&1)"; then
    printf '%s\n' "${migrations:-"(无迁移记录)"}"
    return
  fi

  warn "读取迁移记录失败："
  printf '%s\n' "${migrations}"
}

print_diagnostics() {
  warn "当前 dev 容器状态："
  "${COMPOSE_CMD[@]}" ps || true

  warn "后端最近日志："
  local backend_logs
  backend_logs="$("${COMPOSE_CMD[@]}" logs --tail=120 backend 2>&1 || true)"
  printf '%s\n' "${backend_logs}"

  if printf '%s\n' "${backend_logs}" | grep -q 'password authentication failed for user "postgres"'; then
    warn "检测到 PostgreSQL 密码认证失败。通常是 postgres_dev_data 已用旧密码初始化，而 .env 改成了新密码。"
    warn "无本地数据保留需求时可执行：RESET_DB_CONFIRM=1 bash deploy/mac-dev.sh --reset-db"
  fi

  warn "前端最近日志："
  "${COMPOSE_CMD[@]}" logs --tail=80 frontend || true

  warn "MinIO 最近日志："
  "${COMPOSE_CMD[@]}" logs --tail=80 minio || true

  warn "后端 ready 接口："
  "${COMPOSE_CMD[@]}" exec -T backend sh -lc 'curl -fsS http://127.0.0.1:3000/health/ready || true' || true

  print_applied_migrations || true
}

print_endpoints() {
  local minio_console_port
  minio_console_port="$(read_effective_env_value MINIO_CONSOLE_PORT 19001)"

  log "常用入口"
  printf '%s\n' "  前端、后台、领取页：http://localhost"
  printf '%s\n' "  后端健康检查：http://localhost/health"
  printf '%s\n' "  后端 ready 检查：http://localhost/health/ready"
  printf '%s\n' "  MinIO 控制台：http://localhost:${minio_console_port}"
}

main() {
  parse_args "$@"

  cd "${PROJECT_ROOT}"

  ensure_docker
  ensure_env_file
  validate_env_file
  confirm_reset_database
  ensure_data_dir
  check_compose_config
  reset_database
  start_dev_stack
  run_database_migrations
  wait_for_health
  print_applied_migrations

  log "Mac 本机开发环境启动完成"
  "${COMPOSE_CMD[@]}" ps
  print_endpoints
}

main "$@"
