#!/bin/bash

# ==============================================================================
# 无人机自然语言控制项目 - 一键启动脚本
# ==============================================================================
# 功能：启动所有必需的服务（llama-server推理模型、embedding模型、FastAPI后端）
# 用法：./start_all.sh [选项]
# ==============================================================================

set -e  # 遇到错误立即退出

# 颜色定义
RED='\033[0;31m'
GREEN='\033[0;32m'
YELLOW='\033[1;33m'
BLUE='\033[0;34m'
NC='\033[0m' # No Color

# 默认配置（可通过环境变量覆盖）
PROJECT_ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
LLAMA_SERVER_DIR="${LLAMA_SERVER_DIR:-~/llama.cpp/build/bin}"
INFERENCE_MODEL="${INFERENCE_MODEL:-~/models/gguf/Qwen/Qwen3-4B/Qwen3-4B-Q5_K_M.gguf}"
EMBEDDING_MODEL="${EMBEDDING_MODEL:-~/models/gguf/Qwen/Qwen3-Embedding-4B/Qwen3-Embedding-4B-Q4_K_M.gguf}"
VENV_PATH="${VENV_PATH:-${PROJECT_ROOT}/backend_service/venv}"
LOG_DIR="${PROJECT_ROOT}/logs"
PID_FILE="${LOG_DIR}/services.pid"

# 端口配置
INFERENCE_PORT=8081
EMBEDDING_PORT=8090
API_PORT=8000

# 创建日志目录
mkdir -p "${LOG_DIR}"

# ==============================================================================
# 辅助函数
# ==============================================================================

print_info() {
    echo -e "${BLUE}[INFO]${NC} $1"
}

print_success() {
    echo -e "${GREEN}[SUCCESS]${NC} $1"
}

print_warning() {
    echo -e "${YELLOW}[WARNING]${NC} $1"
}

print_error() {
    echo -e "${RED}[ERROR]${NC} $1"
}

# 检查命令是否存在
check_command() {
    if ! command -v "$1" &> /dev/null; then
        print_error "$1 命令未找到，请先安装"
        return 1
    fi
    return 0
}

# 检查端口是否被占用
check_port() {
    local port=$1
    if lsof -Pi :${port} -sTCP:LISTEN -t >/dev/null 2>&1 ; then
        return 0  # 端口被占用
    else
        return 1  # 端口空闲
    fi
}

# 等待服务就绪
wait_for_service() {
    local url=$1
    local service_name=$2
    local max_attempts=30
    local attempt=0
    
    print_info "等待 ${service_name} 启动..."
    while [ $attempt -lt $max_attempts ]; do
        if curl -s "${url}" > /dev/null 2>&1; then
            print_success "${service_name} 已就绪"
            return 0
        fi
        attempt=$((attempt + 1))
        sleep 1
    done
    
    print_error "${service_name} 启动超时"
    return 1
}

# 停止所有服务
stop_services() {
    print_info "正在停止所有服务..."
    
    if [ -f "${PID_FILE}" ]; then
        while read pid; do
            if ps -p $pid > /dev/null 2>&1; then
                print_info "停止进程 PID: $pid"
                kill $pid 2>/dev/null || true
            fi
        done < "${PID_FILE}"
        rm -f "${PID_FILE}"
    fi
    
    # 尝试通过端口停止服务
    for port in ${INFERENCE_PORT} ${EMBEDDING_PORT} ${API_PORT}; do
        if check_port ${port}; then
            local pid=$(lsof -ti:${port})
            if [ ! -z "$pid" ]; then
                print_info "停止占用端口 ${port} 的进程 (PID: $pid)"
                kill $pid 2>/dev/null || true
            fi
        fi
    done
    
    print_success "所有服务已停止"
}

# 清理函数（脚本退出时调用）
cleanup() {
    if [ "$?" -ne 0 ]; then
        print_error "启动过程中发生错误，正在清理..."
    fi
    # 注意：这里不自动停止服务，让用户手动控制
}

trap cleanup EXIT

# ==============================================================================
# 主函数
# ==============================================================================

start_services() {
    print_info "=========================================="
    print_info "  无人机自然语言控制项目 - 服务启动"
    print_info "=========================================="
    echo ""
    
    # 检查必要的命令
    print_info "检查必要的命令..."
    check_command "python3" || exit 1
    check_command "curl" || exit 1
    check_command "lsof" || print_warning "lsof 未安装，将无法检查端口占用"
    echo ""
    
    # 检查端口占用
    print_info "检查端口占用..."
    if check_port ${INFERENCE_PORT}; then
        print_warning "端口 ${INFERENCE_PORT} 已被占用，推理模型可能已在运行"
    fi
    if check_port ${EMBEDDING_PORT}; then
        print_warning "端口 ${EMBEDDING_PORT} 已被占用，Embedding模型可能已在运行"
    fi
    if check_port ${API_PORT}; then
        print_error "端口 ${API_PORT} 已被占用，请先停止占用该端口的服务"
        exit 1
    fi
    echo ""
    
    # 检查llama-server（展开路径中的 ~）
    local llama_server_dir_expanded=$(eval echo "${LLAMA_SERVER_DIR}")
    local llama_server="${llama_server_dir_expanded}/llama-server"
    if [ ! -f "${llama_server}" ]; then
        print_error "llama-server 未找到: ${llama_server}"
        print_info "请设置 LLAMA_SERVER_DIR 环境变量指向正确的路径"
        print_info "当前路径: ${LLAMA_SERVER_DIR}"
        print_info "展开后路径: ${llama_server_dir_expanded}"
        exit 1
    fi
    print_success "找到 llama-server: ${llama_server}"
    echo ""
    
    # 检查模型文件
    local inference_model_expanded=$(eval echo "${INFERENCE_MODEL}")
    local embedding_model_expanded=$(eval echo "${EMBEDDING_MODEL}")
    
    if [ ! -f "${inference_model_expanded}" ]; then
        print_error "推理模型文件未找到: ${inference_model_expanded}"
        print_info "请设置 INFERENCE_MODEL 环境变量指向正确的模型路径"
        exit 1
    fi
    print_success "找到推理模型: ${inference_model_expanded}"
    
    if [ ! -f "${embedding_model_expanded}" ]; then
        print_error "Embedding模型文件未找到: ${embedding_model_expanded}"
        print_info "请设置 EMBEDDING_MODEL 环境变量指向正确的模型路径"
        exit 1
    fi
    print_success "找到Embedding模型: ${embedding_model_expanded}"
    echo ""
    
    # 检查ROS2环境
    local ros2_setup="${PROJECT_ROOT}/install/setup.bash"
    if [ ! -f "${ros2_setup}" ]; then
        print_warning "ROS2 setup文件未找到: ${ros2_setup}"
        print_warning "如果项目已与ROS2解耦，可以忽略此警告"
    else
        print_success "找到ROS2 setup文件: ${ros2_setup}"
    fi
    echo ""
    
    # 检查venv虚拟环境
    local venv_path_expanded=$(eval echo "${VENV_PATH}")
    print_info "检查venv虚拟环境: ${venv_path_expanded}"
    if [ ! -d "${venv_path_expanded}" ]; then
        print_error "venv虚拟环境目录不存在: ${venv_path_expanded}"
        print_info "请先创建venv环境: python3 -m venv ${venv_path_expanded}"
        print_info "然后安装依赖: ${venv_path_expanded}/bin/pip install -r backend_service/requirements.txt"
        exit 1
    fi
    if [ ! -f "${venv_path_expanded}/bin/activate" ]; then
        print_error "venv激活脚本不存在: ${venv_path_expanded}/bin/activate"
        print_error "这看起来不是一个有效的venv环境"
        exit 1
    fi
    print_success "venv虚拟环境存在: ${venv_path_expanded}"
    echo ""
    
    # 初始化PID文件
    > "${PID_FILE}"
    
    # ==========================================================================
    # 启动推理模型服务
    # ==========================================================================
    print_info "启动推理模型服务 (端口 ${INFERENCE_PORT})..."
    cd "${llama_server_dir_expanded}"
    nohup ./llama-server \
        -m "${inference_model_expanded}" \
        --port ${INFERENCE_PORT} \
        --gpu-layers 36 \
        --host 0.0.0.0 \
        -c 8192 \
        > "${LOG_DIR}/inference_model.log" 2>&1 &
    local inference_pid=$!
    echo $inference_pid >> "${PID_FILE}"
    print_success "推理模型服务已启动 (PID: $inference_pid)"
    print_info "日志文件: ${LOG_DIR}/inference_model.log"
    echo ""
    
    # ==========================================================================
    # 启动Embedding模型服务
    # ==========================================================================
    print_info "启动Embedding模型服务 (端口 ${EMBEDDING_PORT})..."
    nohup ./llama-server \
        -m "${embedding_model_expanded}" \
        --gpu-layers 36 \
        --port ${EMBEDDING_PORT} \
        --embeddings \
        --pooling last \
        --host 0.0.0.0 \
        > "${LOG_DIR}/embedding_model.log" 2>&1 &
    local embedding_pid=$!
    echo $embedding_pid >> "${PID_FILE}"
    print_success "Embedding模型服务已启动 (PID: $embedding_pid)"
    print_info "日志文件: ${LOG_DIR}/embedding_model.log"
    echo ""
    
    # ==========================================================================
    # 等待模型服务就绪
    # ==========================================================================
    print_info "等待模型服务就绪..."
    sleep 3  # 给服务一些启动时间
    
    # 等待推理模型服务
    if ! wait_for_service "http://localhost:${INFERENCE_PORT}/health" "推理模型服务"; then
        # 如果health端点不存在，尝试检查根路径
        if ! wait_for_service "http://localhost:${INFERENCE_PORT}/v1/models" "推理模型服务"; then
            print_warning "推理模型服务可能未完全就绪，但将继续启动"
        fi
    fi
    
    # 等待Embedding模型服务
    if ! wait_for_service "http://localhost:${EMBEDDING_PORT}/health" "Embedding模型服务"; then
        if ! wait_for_service "http://localhost:${EMBEDDING_PORT}/v1/models" "Embedding模型服务"; then
            print_warning "Embedding模型服务可能未完全就绪，但将继续启动"
        fi
    fi
    echo ""
    
    # ==========================================================================
    # 启动FastAPI后端服务
    # ==========================================================================
    print_info "启动FastAPI后端服务 (端口 ${API_PORT})..."
    cd "${PROJECT_ROOT}"
    
    # 激活venv虚拟环境并启动FastAPI服务
    # 使用bash -c来在新的shell中激活venv环境
    bash -c "
        # 激活ROS2环境（如果存在）
        if [ -f '${ros2_setup}' ]; then
            source '${ros2_setup}'
        fi
        # 激活venv虚拟环境
        source '${venv_path_expanded}/bin/activate' && \
        cd '${PROJECT_ROOT}/backend_service' && \
        uvicorn src.main:app --host 0.0.0.0 --port ${API_PORT}
    " > "${LOG_DIR}/fastapi.log" 2>&1 &
    local api_pid=$!
    echo $api_pid >> "${PID_FILE}"
    print_success "FastAPI服务已启动 (PID: $api_pid)"
    print_info "日志文件: ${LOG_DIR}/fastapi.log"
    echo ""
    
    # 等待FastAPI服务就绪
    sleep 3
    if wait_for_service "http://localhost:${API_PORT}/docs" "FastAPI服务"; then
        print_success "所有服务已成功启动！"
    else
        print_warning "FastAPI服务可能未完全就绪，请检查日志: ${LOG_DIR}/fastapi.log"
    fi
    echo ""
    
    # 显示服务访问信息
    print_info "=========================================="
    print_info "  服务启动完成！"
    print_info "=========================================="
    print_info "推理模型API: http://localhost:${INFERENCE_PORT}/v1"
    print_info "Embedding模型API: http://localhost:${EMBEDDING_PORT}/v1"
    print_info "FastAPI后端: http://localhost:${API_PORT}"
    print_info "API文档: http://localhost:${API_PORT}/docs"
    print_info ""
    print_info "日志文件位置:"
    print_info "  - 推理模型: ${LOG_DIR}/inference_model.log"
    print_info "  - Embedding模型: ${LOG_DIR}/embedding_model.log"
    print_info "  - FastAPI服务: ${LOG_DIR}/fastapi.log"
    print_info ""
    print_info "按 Ctrl+C 停止所有服务"
    print_info "=========================================="
    echo ""
    
    # 设置信号处理，确保Ctrl+C时能清理
    trap 'print_info "\n正在停止服务..."; stop_services; exit 0' INT TERM
    
    # 等待所有后台进程（保持脚本运行）
    print_info "所有服务正在运行中，查看日志请使用:"
    print_info "  tail -f ${LOG_DIR}/*.log"
    echo ""
    
    # 等待所有后台进程
    wait
}

# ==============================================================================
# 脚本入口
# ==============================================================================

case "${1:-start}" in
    start)
        start_services
        ;;
    stop)
        stop_services
        ;;
    restart)
        stop_services
        sleep 2
        start_services
        ;;
    status)
        print_info "检查服务状态..."
        if [ -f "${PID_FILE}" ]; then
            print_info "已记录的服务进程:"
            while read pid; do
                if ps -p $pid > /dev/null 2>&1; then
                    print_success "PID $pid: 运行中"
                else
                    print_warning "PID $pid: 已停止"
                fi
            done < "${PID_FILE}"
        else
            print_info "未找到PID文件，服务可能未启动"
        fi
        echo ""
        print_info "端口占用情况:"
        for port in ${INFERENCE_PORT} ${EMBEDDING_PORT} ${API_PORT}; do
            if check_port ${port}; then
                local pid=$(lsof -ti:${port})
                print_success "端口 ${port}: 被占用 (PID: $pid)"
            else
                print_warning "端口 ${port}: 空闲"
            fi
        done
        ;;
    *)
        echo "用法: $0 {start|stop|restart|status}"
        echo ""
        echo "命令说明:"
        echo "  start   - 启动所有服务（默认）"
        echo "  stop    - 停止所有服务"
        echo "  restart - 重启所有服务"
        echo "  status  - 查看服务状态"
        echo ""
        echo "环境变量配置:"
        echo "  LLAMA_SERVER_DIR  - llama-server所在目录 (默认: ~/llama.cpp/build/bin)"
        echo "  INFERENCE_MODEL   - 推理模型路径 (默认: ~/models/gguf/Qwen/Qwen3-4B/Qwen3-4B-Q5_K_M.gguf)"
        echo "  EMBEDDING_MODEL   - Embedding模型路径 (默认: ~/models/gguf/Qwen/Qwen3-Embedding-4B/Qwen3-Embedding-4B-Q4_K_M.gguf)"
        echo "  VENV_PATH         - venv虚拟环境路径 (默认: \${PROJECT_ROOT}/backend_service/venv)"
        exit 1
        ;;
esac

