#!/bin/bash

################################################################################
# FF AI Workspace 统一部署脚本
################################################################################
#
# 用法:
#   bash scripts/deploy/deploy.sh [环境] [命令] [选项]
#
# 环境:
#   test         - 测试环境 (端口: 50XX, 43.153.69.73 上 develop 分支自动部署的 L2 站点)
#   uat          - UAT 用户验收环境 (端口: 70XX, staging 分支)
#   production   - 生产环境 (端口: 60XX, production 分支)
#
#
# 示例:
#   bash scripts/deploy/deploy.sh uat up
#   bash scripts/deploy/deploy.sh production deploy
#   DEPLOY_ENV=uat bash scripts/deploy/deploy.sh build
#
################################################################################

# 检查 Bash 版本（需要 Bash 4.0+ 支持关联数组）
if [ -z "${BASH_VERSINFO}" ] || [ "${BASH_VERSINFO[0]}" -lt 4 ]; then
    echo "❌ 错误: 此脚本需要 Bash 4.0 或更高版本"
    echo "当前版本: ${BASH_VERSION:-未知}"
    echo ""
    echo "📍 macOS 默认使用旧版 Bash 3.2"
    echo ""
    echo "🔧 解决方案："
    echo "  1. 安装新版 Bash:"
    echo "     brew install bash"
    echo ""
    echo "  2. 使用新版 Bash 运行此脚本:"
    echo "     /opt/homebrew/bin/bash scripts/deploy/deploy.sh [环境] [命令]"
    echo ""
    echo "  3. 或者使用开发专用脚本（不需要 Bash 4）:"
    echo "     bash scripts/dev/dev.sh [命令]"
    echo ""
    exit 1
fi

# 检查 Node 版本（必须 ≥ backend/package.json engines.node 要求）
# #249【7】部署前 fail-fast，拦元根因 1+3（契约面 + 环境一致性）
# 详见 docs/standards/12-five-meta-rules.md 规则 3
verify_node_version() {
    if ! command -v node >/dev/null 2>&1; then
        echo "❌ 错误: node 命令未找到"
        echo "   请先安装 Node.js（推荐 nvm install --lts）"
        exit 1
    fi

    local pkg_json="$(dirname "${BASH_SOURCE[0]}")/../../backend/package.json"
    if [ ! -f "$pkg_json" ]; then
        echo "⚠️  警告: 找不到 backend/package.json，跳过 Node 版本检查"
        return 0
    fi

    # 解析 engines.node 第一个数字（兼容 ">=22" / ">=22.0.0" / "^22.0.0" / 多行 engines 块）
    # 用 node 自己 parse JSON 避免 grep -A 2 这种依赖行序的脆弱解析
    local required_major
    required_major=$(node -e "
        try {
            const e = JSON.parse(require('fs').readFileSync('$pkg_json', 'utf8')).engines || {};
            const m = (e.node || '').match(/[0-9]+/);
            process.stdout.write(m ? m[0] : '');
        } catch (_) { process.stdout.write(''); }
    " 2>/dev/null)
    if [ -z "$required_major" ]; then
        echo "⚠️  警告: 无法从 backend/package.json 解析 engines.node，跳过版本检查"
        return 0
    fi

    local current_major
    current_major=$(node -v | grep -oE '[0-9]+' | head -1)

    if [ "$current_major" -lt "$required_major" ]; then
        echo "❌ 错误: Node 版本不满足要求"
        echo "   当前: $(node -v) (major=$current_major)"
        echo "   要求: >=$required_major (来自 backend/package.json engines.node)"
        echo ""
        echo "🔧 解决方案:"
        echo "   nvm install $required_major && nvm use $required_major"
        echo ""
        exit 1
    fi
}

# Help / version 等纯查询命令不需要 Node，跳过检查
case " $* " in
    *" help "*|*" --help "*|*" -h "*) ;;
    *) verify_node_version ;;
esac

set -e

# CI 模式检测：设置 CI=true 可跳过所有交互提示
# 用法：CI=true bash scripts/deploy/deploy.sh uat deploy
CI_MODE="${CI:-false}"

# 交互确认函数：CI 模式下自动返回指定默认值
# 用法：confirm "提示信息" [default_in_ci: y/n]
# 返回：0=yes, 1=no
confirm() {
    local prompt="$1"
    local ci_default="${2:-n}"  # CI 模式下默认为 no

    if [ "$CI_MODE" = "true" ]; then
        if [ "$ci_default" = "y" ]; then
            log_info "[CI] 自动确认: $prompt → Yes"
            return 0
        else
            log_info "[CI] 自动拒绝: $prompt → No"
            return 1
        fi
    fi

    read -p "$prompt" -n 1 -r
    echo
    [[ $REPLY =~ ^[Yy]$ ]]
}

# 颜色定义
RED='\033[0;31m'
GREEN='\033[0;32m'
YELLOW='\033[1;33m'
BLUE='\033[0;34m'
PURPLE='\033[0;35m'
CYAN='\033[0;36m'
NC='\033[0m'

# 日志函数
log_info() { echo -e "${GREEN}[INFO]${NC} $1"; }
log_warn() { echo -e "${YELLOW}[WARN]${NC} $1"; }
log_error() { echo -e "${RED}[ERROR]${NC} $1"; }
log_step() { echo -e "${BLUE}[STEP]${NC} $1"; }
log_success() { echo -e "${GREEN}✅${NC} $1"; }

# 获取脚本所在目录和项目根目录
SCRIPT_DIR=$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)
PROJECT_ROOT=$(cd "$SCRIPT_DIR/../.." && pwd)
DOCKER_DIR="$PROJECT_ROOT/docker"

# shellcheck source=../lib/pg-bootstrap.sh
source "$PROJECT_ROOT/scripts/lib/pg-bootstrap.sh"

# ============================================================================
# 环境配置字典
# ============================================================================
declare -A ENV_CONFIG=(
    # UAT 环境配置
    [uat_frontend_port]=7000
    [uat_backend_port]=7001
    [uat_postgres_port]=7002
    [uat_redis_port]=7003
    [uat_temporal_grpc]=7033
    [uat_temporal_http]=7034
    [uat_temporal_ui]=7080
    [uat_minio_api]=7090
    [uat_minio_console]=7091
    [uat_redis_commander]=7081
    [uat_compose_file]="docker-compose.yml"
    [uat_env_file]=".env.uat"
    [uat_container_prefix]="ffws-uat"
    [uat_project_name]="ffws-uat"
    [uat_pm2_backend]="ffws-uat-backend"
    [uat_pm2_frontend]="ffws-uat-frontend"
    [uat_pm2_instances]=1
    [uat_pm2_mode]="fork"
    [uat_enable_auto_backup]="true"
    [uat_log_max_size]="10m"
    [uat_log_max_file]="3"
    
    # 生产环境配置
    [production_frontend_port]=6064  # 避开 X11 保留端口 (6000-6063)
    [production_backend_port]=6001
    [production_postgres_port]=6002
    [production_redis_port]=6003
    [production_temporal_grpc]=6033
    [production_temporal_http]=6034
    [production_temporal_ui]=6080
    [production_minio_api]=6090
    [production_minio_console]=6091
    [production_prometheus]=6009
    [production_grafana]=6012
    [production_compose_file]="docker-compose.yml"
    [production_env_file]=".env.pro"
    [production_container_prefix]="ffws"
    [production_project_name]="ffws"
    [production_pm2_backend]="ffws-backend"
    [production_pm2_frontend]="ffws-frontend"
    [production_pm2_instances]=2
    [production_pm2_mode]="cluster"
    [production_enable_auto_backup]="true"
    [production_log_max_size]="50m"
    [production_log_max_file]="5"
    [production_temporal_image]="temporalio/auto-setup:1.26.1"
    [production_temporal_ui_image]="temporalio/ui:2.34.2"
    [production_minio_image]="minio/minio:RELEASE.2025-01-08T05-46-28Z"
    
    # 测试环境配置（43.153.69.73 上 develop 分支自动部署的 L2 站点；与 UAT 共用机器，
    # 端口避开 UAT 7xxx 段 + grafana 3000 + cadvisor 8080）
    [test_frontend_port]=5000
    [test_backend_port]=5001
    [test_postgres_port]=5002
    [test_redis_port]=5003
    [test_temporal_grpc]=5033
    [test_temporal_http]=5034
    [test_temporal_ui]=5080
    [test_minio_api]=5090
    [test_minio_console]=5091
    [test_redis_commander]=5081
    [test_compose_file]="docker-compose.yml"
    [test_env_file]=".env.test"
    [test_container_prefix]="ffws-test"
    [test_project_name]="ffws-test"
    [test_pm2_backend]="ffws-test-backend"
    [test_pm2_frontend]="ffws-test-frontend"
    [test_pm2_instances]=1
    [test_pm2_mode]="fork"
    [test_enable_auto_backup]="false"
    [test_log_max_size]="5m"
    [test_log_max_file]="2"
)

# ============================================================================
# 加载环境配置
# ============================================================================
load_env_config() {
    local env=$1

    # 验证环境是否存在
    if [ -z "${ENV_CONFIG[${env}_frontend_port]}" ]; then
        log_error "未知环境: $env"
        log_info "支持的环境: test, uat, production"
        exit 1
    fi
    
    # 设置全局变量
    DEPLOY_ENV=$env
    FRONTEND_PORT=${ENV_CONFIG[${env}_frontend_port]}
    BACKEND_PORT=${ENV_CONFIG[${env}_backend_port]}
    POSTGRES_PORT=${ENV_CONFIG[${env}_postgres_port]}
    REDIS_PORT=${ENV_CONFIG[${env}_redis_port]}
    COMPOSE_FILE="${DOCKER_DIR}/${ENV_CONFIG[${env}_compose_file]}"
    ENV_FILE="${PROJECT_ROOT}/${ENV_CONFIG[${env}_env_file]}"
    CONTAINER_PREFIX=${ENV_CONFIG[${env}_container_prefix]}
    PROJECT_NAME=${ENV_CONFIG[${env}_project_name]}
    PM2_BACKEND_NAME=${ENV_CONFIG[${env}_pm2_backend]}
    PM2_FRONTEND_NAME=${ENV_CONFIG[${env}_pm2_frontend]}
    PM2_WORKER_NAME="${ENV_CONFIG[${env}_pm2_backend]}-temporal-worker"
    PM2_INSTANCES=${ENV_CONFIG[${env}_pm2_instances]}
    PM2_EXEC_MODE=${ENV_CONFIG[${env}_pm2_mode]}
    ENABLE_AUTO_BACKUP=${ENV_CONFIG[${env}_enable_auto_backup]}
    
    # Docker 日志配置
    LOG_MAX_SIZE=${ENV_CONFIG[${env}_log_max_size]:-"10m"}
    LOG_MAX_FILE=${ENV_CONFIG[${env}_log_max_file]:-"3"}
    
    # 可选服务端口
    TEMPORAL_GRPC_PORT=${ENV_CONFIG[${env}_temporal_grpc]}
    TEMPORAL_HTTP_PORT=${ENV_CONFIG[${env}_temporal_http]}
    TEMPORAL_UI_PORT=${ENV_CONFIG[${env}_temporal_ui]}
    MINIO_API_PORT=${ENV_CONFIG[${env}_minio_api]}
    MINIO_CONSOLE_PORT=${ENV_CONFIG[${env}_minio_console]}
    PROMETHEUS_PORT=${ENV_CONFIG[${env}_prometheus]}
    GRAFANA_PORT=${ENV_CONFIG[${env}_grafana]}
    REDIS_COMMANDER_PORT=${ENV_CONFIG[${env}_redis_commander]}
    
    # 镜像版本
    TEMPORAL_IMAGE=${ENV_CONFIG[${env}_temporal_image]:-"temporalio/auto-setup:1.26.1"}
    TEMPORAL_UI_IMAGE=${ENV_CONFIG[${env}_temporal_ui_image]:-"temporalio/ui:2.34.2"}
    MINIO_IMAGE=${ENV_CONFIG[${env}_minio_image]:-"minio/minio:RELEASE.2025-01-08T05-46-28Z"}
    
    # 设置环境特定的目录
    BACKUP_DIR="$PROJECT_ROOT/backups/${env}"
    LOGS_DIR="$PROJECT_ROOT/logs/${env}"
    PM2_CONFIG="$PROJECT_ROOT/ecosystem.${env}.config.js"
    
    # 创建必要的目录
    mkdir -p "$BACKUP_DIR"
    mkdir -p "$LOGS_DIR"
    
    # 显示环境信息
    log_info "🌍 环境: ${CYAN}${env}${NC}"
    log_info "📁 项目: $PROJECT_ROOT"
    log_info "🔧 配置: $ENV_FILE"
    log_info "🐳 Docker Compose: $(basename $COMPOSE_FILE)"
}

# ============================================================================
# 检查环境变量文件
# ============================================================================
check_env_file() {
    # 检查主环境文件
    if [ ! -f "$ENV_FILE" ]; then
        # 尝试回退到通用 .env
        local fallback_env="$DOCKER_DIR/.env"
        if [ -f "$fallback_env" ]; then
            log_warn "环境文件不存在: $(basename $ENV_FILE)"
            log_info "使用通用环境文件: .env"
            ENV_FILE="$fallback_env"
        else
            log_error "环境文件不存在: $ENV_FILE"
            log_info "请先创建环境配置文件"
            exit 1
        fi
    fi
    
    log_success "环境文件: $(basename $ENV_FILE)"
}

# ============================================================================
# 加载环境变量（保持 DEPLOY_ENV 不被 .env 覆盖）
# ============================================================================
source_env_file() {
    local current_deploy_env="$DEPLOY_ENV"
    # shellcheck disable=SC1090
    source "$ENV_FILE"
    DEPLOY_ENV="$current_deploy_env"
    export ENV_FILE_PATH="$ENV_FILE"
    export NEXT_PUBLIC_DEPLOY_ENV="$DEPLOY_ENV"
    # export 所有 NEXT_PUBLIC_* 变量（Next.js 构建时需要从环境读取）
    eval "$(grep '^NEXT_PUBLIC_' "$ENV_FILE" | sed 's/^/export /')"
}

# ============================================================================
# 检查是否启用 Knowledge (RAGFlow) Profile
# 返回:
#   0 - 启用 knowledge profile
#   1 - 跳过 knowledge profile
# ============================================================================
should_enable_knowledge_profile() {
    if [ -z "${RAGFLOW_API_KEY}" ]; then
        log_warn "检测到 RAGFLOW_API_KEY 未配置，自动跳过 Knowledge 容器启动"
        return 1
    fi

    return 0
}

# ============================================================================
# 完整的环境检查和初始化（包括软链接）
# ============================================================================
check_and_setup_environment() {
    log_step "检查和初始化部署环境..."
    echo ""
    
    # 1. 检查主环境文件
    log_info "[1/4] 检查环境配置文件..."
    if [ ! -f "$ENV_FILE" ]; then
        log_error "环境文件不存在: $ENV_FILE"
        log_info "请先创建环境配置文件："
        log_info "  cp docs/setup/env.${DEPLOY_ENV}.example $ENV_FILE"
        log_info "  然后编辑配置文件"
        exit 1
    fi
    log_success "✓ 环境文件存在: $(basename $ENV_FILE)"
    
    # 2. 检查并创建 backend/.env 软链接
    log_info "[2/4] 检查 backend/.env 软链接..."
    local expected_backend_link="../$(basename $ENV_FILE)"
    
    if [ ! -e "$PROJECT_ROOT/backend/.env" ]; then
        # 不存在，创建软链接
        log_info "创建 backend/.env 软链接..."
        cd "$PROJECT_ROOT/backend"
        ln -sf "$expected_backend_link" .env
        cd "$PROJECT_ROOT"
        log_success "✓ 已创建 backend/.env -> $expected_backend_link"
    elif [ ! -L "$PROJECT_ROOT/backend/.env" ]; then
        # 存在但不是软链接
        log_warn "⚠️  backend/.env 存在但不是软链接"
        log_info "建议将其改为软链接以统一管理"
    else
        # 是软链接，检查目标是否正确
        local link_target=$(readlink "$PROJECT_ROOT/backend/.env")
        if [ "$link_target" != "$expected_backend_link" ]; then
            log_warn "⚠️  backend/.env 软链接指向错误: $link_target"
            log_info "重新创建软链接指向: $expected_backend_link"
            cd "$PROJECT_ROOT/backend"
            ln -sf "$expected_backend_link" .env
            cd "$PROJECT_ROOT"
            log_success "✓ 已更新 backend/.env -> $expected_backend_link"
        else
            log_success "✓ backend/.env 软链接正确 -> $link_target"
        fi
    fi
    
    # 3. 检查并创建 frontend/.env 软链接
    log_info "[3/4] 检查 frontend/.env 软链接..."
    local expected_frontend_link="../$(basename $ENV_FILE)"
    
    if [ ! -e "$PROJECT_ROOT/frontend/.env" ]; then
        # 不存在，创建软链接
        log_info "创建 frontend/.env 软链接..."
        cd "$PROJECT_ROOT/frontend"
        ln -sf "$expected_frontend_link" .env
        cd "$PROJECT_ROOT"
        log_success "✓ 已创建 frontend/.env -> $expected_frontend_link"
    elif [ ! -L "$PROJECT_ROOT/frontend/.env" ]; then
        # 存在但不是软链接
        log_warn "⚠️  frontend/.env 存在但不是软链接"
        log_info "建议将其改为软链接以统一管理"
    else
        # 是软链接，检查目标是否正确
        local link_target=$(readlink "$PROJECT_ROOT/frontend/.env")
        if [ "$link_target" != "$expected_frontend_link" ]; then
            log_warn "⚠️  frontend/.env 软链接指向错误: $link_target"
            log_info "重新创建软链接指向: $expected_frontend_link"
            cd "$PROJECT_ROOT/frontend"
            ln -sf "$expected_frontend_link" .env
            cd "$PROJECT_ROOT"
            log_success "✓ 已更新 frontend/.env -> $expected_frontend_link"
        else
            log_success "✓ frontend/.env 软链接正确 -> $link_target"
        fi
    fi
    
    # 4. 检查关键环境变量
    log_info "[4/4] 检查关键环境变量..."
    source_env_file
    
    local missing_vars=()
    local invalid_vars=()
    
    # 检查必需的环境变量
    [ -z "$CONTAINER_PREFIX" ] && missing_vars+=("CONTAINER_PREFIX")
    [ -z "$POSTGRES_DB" ] && missing_vars+=("POSTGRES_DB")
    [ -z "$POSTGRES_USER" ] && missing_vars+=("POSTGRES_USER")
    [ -z "$POSTGRES_PASSWORD" ] && missing_vars+=("POSTGRES_PASSWORD")
    [ -z "$REDIS_PASSWORD" ] && missing_vars+=("REDIS_PASSWORD")
    [ -z "$JWT_SECRET" ] && missing_vars+=("JWT_SECRET")
    [ -z "$DATABASE_URL" ] && missing_vars+=("DATABASE_URL")
    
    # 检查是否包含占位符
    [[ "$POSTGRES_PASSWORD" == *"CHANGE_ME"* ]] && invalid_vars+=("POSTGRES_PASSWORD")
    [[ "$REDIS_PASSWORD" == *"CHANGE_ME"* ]] && invalid_vars+=("REDIS_PASSWORD")
    [[ "$JWT_SECRET" == *"CHANGE_ME"* ]] && invalid_vars+=("JWT_SECRET")
    
    if [ ${#missing_vars[@]} -gt 0 ]; then
        log_error "缺少必需的环境变量: ${missing_vars[*]}"
        log_info "请检查 $ENV_FILE 文件"
        exit 1
    fi
    
    if [ ${#invalid_vars[@]} -gt 0 ]; then
        log_error "以下环境变量仍包含占位符 CHANGE_ME: ${invalid_vars[*]}"
        log_info "请修改 $ENV_FILE 文件，设置实际的值"
        exit 1
    fi
    
    log_success "✓ 关键环境变量配置正确"
    echo ""
    log_success "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
    log_success "  ✅ 环境检查通过！"
    log_success "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
    echo ""
}

# ============================================================================
# 检查并释放占用的端口
# ============================================================================
kill_port() {
    local port=$1
    local desc=$2
    
    if command -v lsof &> /dev/null; then
        local pid=$(lsof -ti:$port 2>/dev/null)
        if [ -n "$pid" ]; then
            log_warn "端口 $port 被占用 ($desc)，正在释放..."
            kill -9 $pid 2>/dev/null || true
            sleep 1
        fi
    fi
}

# ============================================================================
# 创建环境变量软链接（用于 build 命令）
# ============================================================================
create_env_symlinks() {
    log_step "创建环境变量软链接..."
    
    local env_file_to_use="$ENV_FILE"
    local env_basename=$(basename "$env_file_to_use")
    
    # 如果 ENV_FILE 不存在，尝试回退到 .env
    if [ ! -f "$env_file_to_use" ]; then
        if [ -f "$PROJECT_ROOT/.env" ]; then
            env_file_to_use="$PROJECT_ROOT/.env"
            env_basename=".env"
            log_info "使用默认环境文件: .env"
        else
            log_warn "环境变量文件不存在，跳过软链接创建"
            return
        fi
    fi
    
    # 创建 backend/.env 软链接
    local expected_backend_link="../$env_basename"
    if [ ! -L "$PROJECT_ROOT/backend/.env" ]; then
        log_info "创建 backend/.env 软链接 -> $env_basename"
        cd "$PROJECT_ROOT/backend"
        ln -sf "$expected_backend_link" .env
        cd "$PROJECT_ROOT"
    else
        # 检查是否指向正确的文件
        local current_link=$(readlink "$PROJECT_ROOT/backend/.env")
        if [ "$current_link" != "$expected_backend_link" ]; then
            log_info "更新 backend/.env 软链接 -> $env_basename"
            cd "$PROJECT_ROOT/backend"
            ln -sf "$expected_backend_link" .env
            cd "$PROJECT_ROOT"
        fi
    fi
    
    # 创建 frontend/.env 软链接
    local expected_frontend_link="../$env_basename"
    if [ ! -L "$PROJECT_ROOT/frontend/.env" ]; then
        log_info "创建 frontend/.env 软链接 -> $env_basename"
        cd "$PROJECT_ROOT/frontend"
        ln -sf "$expected_frontend_link" .env
        cd "$PROJECT_ROOT"
    else
        # 检查是否指向正确的文件
        local current_link=$(readlink "$PROJECT_ROOT/frontend/.env")
        if [ "$current_link" != "$expected_frontend_link" ]; then
            log_info "更新 frontend/.env 软链接 -> $env_basename"
            cd "$PROJECT_ROOT/frontend"
            ln -sf "$expected_frontend_link" .env
            cd "$PROJECT_ROOT"
        fi
    fi
    
    log_success "环境变量软链接已配置 -> $env_basename"
}

# ============================================================================
# 安装后端依赖
# ============================================================================
install_backend_dependencies() {
    log_step "安装后端依赖..."
    
    # 保存当前目录
    local original_dir=$(pwd)
    
    cd "$PROJECT_ROOT/backend"
    
    # 检查 package.json 是否存在
    if [ ! -f "package.json" ]; then
        log_error "package.json 不存在"
        cd "$original_dir"
        return 1
    fi
    
    # 安装依赖（包含开发依赖，构建需要）
    npm ci || {
        log_error "后端依赖安装失败"
        cd "$original_dir"
        return 1
    }
    
    log_success "后端依赖安装完成"
    cd "$original_dir"
}

# ============================================================================
# 安装前端依赖
# ============================================================================
install_frontend_dependencies() {
    log_step "安装前端依赖..."
    
    # 保存当前目录
    local original_dir=$(pwd)
    
    cd "$PROJECT_ROOT/frontend"
    
    # 检查 package.json 是否存在
    if [ ! -f "package.json" ]; then
        log_error "package.json 不存在"
        cd "$original_dir"
        return 1
    fi
    
    # 安装依赖（包含开发依赖，构建需要）
    npm ci || {
        log_error "前端依赖安装失败"
        cd "$original_dir"
        return 1
    }
    
    log_success "前端依赖安装完成"
    cd "$original_dir"
}

# ============================================================================
# 生成 Prisma Client
# ============================================================================
generate_prisma_client() {
    log_step "生成 Prisma Client..."
    
    # 保存当前目录
    local original_dir=$(pwd)
    
    cd "$PROJECT_ROOT/backend"
    
    # 确保环境变量已加载
    if [ -f ".env" ] || [ -L ".env" ]; then
        npm run prisma:generate || {
            log_error "Prisma Client 生成失败"
            log_info "请检查 .env 文件中的 DATABASE_URL"
            cd "$original_dir"
            return 1
        }
        log_success "Prisma Client 生成完成"
    else
        log_warn ".env 文件不存在，跳过 Prisma Client 生成"
    fi
    
    # 恢复原来的目录
    cd "$original_dir"
}

# ============================================================================
# 检查并创建迁移文件结构
# ============================================================================
check_migration_files() {
    log_step "检查迁移文件..."
    
    # 保存当前目录
    local original_dir=$(pwd)
    
    cd "$PROJECT_ROOT/backend"
    
    # 检查迁移目录
    if [ ! -d "prisma/migrations" ]; then
        log_warn "迁移目录不存在，创建目录..."
        mkdir -p prisma/migrations
    fi
    
    # 检查 migration_lock.toml
    if [ ! -f "prisma/migrations/migration_lock.toml" ]; then
        log_warn "迁移锁文件不存在，创建默认文件..."
        cat > prisma/migrations/migration_lock.toml << 'EOF'
# Please do not edit this file manually
# It should be added in your version-control system (i.e. Git)
provider = "postgresql"
EOF
        log_info "已创建 migration_lock.toml"
    fi
    
    # 统计迁移文件数量
    local migration_count=0
    if [ -d "prisma/migrations" ]; then
        migration_count=$(find prisma/migrations -name "migration.sql" -type f 2>/dev/null | wc -l | tr -d ' ')
    fi
    
    if [ "$migration_count" -eq 0 ]; then
        log_warn "未找到迁移文件"
        log_info "如果这是首次部署，数据库表可能尚未创建"
        log_info "请确认："
        log_info "  1. 开发环境中已生成迁移: npm run prisma:migrate"
        log_info "  2. 迁移文件已提交到 Git"
        log_info "  3. 迁移目录结构: prisma/migrations/XXXXXX_name/migration.sql"
        echo ""
        if ! confirm "是否继续（将跳过迁移）? [y/N] " "y"; then
            log_info "取消操作"
            cd "$original_dir"
            return 1
        fi
        log_warn "继续执行，但将跳过数据库迁移..."
    else
        log_success "找到 $migration_count 个迁移文件"
    fi
    
    # 恢复原来的目录
    cd "$original_dir"
    return 0
}

# ============================================================================
# 验证构建产物
# ============================================================================
verify_build_output() {
    local build_type=$1  # "backend" or "frontend"
    
    if [ "$build_type" = "backend" ]; then
        log_step "验证后端构建产物..."
        
        if [ -f "$PROJECT_ROOT/backend/dist/main.js" ]; then
            log_success "后端构建产物验证通过: dist/main.js"
            return 0
        elif [ -f "$PROJECT_ROOT/backend/dist/src/main.js" ]; then
            log_warn "检测到旧的构建输出路径: dist/src/main.js"
            log_success "后端构建产物验证通过: dist/src/main.js"
            log_warn "⚠️  建议修复 tsconfig.json 添加 'rootDir': './src'"
            return 0
        else
            log_error "后端构建产物不存在"
            log_error "预期位置: backend/dist/main.js 或 backend/dist/src/main.js"
            return 1
        fi
    elif [ "$build_type" = "frontend" ]; then
        log_step "验证前端构建产物..."
        
        if [ -d "$PROJECT_ROOT/frontend/.next" ]; then
            log_success "前端构建产物验证通过: .next/"
            return 0
        else
            log_error "前端构建产物不存在: frontend/.next"
            return 1
        fi
    fi
}

# ============================================================================
# 检查必要的工具
# ============================================================================
check_requirements() {
    local missing_tools=()
    
    command -v docker &> /dev/null || missing_tools+=("docker")
    command -v node &> /dev/null || missing_tools+=("node")
    command -v npm &> /dev/null || missing_tools+=("npm")
    command -v pm2 &> /dev/null || missing_tools+=("pm2")
    
    if [ ${#missing_tools[@]} -ne 0 ]; then
        log_error "缺少必要工具: ${missing_tools[*]}"
        exit 1
    fi
}

# ============================================================================
# 数据库备份（根据环境配置决定是否自动备份）
# ============================================================================
cmd_db_backup() {
    log_step "备份数据库..."
    
    # 加载数据库配置
    source_env_file
    
    local timestamp=$(date +%Y%m%d_%H%M%S)
    local backup_file="$BACKUP_DIR/backup_${timestamp}.sql.gz"
    
    if docker exec ${CONTAINER_PREFIX}-postgres pg_dump \
        -U "${POSTGRES_USER}" "${POSTGRES_DB}" | gzip > "$backup_file"; then
        
        local backup_size=$(du -h "$backup_file" | cut -f1)
        log_success "备份完成: $(basename $backup_file) (${backup_size})"
        
        # 保留最近10个备份
        ls -1t "$BACKUP_DIR"/backup_*.sql.gz 2>/dev/null | tail -n +11 | xargs rm -f 2>/dev/null || true
    else
        log_error "备份失败"
        return 1
    fi
}

# ============================================================================
# 数据库回滚
# ============================================================================
cmd_db_rollback() {
    log_step "回滚数据库..."
    
    local latest_backup=$(ls -1t "$BACKUP_DIR"/backup_*.sql.gz 2>/dev/null | head -n 1)
    
    if [ -z "$latest_backup" ]; then
        log_error "没有找到备份文件"
        exit 1
    fi
    
    log_warn "⚠️  即将回滚到: $(basename $latest_backup)"
    if confirm "确认回滚？[y/N] " "n"; then
        source_env_file
        gunzip < "$latest_backup" | docker exec -i ${CONTAINER_PREFIX}-postgres \
            psql -U "${POSTGRES_USER}" -d "${POSTGRES_DB}"
        log_success "数据库已回滚"
    else
        log_info "取消回滚"
    fi
}

# ============================================================================
# 数据库恢复（从指定备份文件）
# ============================================================================
cmd_db_restore() {
    local backup_file=$1
    
    if [ -z "$backup_file" ]; then
        # 列出可用备份
        log_info "可用的备份文件:"
        ls -lht "$BACKUP_DIR"/*.sql.gz 2>/dev/null | head -10 || log_error "没有找到备份文件"
        echo ""
        log_error "用法: bash $0 $DEPLOY_ENV db:restore <backup_file>"
        exit 1
    fi
    
    if [ ! -f "$backup_file" ]; then
        log_error "备份文件不存在: $backup_file"
        exit 1
    fi
    
    log_warn "⚠️  恢复数据库将覆盖现有数据！"
    log_info "备份文件: $(basename $backup_file)"
    log_info "文件大小: $(du -h $backup_file | cut -f1)"
    echo ""
    if confirm "确认恢复？[y/N] " "n"; then
        source_env_file
        log_step "正在恢复数据库..."
        gunzip < "$backup_file" | docker exec -i ${CONTAINER_PREFIX}-postgres \
            psql -U "$POSTGRES_USER" -d "$POSTGRES_DB"
        log_success "数据库已恢复"
    else
        log_info "取消恢复"
    fi
}

# ============================================================================
# 拉取最新代码
# ============================================================================
pull_code() {
    log_step "拉取最新代码..."
    
    if [ ! -d "$PROJECT_ROOT/.git" ]; then
        log_warn "不是 Git 仓库，跳过代码拉取"
        return
    fi
    
    cd "$PROJECT_ROOT"
    
    # 检查未提交更改
    if ! git diff-index --quiet HEAD -- 2>/dev/null; then
        log_warn "检测到未提交的更改"
        if ! confirm "是否继续拉取代码？[y/N] " "y"; then
            log_info "跳过代码拉取"
            return
        fi
    fi
    
    local CURRENT_BRANCH=$(git rev-parse --abbrev-ref HEAD)
    log_info "当前分支: $CURRENT_BRANCH"
    
    local OLD_COMMIT=$(git rev-parse HEAD)
    # 部署服务器是 mirror 模式，硬同步远端是正确语义。
    # 不用 git pull：上游 force-push 后 git pull 会因 divergent branches 失败
    # （即使工作树等价），详见 .learnings/ERRORS/ERR-20260512-002-...。
    # 任何本地 tracked 文件改动（如 deploy 期间临时改的 frontend/tsconfig.json）
    # 都会被 reset 丢弃——这正是 mirror 部署机想要的行为。
    git fetch origin "$CURRENT_BRANCH"
    git reset --hard "origin/$CURRENT_BRANCH"
    local NEW_COMMIT=$(git rev-parse HEAD)
    
    if [ "$OLD_COMMIT" != "$NEW_COMMIT" ]; then
        log_success "代码已更新: ${OLD_COMMIT:0:8} -> ${NEW_COMMIT:0:8}"
        
        # 检查部署脚本是否更新
        if git diff --name-only $OLD_COMMIT $NEW_COMMIT | grep -q "scripts/deploy/"; then
            log_warn "⚠️  部署脚本已更新！"
            log_warn "建议重新运行以使用最新版本"
        fi
    else
        log_info "代码已是最新版本"
    fi
}

# ============================================================================
# 启动 Docker 服务
# ============================================================================
cmd_up() {
    local profile="core"  # 默认启动核心服务（不包括可选的 temporal/minio 等）
    local force=false      # 是否强制重启
    
    # 解析参数
    while [[ $# -gt 0 ]]; do
        case $1 in
            --all) profile="all"; shift ;;
            --basic) profile="basic"; shift ;;  # 仅 postgres + redis
            --temporal) profile="temporal"; shift ;;
            --storage) profile="storage"; shift ;;
            --monitoring) profile="monitoring"; shift ;;
            --knowledge) profile="knowledge"; shift ;;
            --force) force=true; shift ;;  # 强制重启
            *) shift ;;
        esac
    done
    
    log_info "🚀 启动 ${DEPLOY_ENV} 环境 Docker 服务..."
    
    # 检查关键服务是否已运行
    local postgres_running=false
    local redis_running=false
    
    if docker ps 2>/dev/null | grep -q "${CONTAINER_PREFIX}-postgres"; then
        postgres_running=true
    fi
    
    if docker ps 2>/dev/null | grep -q "${CONTAINER_PREFIX}-redis"; then
        redis_running=true
    fi
    
    # 如果核心服务已运行，给出提示
    if [ "$postgres_running" = true ] && [ "$redis_running" = true ] && [ "$force" = false ]; then
        echo ""
        log_success "✅ 核心 Docker 服务已在运行"
        log_info "PostgreSQL: ${CONTAINER_PREFIX}-postgres"
        log_info "Redis: ${CONTAINER_PREFIX}-redis"
        echo ""
        
        if ! confirm "是否重新启动/更新服务？[y/N] " "n"; then
            log_info "保持现有服务运行"
            cmd_status
            return 0
        fi
        
        log_info "重新启动服务..."
    fi
    
    # 导出环境变量供 Docker Compose 使用
    # 需要先从 .env 文件加载这些变量
    source_env_file
    
    # 验证必需的容器配置
    if [ -z "$CONTAINER_PREFIX" ]; then
        log_error "环境变量 CONTAINER_PREFIX 未设置"
        log_info "请在 $ENV_FILE 中设置 CONTAINER_PREFIX"
        exit 1
    fi
    
    # 容器配置
    export CONTAINER_PREFIX
    
    # PostgreSQL 配置
    export POSTGRES_DB
    export POSTGRES_USER
    export POSTGRES_PASSWORD
    export POSTGRES_PORT
    
    # Redis 配置
    export REDIS_PASSWORD
    export REDIS_PORT
    
    # MinIO 配置
    export MINIO_ROOT_USER
    export MINIO_ROOT_PASSWORD
    export MINIO_API_PORT
    export MINIO_CONSOLE_PORT
    
    # Grafana 配置
    export GRAFANA_USER=${GRAFANA_USER:-admin}
    export GRAFANA_PASSWORD
    export GRAFANA_PORT
    
    # Temporal 配置
    export TEMPORAL_GRPC_PORT
    export TEMPORAL_HTTP_PORT
    export TEMPORAL_UI_PORT
    
    # Prometheus 配置
    export PROMETHEUS_PORT
    
    # Redis Commander 配置
    export REDIS_COMMANDER_PORT
    
    # 日志配置
    export LOG_MAX_SIZE
    export LOG_MAX_FILE
    
    case $profile in
        all)
            # 启动所有服务（包括所有可选服务）
            if should_enable_knowledge_profile; then
                docker compose -p "$PROJECT_NAME" -f "$COMPOSE_FILE" --env-file "$ENV_FILE" \
                    --profile core --profile temporal --profile storage --profile monitoring --profile tools --profile knowledge up -d
            else
                log_info "本次 --all 将按非知识库模式启动（不包含 --profile knowledge）"
                docker compose -p "$PROJECT_NAME" -f "$COMPOSE_FILE" --env-file "$ENV_FILE" \
                    --profile core --profile temporal --profile storage --profile monitoring --profile tools up -d
            fi
            ;;
        basic)
            # 基础服务：PostgreSQL 和 Redis（核心必需）
            docker compose -p "$PROJECT_NAME" -f "$COMPOSE_FILE" --env-file "$ENV_FILE" \
                --profile core up -d
            ;;
        core)
            # 核心服务：不带可选服务的所有基础设施
            docker compose -p "$PROJECT_NAME" -f "$COMPOSE_FILE" --env-file "$ENV_FILE" \
                --profile core up -d
            ;;
        temporal)
            # Temporal 工作流引擎
            docker compose -p "$PROJECT_NAME" -f "$COMPOSE_FILE" --env-file "$ENV_FILE" \
                --profile core --profile temporal up -d
            ;;
        storage)
            # MinIO 对象存储
            docker compose -p "$PROJECT_NAME" -f "$COMPOSE_FILE" --env-file "$ENV_FILE" \
                --profile core --profile storage up -d
            ;;
        monitoring)
            # Prometheus + Grafana 监控
            docker compose -p "$PROJECT_NAME" -f "$COMPOSE_FILE" --env-file "$ENV_FILE" \
                --profile core --profile monitoring up -d
            ;;
        knowledge)
            # RAGFlow 知识库引擎
            docker compose -p "$PROJECT_NAME" -f "$COMPOSE_FILE" --env-file "$ENV_FILE" \
                --profile knowledge up -d
            ;;
    esac
    
    log_success "Docker 服务已启动"
    sleep 3

    # 基础设施 bootstrap：PG 扩展 + Temporal namespace（新机器/新容器首次部署兜底，#273）
    ensure_postgres_citext
    ensure_temporal_namespace

    cmd_status
}

# 幂等地在 PG 容器内建 citext 扩展。
# 历史：Prisma schema 用 @db.Citext，但 CREATE EXTENSION 不在 Prisma 迁移管辖范围，
# 新 PG 容器首次 db push 会撞 "type citext does not exist"。详见工单 #273 坑 1。
# 实现委托 scripts/lib/pg-bootstrap.sh（issue #312）。
ensure_postgres_citext() {
    local container="${CONTAINER_PREFIX}-postgres"
    if ! docker ps --format '{{.Names}}' 2>/dev/null | grep -Fxq "$container"; then
        return 0
    fi

    log_step "PG 扩展自检：citext"

    PG_BOOTSTRAP_LOG_PREFIX="deploy.sh" \
        wait_for_pg_container "$container" "$POSTGRES_USER" "$POSTGRES_DB" 15 || {
        log_warn "$container 未在 15s 内就绪，跳过 citext bootstrap（下次 cmd_up 会重试）"
        return 0
    }

    PG_BOOTSTRAP_LOG_PREFIX="deploy.sh" \
        ensure_pg_extensions "$container" "$POSTGRES_USER" "$POSTGRES_DB" "" citext \
        || log_warn "citext 创建失败（可能权限不足），prisma db push 撞 @db.Citext 时再排查"
}

# 幂等地在 Temporal 注册业务 namespace（${TEMPORAL_NAMESPACE}）。
# 历史：Temporal namespace 是 server admin 资源，不在 Prisma 迁移管辖；新机器 worker 启动报
# "Namespace <env> was not found" 持续 crash。详见工单 #273 评论 1。
ensure_temporal_namespace() {
    local container="${CONTAINER_PREFIX}-temporal"
    if ! docker ps --format '{{.Names}}' 2>/dev/null | grep -Fxq "$container"; then
        return 0
    fi
    if [ -z "${TEMPORAL_NAMESPACE:-}" ]; then
        log_warn "TEMPORAL_NAMESPACE 未设置，跳过 namespace 注册（请补 $ENV_FILE）"
        return 0
    fi

    log_step "Temporal namespace 自检：${TEMPORAL_NAMESPACE}"

    local network="${CONTAINER_PREFIX}-network"
    local admin_run=(docker run --rm --network "$network" temporalio/admin-tools:1.25.2 temporal)
    local retries=30
    local err

    # describe 失败有两种语义截然不同的错误：NamespaceNotFound vs 连接拒绝。
    # 旧实现 2>&1 吞掉两者后盲目 create，遇到 server 未就绪时空转 60s + 60 个 docker run。
    # 现在抓 stderr 区分：NotFound → 立即 create + 返回；连接错 → 仅此分支 retry。
    while (( retries > 0 )); do
        if err=$("${admin_run[@]}" operator namespace describe \
            --namespace "$TEMPORAL_NAMESPACE" --address "$container:7233" 2>&1 >/dev/null); then
            log_info "✅ Temporal namespace ${TEMPORAL_NAMESPACE} 已存在"
            return 0
        fi
        if [[ "$err" == *"NamespaceNotFound"* || "$err" == *"not found"* ]]; then
            if "${admin_run[@]}" operator namespace create \
                --namespace "$TEMPORAL_NAMESPACE" --address "$container:7233" >/dev/null 2>&1; then
                log_info "✅ Temporal namespace ${TEMPORAL_NAMESPACE} 已注册"
                return 0
            fi
            log_warn "Temporal namespace create 失败（权限/参数问题），手工排查"
            return 0
        fi
        sleep 2
        retries=$((retries - 1))
    done
    log_warn "Temporal server 60s 内未就绪，namespace ${TEMPORAL_NAMESPACE} 未注册，worker 启动前请手工执行：
  docker run --rm --network ${network} temporalio/admin-tools:1.25.2 \\
    temporal operator namespace create --namespace ${TEMPORAL_NAMESPACE} --address ${container}:7233"
}

# ============================================================================
# 停止 Docker 服务
# ============================================================================
cmd_down() {
    local clean_volumes=false
    
    while [[ $# -gt 0 ]]; do
        case $1 in
            --volumes|-v) clean_volumes=true; shift ;;
            *) shift ;;
        esac
    done
    
    check_env_file
    cd "$DOCKER_DIR"
    
    if [ "$clean_volumes" = true ]; then
        log_warn "停止服务并清除数据卷..."
        docker compose -p "$PROJECT_NAME" -f "$COMPOSE_FILE" --env-file "$ENV_FILE" down -v
    else
        log_step "停止服务..."
        docker compose -p "$PROJECT_NAME" -f "$COMPOSE_FILE" --env-file "$ENV_FILE" down
    fi
    
    log_success "Docker 服务已停止"
}

# ============================================================================
# 重启 Docker 服务
# ============================================================================
cmd_restart() {
    log_step "重启 ${DEPLOY_ENV} 环境 Docker 服务..."
    cmd_down "$@"
    sleep 2
    cmd_up "$@"
    log_success "Docker 服务已重启"
}

# ============================================================================
# 查看 Docker 日志
# ============================================================================
cmd_logs() {
    local service=$1
    shift || true
    
    local follow=false
    local tail_lines=100
    
    while [[ $# -gt 0 ]]; do
        case $1 in
            -f|--follow) follow=true; shift ;;
            --tail) tail_lines=$2; shift 2 ;;
            *) shift ;;
        esac
    done
    
    check_env_file
    cd "$DOCKER_DIR"
    
    if [ -z "$service" ]; then
        # 查看所有服务日志
        if [ "$follow" = true ]; then
            docker compose -p "$PROJECT_NAME" -f "$COMPOSE_FILE" --env-file "$ENV_FILE" logs -f
        else
            docker compose -p "$PROJECT_NAME" -f "$COMPOSE_FILE" --env-file "$ENV_FILE" logs --tail=$tail_lines
        fi
    else
        # 查看指定服务日志
        if [ "$follow" = true ]; then
            docker compose -p "$PROJECT_NAME" -f "$COMPOSE_FILE" --env-file "$ENV_FILE" logs -f "$service"
        else
            docker compose -p "$PROJECT_NAME" -f "$COMPOSE_FILE" --env-file "$ENV_FILE" logs --tail=$tail_lines "$service"
        fi
    fi
}

# ============================================================================
# 查看服务状态
# ============================================================================
cmd_status() {
    echo ""
    log_info "📊 ${DEPLOY_ENV} 环境服务状态"
    echo ""
    
    check_env_file
    cd "$DOCKER_DIR"
    docker compose -p "$PROJECT_NAME" -f "$COMPOSE_FILE" --env-file "$ENV_FILE" ps 2>/dev/null || true
    echo ""
}

# ============================================================================
# 数据库迁移（增强版）
# ============================================================================
cmd_db_migrate() {
    log_step "运行数据库迁移..."
    
    # 保存当前目录
    local original_dir=$(pwd)
    
    source_env_file
    export DATABASE_URL="postgresql://${POSTGRES_USER}:${POSTGRES_PASSWORD}@localhost:${POSTGRES_PORT}/${POSTGRES_DB}?schema=public"
    
    cd "$PROJECT_ROOT/backend"
    
    # 检查迁移状态（可选）
    log_info "检查数据库迁移状态..."
    if npx prisma migrate status &> /dev/null; then
        log_info "迁移状态检查完成"
    else
        log_warn "迁移状态检查失败，继续尝试部署..."
    fi
    
    # 执行迁移
    npm run prisma:migrate:deploy || {
        log_error "数据库迁移失败"
        log_info "请检查："
        log_info "  1. 数据库连接是否正常"
        log_info "  2. 迁移文件是否完整"
        log_info "  3. 手动检查: cd backend && npm run prisma:migrate:deploy"
        cd "$original_dir"
        return 1
    }
    
    log_success "数据库迁移完成"
    cd "$original_dir"
}

# ============================================================================
# 检查迁移状态
# ============================================================================
cmd_db_migrate_status() {
    log_step "检查数据库迁移状态..."
    
    # 保存当前目录
    local original_dir=$(pwd)
    
    source_env_file
    export DATABASE_URL="postgresql://${POSTGRES_USER}:${POSTGRES_PASSWORD}@localhost:${POSTGRES_PORT}/${POSTGRES_DB}?schema=public"
    
    cd "$PROJECT_ROOT/backend"
    
    echo ""
    npm run prisma:migrate:status || {
        log_warn "迁移状态检查失败"
        cd "$original_dir"
        return 1
    }
    echo ""
    
    cd "$original_dir"
}

# ============================================================================
# 检查 Schema 差异
# ============================================================================
cmd_db_migrate_diff() {
    log_step "检查 Schema 与数据库的差异..."
    
    # 保存当前目录
    local original_dir=$(pwd)
    
    source_env_file
    export DATABASE_URL="postgresql://${POSTGRES_USER}:${POSTGRES_PASSWORD}@localhost:${POSTGRES_PORT}/${POSTGRES_DB}?schema=public"
    
    cd "$PROJECT_ROOT/backend"
    
    echo ""
    log_info "生成差异 SQL..."
    npm run prisma:migrate:diff || {
        log_error "差异检查失败"
        cd "$original_dir"
        return 1
    }
    echo ""
    
    log_info "💡 提示："
    log_info "  - 如果有差异，需要创建新的迁移文件"
    log_info "  - 使用命令: bash $0 $DEPLOY_ENV db:migrate:create"
    
    cd "$original_dir"
}

# ============================================================================
# 创建新的迁移文件（仅创建，不应用）
# ============================================================================
cmd_db_migrate_create() {
    local migration_name=$1
    
    log_step "创建新的迁移文件..."
    
    # 保存当前目录
    local original_dir=$(pwd)
    
    source_env_file
    export DATABASE_URL="postgresql://${POSTGRES_USER}:${POSTGRES_PASSWORD}@localhost:${POSTGRES_PORT}/${POSTGRES_DB}?schema=public"
    
    cd "$PROJECT_ROOT/backend"
    
    if [ -z "$migration_name" ]; then
        log_warn "未指定迁移名称，使用默认名称"
        npm run prisma:migrate:create || {
            log_error "迁移创建失败"
            cd "$original_dir"
            return 1
        }
    else
        log_info "创建迁移: $migration_name"
        npx prisma migrate dev --create-only --name "$migration_name" || {
            log_error "迁移创建失败"
            cd "$original_dir"
            return 1
        }
    fi
    
    echo ""
    log_success "✅ 迁移文件已创建"
    log_info "📍 位置: backend/prisma/migrations/"
    log_info "⚠️  注意："
    log_info "  1. 请检查生成的 SQL 文件"
    log_info "  2. 提交到 Git: git add backend/prisma/migrations/"
    log_info "  3. 部署时会自动应用: bash $0 $DEPLOY_ENV deploy"
    
    cd "$original_dir"
}

# ============================================================================
# 开发环境：生成并应用迁移
# ============================================================================
cmd_db_migrate_dev() {
    local migration_name=$1
    
    log_step "开发环境：生成并应用迁移..."
    
    # 保存当前目录
    local original_dir=$(pwd)
    
    source_env_file
    export DATABASE_URL="postgresql://${POSTGRES_USER}:${POSTGRES_PASSWORD}@localhost:${POSTGRES_PORT}/${POSTGRES_DB}?schema=public"
    
    cd "$PROJECT_ROOT/backend"
    
    if [ -z "$migration_name" ]; then
        log_warn "未指定迁移名称"
        npm run prisma:migrate || {
            log_error "迁移失败"
            cd "$original_dir"
            return 1
        }
    else
        log_info "创建并应用迁移: $migration_name"
        npx prisma migrate dev --name "$migration_name" || {
            log_error "迁移失败"
            cd "$original_dir"
            return 1
        }
    fi
    
    echo ""
    log_success "✅ 迁移已创建并应用"
    log_info "📍 迁移文件: backend/prisma/migrations/"
    log_info "💡 下一步："
    log_info "  git add backend/prisma/migrations/"
    log_info "  git commit -m 'feat: add migration $migration_name'"
    
    cd "$original_dir"
}

# ============================================================================
# 推送数据库结构（无迁移记录）
# ============================================================================
cmd_db_push() {
    log_step "推送数据库结构（开发用）..."
    
    # 保存当前目录
    local original_dir=$(pwd)
    
    source_env_file
    export DATABASE_URL="postgresql://${POSTGRES_USER}:${POSTGRES_PASSWORD}@localhost:${POSTGRES_PORT}/${POSTGRES_DB}?schema=public"
    
    cd "$PROJECT_ROOT/backend"
    npm run db:push:force
    
    log_success "数据库结构已推送"
    cd "$original_dir"
}

# ============================================================================
# 运行种子数据
# ============================================================================
cmd_db_seed() {
    log_step "运行种子数据..."

    # 保存当前目录
    local original_dir=$(pwd)

    source_env_file
    export DATABASE_URL="postgresql://${POSTGRES_USER}:${POSTGRES_PASSWORD}@localhost:${POSTGRES_PORT}/${POSTGRES_DB}?schema=public"

    cd "$PROJECT_ROOT/backend"
    npm run db:seed

    log_success "种子数据已导入"
    cd "$original_dir"
}

# 细粒度 seed 子命令（生产环境按需精确运行）
_run_seed_script() {
    local script_name="$1"
    local description="$2"
    local original_dir=$(pwd)
    source_env_file
    export DATABASE_URL="postgresql://${POSTGRES_USER}:${POSTGRES_PASSWORD}@localhost:${POSTGRES_PORT}/${POSTGRES_DB}?schema=public"
    log_step "运行 ${description}..."
    cd "$PROJECT_ROOT/backend"
    npm run "${script_name}"
    log_success "${description} 完成"
    cd "$original_dir"
}

cmd_db_seed_iam()          { _run_seed_script "db:seed:iam"          "IAM 权限 & 角色"; }
cmd_db_seed_positions()    { _run_seed_script "db:seed:positions"     "岗位数据"; }
cmd_db_seed_part_groups()  { _run_seed_script "db:seed:part-groups"   "配件分组"; }
cmd_db_seed_dingtalk()     { _run_seed_script "db:seed:dingtalk"      "钉钉同步配置"; }
cmd_db_seed_sync_bot()     { _run_seed_script "db:seed:sync-bot"      "Sync Bot 服务账号"; }
cmd_db_seed_robot_fields() { _run_seed_script "db:seed:robot-fields"  "Robot Manager FieldDefs"; }

# ============================================================================
# 修复损坏的迁移（清理无效迁移目录）
# ============================================================================
cmd_db_migrate_fix() {
    log_step "修复损坏的迁移文件..."
    
    # 保存当前目录
    local original_dir=$(pwd)
    
    cd "$PROJECT_ROOT/backend/prisma/migrations"
    
    if [ ! -d "." ]; then
        log_error "迁移目录不存在"
        cd "$original_dir"
        return 1
    fi
    
    log_info "扫描迁移目录..."
    
    # 查找所有迁移目录
    local broken_dirs=()
    for dir in */; do
        if [ -d "$dir" ] && [ "$dir" != "migration_lock.toml" ]; then
            # 检查是否有 migration.sql 文件
            if [ ! -f "${dir}migration.sql" ]; then
                broken_dirs+=("$dir")
                log_warn "发现损坏的迁移目录: $dir"
            fi
        fi
    done
    
    if [ ${#broken_dirs[@]} -eq 0 ]; then
        log_success "✅ 所有迁移文件完整"
        cd "$original_dir"
        return 0
    fi
    
    echo ""
    log_warn "⚠️  发现 ${#broken_dirs[@]} 个损坏的迁移目录："
    for dir in "${broken_dirs[@]}"; do
        echo "   - $dir"
    done
    echo ""
    
    if confirm "是否删除这些损坏的迁移目录？[y/N] " "n"; then
        for dir in "${broken_dirs[@]}"; do
            log_info "删除: $dir"
            rm -rf "$dir"
        done
        log_success "✅ 已清理损坏的迁移目录"
        echo ""
        log_info "💡 下一步："
        log_info "  1. 重新生成迁移: ./scripts/deploy/deploy.sh $DEPLOY_ENV db:migrate:dev"
        log_info "  2. 或检查迁移状态: ./scripts/deploy/deploy.sh $DEPLOY_ENV db:migrate:status"
    else
        log_info "取消清理"
    fi
    
    cd "$original_dir"
}

# ============================================================================
# 重置迁移历史（危险操作，仅用于开发环境）
# ============================================================================
cmd_db_migrate_reset() {
    log_warn "⚠️  ⚠️  ⚠️  危险操作 ⚠️  ⚠️  ⚠️"
    log_warn "此操作将："
    log_warn "  1. 删除数据库中的所有数据"
    log_warn "  2. 删除所有迁移记录"
    log_warn "  3. 重新应用所有迁移"
    echo ""
    
    if [ "$DEPLOY_ENV" = "production" ]; then
        log_error "禁止在生产环境执行此操作！"
        exit 1
    fi
    
    log_info "当前环境: ${DEPLOY_ENV}"
    echo ""
    if [ "$CI_MODE" = "true" ]; then
        log_error "[CI] 禁止在 CI 模式下重置数据库"
        return 1
    fi
    read -p "确认重置数据库？输入环境名称以确认 [$DEPLOY_ENV]: " confirmation

    if [ "$confirmation" != "$DEPLOY_ENV" ]; then
        log_info "取消操作"
        return
    fi
    
    # 保存当前目录
    local original_dir=$(pwd)
    
    source_env_file
    export DATABASE_URL="postgresql://${POSTGRES_USER}:${POSTGRES_PASSWORD}@localhost:${POSTGRES_PORT}/${POSTGRES_DB}?schema=public"
    
    cd "$PROJECT_ROOT/backend"
    
    log_step "执行数据库重置..."
    npx prisma migrate reset --force || {
        log_error "重置失败"
        cd "$original_dir"
        return 1
    }
    
    log_success "✅ 数据库已重置"
    cd "$original_dir"
}

# ============================================================================
# 初始化权限
# ============================================================================
cmd_init_permissions() {
    log_step "初始化权限和角色..."
    
    # 保存当前目录
    local original_dir=$(pwd)
    
    source_env_file
    export DATABASE_URL="postgresql://${POSTGRES_USER}:${POSTGRES_PASSWORD}@localhost:${POSTGRES_PORT}/${POSTGRES_DB}?schema=public"
    
    cd "$PROJECT_ROOT/backend"
    npm run init:permissions
    
    log_success "权限初始化完成"
    cd "$original_dir"
}

# ============================================================================
# 初始化管理员
# ============================================================================
cmd_init_admin() {
    log_step "创建管理员用户..."
    
    # 保存当前目录
    local original_dir=$(pwd)
    
    source_env_file
    export DATABASE_URL="postgresql://${POSTGRES_USER}:${POSTGRES_PASSWORD}@localhost:${POSTGRES_PORT}/${POSTGRES_DB}?schema=public"
    
    cd "$PROJECT_ROOT/backend"
    npm run init:itadmin
    
    log_success "管理员创建完成"
    cd "$original_dir"
}

# ============================================================================
# 完整初始化
# ============================================================================
cmd_init_all() {
    echo ""
    log_info "🚀 ${DEPLOY_ENV} 环境完整初始化"
    echo ""
    
    cmd_init_permissions
    cmd_init_admin
    
    log_success "✅ 初始化完成！"
}

# ============================================================================
# 构建应用（完整流程）
# ============================================================================
cmd_build() {
    local build_backend=true
    local build_frontend=true
    local skip_deps=false
    local from_deploy=false  # 标记是否从 deploy/run 调用
    
    while [[ $# -gt 0 ]]; do
        case $1 in
            --backend) build_frontend=false; shift ;;
            --frontend) build_backend=false; shift ;;
            --skip-deps) skip_deps=true; shift ;;
            --from-deploy) from_deploy=true; shift ;;  # 内部标记，用户不可见
            *) shift ;;
        esac
    done
    
    echo ""
    log_info "🔨 构建应用..."
    echo ""
    
    # 如果不是从 deploy/run 调用（即独立执行 build），需要先检查和创建软链接
    if [ "$from_deploy" = false ]; then
        create_env_symlinks
        echo ""
    fi
    
    # 构建后端
    if [ "$build_backend" = true ]; then
        log_step "━━━ 构建后端 ━━━"
        echo ""
        
        cd "$PROJECT_ROOT/backend"
        
        # 安装依赖
        if [ "$skip_deps" = false ]; then
            install_backend_dependencies || return 1
            echo ""
        else
            log_info "跳过依赖安装"
        fi
        
        # 生成 Prisma Client
        generate_prisma_client || return 1
        echo ""
        
        # 检查迁移文件
        check_migration_files || {
            log_warn "迁移文件检查未通过，但继续构建..."
        }
        echo ""
        
        # 构建应用
        log_step "编译后端代码..."
        npm run build || {
            log_error "后端构建失败"
            log_info "请检查代码错误和编译日志"
            return 1
        }
        echo ""
        
        # 验证构建产物
        verify_build_output "backend" || return 1
        
        cd "$PROJECT_ROOT"
        log_success "✅ 后端构建完成"
        echo ""
    fi
    
    # 构建前端
    if [ "$build_frontend" = true ]; then
        log_step "━━━ 构建前端 ━━━"
        echo ""
        
        cd "$PROJECT_ROOT/frontend"
        
        # 安装依赖
        if [ "$skip_deps" = false ]; then
            install_frontend_dependencies || return 1
            echo ""
        else
            log_info "跳过依赖安装"
        fi
        
        # 构建到临时目录，成功后原子替换（构建期间前端始终可用）
        log_step "编译前端代码..."
        [ -d ".next.tmp" ] && rm -rf .next.tmp
        NEXT_BUILD_DIR=.next.tmp npm run build || {
            log_error "前端构建失败，旧版本保持不变"
            [ -d ".next.tmp" ] && rm -rf .next.tmp
            return 1
        }
        # 构建成功，原子替换
        [ -d ".next.bak" ] && rm -rf .next.bak
        [ -d ".next" ] && mv .next .next.bak
        mv .next.tmp .next
        [ -d ".next.bak" ] && rm -rf .next.bak
        log_success "前端构建完成，已替换旧版本"
        echo ""
        
        # 验证构建产物
        verify_build_output "frontend" || return 1
        
        cd "$PROJECT_ROOT"
        log_success "✅ 前端构建完成"
        echo ""
    fi
    
    log_success "═══════════════════════════════════════════"
    log_success "  ✅ 构建完成！"
    log_success "═══════════════════════════════════════════"
    echo ""
}

# ============================================================================
# 生成 PM2 配置
# ============================================================================
create_pm2_config() {
    log_step "生成 PM2 配置..."
    
    # 确定后端脚本路径
    local backend_script="dist/main.js"
    if [ -f "${PROJECT_ROOT}/backend/dist/src/main.js" ]; then
        backend_script="dist/src/main.js"
    elif [ ! -f "${PROJECT_ROOT}/backend/dist/main.js" ]; then
        log_error "后端构建产物不存在"
        return 1
    fi
    
    # 验证前端构建
    if [ ! -d "${PROJECT_ROOT}/frontend/.next" ]; then
        log_error "前端构建产物不存在"
        return 1
    fi
    
    cat > "$PM2_CONFIG" << EOF
module.exports = {
  apps: [
    {
      name: '${PM2_BACKEND_NAME}',
      cwd: '${PROJECT_ROOT}/backend',
      script: '${backend_script}',
      instances: ${PM2_INSTANCES},
      exec_mode: '${PM2_EXEC_MODE}',
      env: {
        NODE_ENV: 'production',
        PORT: ${BACKEND_PORT}
      },
      error_file: '${LOGS_DIR}/backend-error.log',
      out_file: '${LOGS_DIR}/backend-out.log',
      log_date_format: 'YYYY-MM-DD HH:mm:ss Z',
      merge_logs: true,
      // PM2 健康参数（#249【5】拦元根因 5：启动崩溃要被识别成"未活过"，避免 PM2 把秒崩当正常退出）
      max_memory_restart: '1G',
      min_uptime: '10s',
      listen_timeout: 30000,
      max_restarts: 10,
      exp_backoff_restart_delay: 1000,
      autorestart: true,
      watch: false
    },
    {
      name: '${PM2_FRONTEND_NAME}',
      cwd: '${PROJECT_ROOT}/frontend',
      script: 'node_modules/next/dist/bin/next',
      args: 'start -p ${FRONTEND_PORT}',
      instances: ${PM2_INSTANCES},
      exec_mode: '${PM2_EXEC_MODE}',
      env: {
        NODE_ENV: 'production',
        PORT: ${FRONTEND_PORT}
      },
      error_file: '${LOGS_DIR}/frontend-error.log',
      out_file: '${LOGS_DIR}/frontend-out.log',
      log_date_format: 'YYYY-MM-DD HH:mm:ss Z',
      merge_logs: true,
      // PM2 健康参数（同 backend，#249【5】）
      max_memory_restart: '1G',
      min_uptime: '10s',
      listen_timeout: 30000,
      max_restarts: 10,
      exp_backoff_restart_delay: 1000,
      autorestart: true,
      watch: false
    },
    {
      name: '${PM2_BACKEND_NAME}-temporal-worker',
      cwd: '${PROJECT_ROOT}/backend',
      script: 'node_modules/.bin/ts-node',
      args: '--transpile-only --project tsconfig.json -r tsconfig-paths/register src/core/workflow/temporal/worker.ts',
      instances: 1,
      exec_mode: 'fork',
      env: {
        NODE_ENV: 'production'
      },
      error_file: '${LOGS_DIR}/temporal-worker-error.log',
      out_file: '${LOGS_DIR}/temporal-worker-out.log',
      log_date_format: 'YYYY-MM-DD HH:mm:ss Z',
      merge_logs: true,
      max_memory_restart: '1G',
      min_uptime: '10s',
      listen_timeout: 30000,
      kill_timeout: 5000,
      autorestart: true,
      watch: false
    }
  ]
};
EOF
    
    log_success "PM2 配置已生成: $(basename $PM2_CONFIG)"
}

# ============================================================================
# 启动应用
# ============================================================================
cmd_start() {
    log_info "🚀 启动 ${DEPLOY_ENV} 环境应用..."
    
    check_requirements
    create_pm2_config || exit 1
    
    # 检查是否已运行
    if pm2 list | grep -qE "$PM2_BACKEND_NAME|$PM2_FRONTEND_NAME|$PM2_WORKER_NAME"; then
        log_step "重启应用..."
        pm2 reload "$PM2_CONFIG"
    else
        log_step "启动应用..."
        pm2 start "$PM2_CONFIG"
    fi
    
    pm2 save --force 2>/dev/null || true
    
    log_success "应用已启动"
    pm2 list | grep -E "Name|$PM2_BACKEND_NAME|$PM2_FRONTEND_NAME|$PM2_WORKER_NAME" || true
}

# ============================================================================
# 停止应用
# ============================================================================
cmd_stop() {
    log_info "🛑 停止 ${DEPLOY_ENV} 环境应用..."
    
    if pm2 list | grep -q "$PM2_BACKEND_NAME"; then
        pm2 stop "$PM2_BACKEND_NAME" 2>/dev/null || true
    fi
    
    if pm2 list | grep -q "$PM2_FRONTEND_NAME"; then
        pm2 stop "$PM2_FRONTEND_NAME" 2>/dev/null || true
    fi
    
    if pm2 list | grep -q "$PM2_WORKER_NAME"; then
        pm2 stop "$PM2_WORKER_NAME" 2>/dev/null || true
    fi
    
    pm2 save --force 2>/dev/null || true
    log_success "应用已停止"
}

# ============================================================================
# 删除应用（从PM2中完全移除）
# ============================================================================
cmd_pm2_delete() {
    log_info "🗑️  删除 ${DEPLOY_ENV} 环境应用（从PM2中移除）..."
    
    check_requirements
    
    # 删除应用
    pm2 delete "$PM2_BACKEND_NAME" 2>/dev/null || true
    pm2 delete "$PM2_FRONTEND_NAME" 2>/dev/null || true
    pm2 delete "$PM2_WORKER_NAME" 2>/dev/null || true
    
    # 删除配置文件
    if [ -f "$PM2_CONFIG" ]; then
        rm -f "$PM2_CONFIG"
        log_info "已删除 PM2 配置: $(basename $PM2_CONFIG)"
    fi
    
    # 保存
    pm2 save --force 2>/dev/null || true
    
    log_success "应用已从 PM2 中完全删除"
}

# ============================================================================
# 完整部署
# ============================================================================
cmd_deploy() {
    local skip_build=false
    local skip_migrate=false
    local docker_profile="all"  # 默认启动所有服务
    local start_time=$(date +%s)
    
    while [[ $# -gt 0 ]]; do
        case $1 in
            --skip-build) skip_build=true; shift ;;
            --skip-migrate) skip_migrate=true; shift ;;
            --docker-basic) docker_profile="basic"; shift ;;  # 仅基础服务
            --docker-core) docker_profile="core"; shift ;;    # 核心服务
            *) shift ;;
        esac
    done
    
    echo ""
    log_info "═══════════════════════════════════════════"
    log_info "  🚀 ${DEPLOY_ENV} 环境部署"
    log_info "═══════════════════════════════════════════"
    echo ""
    echo "⏰ 开始时间: $(date '+%Y-%m-%d %H:%M:%S')"
    echo "📦 环境: ${DEPLOY_ENV}"
    echo "🔧 配置文件: $(basename $ENV_FILE)"
    echo ""
    
    # 0. 环境检查和初始化
    echo "┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓"
    echo "┃  [0/8] 环境检查                            ┃"
    echo "┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛"
    check_and_setup_environment
    echo ""
    
    # 1. 拉取最新代码
    echo "┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓"
    echo "┃  [1/8] 更新代码                            ┃"
    echo "┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛"
    pull_code
    echo ""
    
    # 2. 启动 Docker（根据配置启动不同范围的服务）
    echo "┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓"
    echo "┃  [2/8] Docker 服务                         ┃"
    echo "┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛"
    
    # 检查 Docker 服务状态
    if docker ps 2>/dev/null | grep -q "${CONTAINER_PREFIX}-postgres"; then
        log_info "✅ Docker 服务已在运行，确保配置最新..."
    fi
    
    case $docker_profile in
        all)
            log_info "启动所有服务（包括可选服务）..."
            cmd_up --all --force
            ;;
        core)
            log_info "启动核心服务..."
            cmd_up --force
            ;;
        basic)
            log_info "启动基础服务（仅 PostgreSQL + Redis）..."
            cmd_up --basic --force
            ;;
    esac
    echo ""
    
    # 3. 自动备份（如果启用）
    echo "┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓"
    echo "┃  [3/8] 数据库备份                          ┃"
    echo "┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛"
    if [ "$ENABLE_AUTO_BACKUP" = "true" ] && [ "$skip_migrate" = false ]; then
        cmd_db_backup || log_warn "备份失败，继续部署..."
    else
        log_info "跳过自动备份"
    fi
    echo ""
    
    # 4. 构建
    echo "┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓"
    echo "┃  [4/8] 构建应用                            ┃"
    echo "┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛"
    if [ "$skip_build" = false ]; then
        cmd_build --from-deploy || {
            log_error "构建失败，终止部署"
            return 1
        }
    else
        log_info "跳过构建"
    fi
    echo ""
    
    # 5. 迁移
    echo "┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓"
    echo "┃  [5/8] 数据库迁移                          ┃"
    echo "┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛"
    if [ "$skip_migrate" = false ]; then
        cmd_db_migrate || {
            log_error "数据库迁移失败"
            log_warn "是否继续部署（不推荐）？"
            if ! confirm "继续？[y/N] " "n"; then
                log_info "终止部署"
                return 1
            fi
        }
    else
        log_info "跳过迁移"
    fi
    echo ""

    # 6. IAM 权限同步
    echo "┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓"
    echo "┃  [6/8] IAM 权限同步                        ┃"
    echo "┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛"
    cmd_db_seed_iam || {
        log_error "IAM 权限同步失败"
        log_warn "是否继续部署（不推荐）？"
        if ! confirm "继续？[y/N] " "n"; then
            log_info "终止部署"
            return 1
        fi
    }
    echo ""

    # 7. 启动应用
    echo "┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓"
    echo "┃  [7/8] 启动应用 (PM2)                      ┃"
    echo "┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛"
    cmd_start
    echo ""

    # 8. 健康检查
    echo "┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓"
    echo "┃  [8/8] 健康检查                            ┃"
    echo "┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛"
    sleep 3
    cmd_health
    
    # 显示详细摘要
    show_deployment_summary $start_time
}

# ============================================================================
# 快捷运行命令（检查Docker + 构建 + 启动）
# ============================================================================
cmd_run() {
    local skip_build=false
    local backend_only=false
    local frontend_only=false
    local build_args=""
    local start_args=""
    
    while [[ $# -gt 0 ]]; do
        case $1 in
            --skip-build) skip_build=true; shift ;;
            --backend) backend_only=true; build_args="--backend"; shift ;;
            --frontend) frontend_only=true; build_args="--frontend"; shift ;;
            *) shift ;;
        esac
    done
    
    local target_desc="应用"
    if [ "$backend_only" = true ]; then
        target_desc="后端"
    elif [ "$frontend_only" = true ]; then
        target_desc="前端"
    fi
    
    echo ""
    log_info "═══════════════════════════════════════════"
    log_info "  🚀 ${DEPLOY_ENV} ${target_desc}快速运行"
    log_info "═══════════════════════════════════════════"
    echo ""
    
    # 0. 环境检查和初始化
    log_step "[0/4] 检查运行环境..."
    check_and_setup_environment
    echo ""
    
    # 1. 检查 Docker 服务
    log_step "[1/4] 检查 Docker 服务..."
    if ! docker ps 2>/dev/null | grep -q "${CONTAINER_PREFIX}-postgres"; then
        log_warn "Docker 服务未运行，正在启动..."
        cmd_up
        sleep 3
    fi
    log_success "Docker 服务正常"
    echo ""
    
    # 2. 构建
    if [ "$skip_build" = false ]; then
        log_step "[2/4] 构建${target_desc}..."
        cmd_build --from-deploy $build_args
    else
        log_info "[2/4] 跳过构建步骤"
    fi
    echo ""
    
    # 3. 启动应用
    log_step "[3/4] 启动${target_desc}..."
    
    # 释放可能被占用的端口
    if [ "$frontend_only" != true ]; then
        kill_port $BACKEND_PORT "后端"
    fi
    if [ "$backend_only" != true ]; then
        kill_port $FRONTEND_PORT "前端"
    fi
    
    cmd_start
    
    echo ""
    log_success "═══════════════════════════════════════════"
    log_success "  ✅ ${DEPLOY_ENV} ${target_desc}已运行！"
    log_success "═══════════════════════════════════════════"
    echo ""
}

# ============================================================================
# 健康检查
# ============================================================================
cmd_health() {
    log_info "🏥 ${DEPLOY_ENV} 环境健康检查"
    
    # 检查后端（重试3次）
    log_step "检查后端..."
    local healthy=false
    for i in {1..3}; do
        if curl -sf --connect-timeout 5 http://localhost:$BACKEND_PORT/api/v1/health &> /dev/null; then
            log_success "后端健康"
            healthy=true
            break
        elif [ $i -lt 3 ]; then
            log_warn "重试 $i/3..."
            sleep 3
        fi
    done
    [ "$healthy" = false ] && log_error "后端不健康"
    
    # 检查前端
    log_step "检查前端..."
    if curl -sf http://localhost:$FRONTEND_PORT &> /dev/null; then
        log_success "前端健康"
    else
        log_error "前端不健康"
    fi
}

# ============================================================================
# 显示部署摘要
# ============================================================================
show_deployment_summary() {
    local start_time=${1:-0}
    local end_time=$(date +%s)
    local duration=$((end_time - start_time))
    local duration_min=$((duration / 60))
    local duration_sec=$((duration % 60))
    
    echo ""
    echo "╔═══════════════════════════════════════════════════════════╗"
    echo "║                                                           ║"
    echo "║           ✅ ${DEPLOY_ENV^^} 环境部署完成！                    ║"
    echo "║                                                           ║"
    echo "╚═══════════════════════════════════════════════════════════╝"
    echo ""
    
    if [ $start_time -gt 0 ]; then
        echo "⏱️  部署耗时: ${duration_min}分${duration_sec}秒"
        echo "📅 完成时间: $(date '+%Y-%m-%d %H:%M:%S')"
        echo ""
    fi
    
    echo "┌─────────────────────────────────────────────────────────┐"
    echo "│  🔗 服务地址                                            │"
    echo "└─────────────────────────────────────────────────────────┘"
    echo "   前端:        http://localhost:$FRONTEND_PORT"
    echo "   后端 API:    http://localhost:$BACKEND_PORT/api/v1"
    echo "   健康检查:    http://localhost:$BACKEND_PORT/api/v1/health"
    echo ""
    
    # 显示数据库信息
    if [ -f "$ENV_FILE" ]; then
        source_env_file || true
        if [ -n "$POSTGRES_DB" ]; then
            echo "┌─────────────────────────────────────────────────────────┐"
            echo "│  🗄️  数据库信息                                         │"
            echo "└─────────────────────────────────────────────────────────┘"
            echo "   主机:        localhost:$POSTGRES_PORT"
            echo "   数据库:      $POSTGRES_DB"
            echo "   用户名:      $POSTGRES_USER"
            echo ""
        fi
    fi
    
    # 显示 PM2 状态
    if command -v pm2 &> /dev/null; then
        echo "┌─────────────────────────────────────────────────────────┐"
        echo "│  📊 PM2 服务状态                                        │"
        echo "└─────────────────────────────────────────────────────────┘"
        pm2 list 2>/dev/null | grep -E "Name|$PM2_BACKEND_NAME|$PM2_FRONTEND_NAME|$PM2_WORKER_NAME" || echo "   无运行中的服务"
        echo ""
    fi
    
    # 显示 Docker 服务状态
    echo "┌─────────────────────────────────────────────────────────┐"
    echo "│  🐳 Docker 服务                                         │"
    echo "└─────────────────────────────────────────────────────────┘"
    if docker ps 2>/dev/null | grep -q "${CONTAINER_PREFIX}-postgres"; then
        echo "   ✅ PostgreSQL - localhost:$POSTGRES_PORT"
    fi
    if docker ps 2>/dev/null | grep -q "${CONTAINER_PREFIX}-redis"; then
        echo "   ✅ Redis - localhost:$REDIS_PORT"
    fi
    
    # 检查可选服务
    if docker ps 2>/dev/null | grep -q "temporal"; then
        echo "   ✅ Temporal"
    fi
    if docker ps 2>/dev/null | grep -q "minio"; then
        echo "   ✅ MinIO"
    fi
    echo ""
    
    echo "┌─────────────────────────────────────────────────────────┐"
    echo "│  📝 常用命令                                            │"
    echo "└─────────────────────────────────────────────────────────┘"
    echo "   查看日志:    pm2 logs"
    echo "   查看状态:    pm2 status"
    echo "   重启服务:    pm2 restart all"
    echo "   热重载:      bash scripts/deploy/deploy.sh $DEPLOY_ENV reload"
    echo "   停止服务:    bash scripts/deploy/deploy.sh $DEPLOY_ENV stop"
    echo ""
    
    echo "┌─────────────────────────────────────────────────────────┐"
    echo "│  🔙 回滚与备份                                          │"
    echo "└─────────────────────────────────────────────────────────┘"
    echo "   数据库回滚:  bash scripts/deploy/deploy.sh $DEPLOY_ENV db:rollback"
    echo "   备份位置:    $BACKUP_DIR"
    echo ""
    
    echo "╔═══════════════════════════════════════════════════════════╗"
    echo "║  💡 提示: 部署日志已保存到                                ║"
    echo "║  📂 $DEPLOY_LOG_FILE"
    echo "╚═══════════════════════════════════════════════════════════╝"
    echo ""
}

# ============================================================================
# PM2 热重载
# ============================================================================
cmd_reload() {
    local backend_only=false
    local frontend_only=false
    
    while [[ $# -gt 0 ]]; do
        case $1 in
            --backend) backend_only=true; shift ;;
            --frontend) frontend_only=true; shift ;;
            *) shift ;;
        esac
    done
    
    log_info "🔄 热重载 ${DEPLOY_ENV} 环境应用..."
    
    check_requirements
    
    if [ "$backend_only" = true ]; then
        pm2 reload "$PM2_BACKEND_NAME" 2>/dev/null || log_warn "后端未运行"
    elif [ "$frontend_only" = true ]; then
        pm2 reload "$PM2_FRONTEND_NAME" 2>/dev/null || log_warn "前端未运行"
    else
        pm2 reload all
    fi
    
    log_success "热重载完成"
}

# ============================================================================
# 查看应用日志
# ============================================================================
cmd_app_logs() {
    local app=$1
    
    check_requirements
    
    case $app in
        backend|back|b)
            pm2 logs "$PM2_BACKEND_NAME"
            ;;
        frontend|front|f)
            pm2 logs "$PM2_FRONTEND_NAME"
            ;;
        *)
            pm2 logs
            ;;
    esac
}

# ============================================================================
# 清理资源
# ============================================================================
cmd_clean() {
    local clean_all=false
    
    while [[ $# -gt 0 ]]; do
        case $1 in
            --all) clean_all=true; shift ;;
            *) shift ;;
        esac
    done
    
    if [ "$clean_all" = true ]; then
        log_warn "⚠️  此操作将清理所有 ${DEPLOY_ENV} Docker 资源，包括数据卷！"
        if confirm "确认清理？[y/N] " "n"; then
            check_env_file
            cd "$DOCKER_DIR"
            docker compose -p "$PROJECT_NAME" -f "$COMPOSE_FILE" --env-file "$ENV_FILE" down -v
            log_success "所有资源已清理"
        else
            log_info "取消清理"
        fi
    else
        cmd_down
    fi
}

# ============================================================================
# 检查环境变量配置
# ============================================================================
cmd_env_check() {
    log_step "检查环境变量配置..."
    
    check_env_file
    source_env_file
    
    local has_error=false
    local required_vars=(
        "DEPLOY_ENV"
        "BACKEND_PORT"
        "POSTGRES_DB"
        "POSTGRES_USER"
        "POSTGRES_PASSWORD"
        "REDIS_PASSWORD"
        "JWT_SECRET"
    )
    
    for var in "${required_vars[@]}"; do
        if [ -z "${!var}" ]; then
            log_error "缺少必需变量: $var"
            has_error=true
        elif [[ "${!var}" == *"CHANGE_ME"* ]]; then
            log_warn "变量未修改: $var (仍包含 CHANGE_ME)"
            has_error=true
        fi
    done
    
    # 验证 DATABASE_URL 一致性
    if [ -n "$DATABASE_URL" ]; then
        if [[ ! "$DATABASE_URL" =~ $POSTGRES_USER ]] || [[ ! "$DATABASE_URL" =~ $POSTGRES_DB ]]; then
            log_error "配置不一致: DATABASE_URL 与 POSTGRES_USER/POSTGRES_DB 不匹配"
            log_info "  POSTGRES_USER: $POSTGRES_USER"
            log_info "  POSTGRES_DB: $POSTGRES_DB"
            log_info "  DATABASE_URL: $DATABASE_URL"
            has_error=true
        fi
    fi
    
    # 验证 JWT_SECRET 长度
    if [ -n "$JWT_SECRET" ] && [ ${#JWT_SECRET} -lt 32 ]; then
        log_warn "JWT_SECRET 长度不足 32 字符（当前: ${#JWT_SECRET}）"
        has_error=true
    fi
    
    if [ "$has_error" = true ]; then
        log_error "环境变量配置检查失败"
        return 1
    fi
    
    log_success "环境变量配置检查通过"
    echo ""
    log_info "环境信息:"
    echo "  环境: $DEPLOY_ENV"
    echo "  前端端口: $FRONTEND_PORT"
    echo "  后端端口: $BACKEND_PORT"
    echo "  数据库: $POSTGRES_DB"
    echo "  PM2 实例: $PM2_INSTANCES ($PM2_EXEC_MODE 模式)"
    echo ""
    return 0
}

# ============================================================================
# 查看部署日志
# ============================================================================
cmd_logs_deploy() {
    local log_dir="$PROJECT_ROOT/logs/deploy"
    
    if [ ! -d "$log_dir" ]; then
        log_error "日志目录不存在: $log_dir"
        return 1
    fi
    
    log_info "📋 最近的部署日志："
    echo ""
    
    # 列出最近 20 个日志文件
    ls -lt "$log_dir"/*.log 2>/dev/null | head -20 | while read -r line; do
        echo "  $line"
    done
    
    echo ""
    log_info "💡 查看日志内容："
    log_info "  tail -f logs/deploy/[日志文件名]"
    log_info "  或"
    log_info "  less logs/deploy/[日志文件名]"
}

# ============================================================================
# 清理旧的部署日志
# ============================================================================
cmd_logs_clean() {
    local log_dir="$PROJECT_ROOT/logs/deploy"
    local keep_days=${1:-7}  # 默认保留 7 天
    
    if [ ! -d "$log_dir" ]; then
        log_warn "日志目录不存在: $log_dir"
        return 0
    fi
    
    log_info "清理 ${keep_days} 天前的部署日志..."
    
    local count=$(find "$log_dir" -name "*.log" -type f -mtime +${keep_days} 2>/dev/null | wc -l | tr -d ' ')
    
    if [ "$count" -eq 0 ]; then
        log_info "没有需要清理的日志"
        return 0
    fi
    
    echo ""
    log_warn "将删除 $count 个日志文件"
    if confirm "确认清理？[y/N] " "n"; then
        find "$log_dir" -name "*.log" -type f -mtime +${keep_days} -delete
        log_success "已清理旧日志"
    else
        log_info "取消清理"
    fi
}

# ============================================================================
# 显示帮助
# ============================================================================
show_help() {
    cat << 'EOF'
╔═══════════════════════════════════════════════════════╗
║                                                       ║
║      FF AI Workspace 统一部署脚本                    ║
║                                                       ║
╚═══════════════════════════════════════════════════════╝

用法:
    bash scripts/deploy/deploy.sh [环境] [命令] [选项]
    或
    DEPLOY_ENV=[环境] bash scripts/deploy/deploy.sh [命令] [选项]

环境:
    test         测试环境 (端口: 50XX, 43.153.69.73 上 develop 分支自动部署的 L2 站点)
    uat          UAT 用户验收环境 (端口: 70XX, staging 分支)
    production   正式环境 (端口: 60XX, production 分支)

命令:
    Docker 服务管理:
      up           启动 Docker 服务（默认启动核心服务）
        --all        启动所有服务（temporal + storage + monitoring + tools + knowledge）
        --basic      仅启动 PostgreSQL 和 Redis
        --temporal   启动 Temporal 工作流引擎
        --storage    启动 MinIO 对象存储
        --monitoring 启动 Prometheus + Grafana 监控
        --knowledge  启动 RAGFlow 知识库引擎
        --force      强制重启（跳过运行检查）
      down         停止 Docker 服务
        --volumes    同时删除数据卷
      restart      重启 Docker 服务
      status       查看服务状态
      logs [svc]   查看 Docker 日志
        -f           跟随日志输出
        --tail N     显示最后 N 行
      clean        清理资源
        --all        清理所有资源（含数据卷）
    
    应用管理:
      build        构建应用 ⭐
        --backend    仅构建后端
        --frontend   仅构建前端
        --skip-deps  跳过依赖安装（假设已安装）
      deploy       完整部署（默认启动所有 Docker 服务）⭐
        --skip-build      跳过构建
        --skip-migrate    跳过迁移
        --docker-basic    仅启动基础服务（PostgreSQL + Redis）
        --docker-core     仅启动核心服务
      run          快捷运行 (检查Docker + 构建 + 启动) ⭐
        --backend       仅运行后端
        --frontend      仅运行前端
        --skip-build    跳过构建
      start        启动应用 (PM2)
      stop         停止应用 (PM2)
      reload       热重载应用（零停机）
        --backend    仅重载后端
        --frontend   仅重载前端
      app:logs [app]  查看应用日志
        backend      后端日志
        frontend     前端日志
      pm2:delete   从 PM2 中完全删除应用
    
    数据库管理:
      db:migrate           运行数据库迁移（生产部署用）
      db:migrate:status    检查迁移状态（查看已应用/待应用的迁移）
      db:migrate:diff      检查 Schema 与数据库的差异
      db:migrate:create [name]  创建新迁移（仅生成文件，不应用）
      db:migrate:dev [name]     开发模式（生成并应用迁移）
      db:migrate:fix       修复损坏的迁移文件（清理无效迁移目录）
      db:migrate:reset     重置迁移历史（⚠️  危险：仅开发环境）
      db:push              推送数据库结构（无迁移记录，开发用）
      db:seed              运行全量种子数据（开发/测试环境用）
      db:seed:iam          仅初始化 IAM 权限 & 角色（生产上线新模块时用）
      db:seed:positions    仅初始化岗位数据
      db:seed:part-groups  仅初始化配件分组
      db:seed:dingtalk     仅初始化钉钉同步配置
      db:seed:sync-bot     仅初始化 Sync Bot 服务账号
      db:seed:robot-fields 仅初始化 Robot Manager FieldDefs（robot-manager 上线时用）
      db:backup            备份数据库
      db:rollback          回滚到最近备份
      db:restore <file>    从指定文件恢复
    
    初始化:
      init:permissions  初始化权限
      init:admin        创建管理员
      init:all          完整初始化
    
    其他:
      health       健康检查
      env:check    检查环境变量配置
      env:setup    环境检查和初始化（检查软链接、环境变量）
      logs:deploy  查看部署日志列表
      logs:clean [days]  清理旧的部署日志（默认保留 7 天）

示例:
    # UAT 环境完整部署
    bash scripts/deploy/deploy.sh uat deploy
    
    # 生产环境仅构建后端
    bash scripts/deploy/deploy.sh production build --backend
    
    # 使用环境变量
    export DEPLOY_ENV=uat
    bash scripts/deploy/deploy.sh up
    bash scripts/deploy/deploy.sh deploy

端口映射:
    测试 (test): 前端 5000, 后端 5001, 数据库 5002
    UAT:         前端 7000, 后端 7001, 数据库 7002
    正式:        前端 6064, 后端 6001, 数据库 6002 (⚠️ 6000-6063 为 X11 保留)

环境配置文件:
    test         → .env.test
    uat          → .env.uat
    production   → .env.pro

EOF
}

# ============================================================================
# 主函数
# ============================================================================
main() {
    # 解析环境参数
    local env="${DEPLOY_ENV:-}"
    local cmd=""
    
    # 如果第一个参数是环境名，则使用它（dev 是 test 的兼容别名，2026-06-09 移除）
    if [[ "$1" =~ ^(test|uat|production|dev)$ ]]; then
        env="$1"
        shift
    fi
    
    # 获取命令
    cmd="${1:-help}"
    shift || true
    
    # 特殊命令：不需要环境参数
    case $cmd in
        help|--help|-h)
            show_help
            exit 0
            ;;
    esac
    
    # 验证环境
    if [ -z "$env" ]; then
        log_error "未指定环境"
        echo ""
        log_info "使用方式 1: bash $0 [test|uat|production] [命令]"
        log_info "使用方式 2: DEPLOY_ENV=[test|uat|production] bash $0 [命令]"
        echo ""
        log_info "查看帮助: bash $0 help"
        echo ""
        exit 1
    fi
    
    # 加载环境配置
    load_env_config "$env"
    
    # 设置日志记录（所有命令的输出都会同时显示在终端和保存到文件）
    DEPLOY_LOG_DIR="$PROJECT_ROOT/logs/deploy"
    mkdir -p "$DEPLOY_LOG_DIR"
    
    # 生成日志文件名：环境_命令_时间戳.log
    local log_timestamp=$(date +%Y%m%d_%H%M%S)
    local log_filename="${env}_${cmd}_${log_timestamp}.log"
    DEPLOY_LOG_FILE="$DEPLOY_LOG_DIR/$log_filename"
    
    if [ "${CI:-false}" = "true" ]; then
        # CI 模式（Gitea Actions / GitHub Actions）：runner 已经抓 stdout 写进 job log，
        # 不再 redirect 到本地文件——避免 `tee >(sed ...)` process substitution 嵌套
        # 在 ssh non-tty 远端执行时把 stdout 全部 buffer，导致 CI job log 整段 0 字节
        # （issue #273 第 8 项 / PR #272 dogfooding 实测）
        echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
        echo "📝 [CI mode] stdout 直通 CI runner log，不写本地 deploy log 文件"
        echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
    else
        # 本地开发：用 tee 同时输出到终端和文件（去除颜色代码保存到文件）
        # 终端保留颜色，文件去除颜色便于查看
        exec > >(tee >(sed -r 's/\x1b\[[0-9;]*[mGKH]//g' >> "$DEPLOY_LOG_FILE"))
        exec 2>&1

        echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
        echo "📝 部署日志已启用"
        echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
        echo "📂 日志文件: $log_filename"
        echo "📍 完整路径: $DEPLOY_LOG_FILE"
        echo "💡 实时查看: tail -f $DEPLOY_LOG_FILE"
        echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
    fi
    echo ""
    
    # 执行命令
    case $cmd in
        up) cmd_up "$@" ;;
        down) cmd_down "$@" ;;
        restart) cmd_restart "$@" ;;
        status) cmd_status ;;
        logs) cmd_logs "$@" ;;
        
        build) cmd_build "$@" ;;
        deploy) cmd_deploy "$@" ;;
        run) cmd_run "$@" ;;
        start) cmd_start ;;
        stop) cmd_stop ;;
        reload) cmd_reload "$@" ;;
        
        db:migrate) cmd_db_migrate ;;
        db:migrate:status) cmd_db_migrate_status ;;
        db:migrate:diff) cmd_db_migrate_diff ;;
        db:migrate:create) cmd_db_migrate_create "$@" ;;
        db:migrate:dev) cmd_db_migrate_dev "$@" ;;
        db:migrate:fix) cmd_db_migrate_fix ;;
        db:migrate:reset) cmd_db_migrate_reset ;;
        db:push) cmd_db_push ;;
        db:seed) cmd_db_seed ;;
        db:seed:iam) cmd_db_seed_iam ;;
        db:seed:positions) cmd_db_seed_positions ;;
        db:seed:part-groups) cmd_db_seed_part_groups ;;
        db:seed:dingtalk) cmd_db_seed_dingtalk ;;
        db:seed:sync-bot) cmd_db_seed_sync_bot ;;
        db:seed:robot-fields) cmd_db_seed_robot_fields ;;
        db:backup) cmd_db_backup ;;
        db:rollback) cmd_db_rollback ;;
        db:restore) cmd_db_restore "$@" ;;
        
        init:permissions) cmd_init_permissions ;;
        init:admin) cmd_init_admin ;;
        init:all) cmd_init_all ;;
        
        health) cmd_health ;;
        clean) cmd_clean "$@" ;;
        
        app:logs) cmd_app_logs "$@" ;;
        pm2:delete) cmd_pm2_delete ;;
        
        env:check) cmd_env_check ;;
        env:setup) check_and_setup_environment ;;
        
        logs:deploy) cmd_logs_deploy ;;
        logs:clean) cmd_logs_clean "$@" ;;
        
        help|--help|-h) show_help ;;
        *)
            log_error "未知命令: $cmd"
            echo ""
            show_help
            exit 1
            ;;
    esac
}

# 运行主函数
main "$@"
