#!/bin/bash

##############################################
# 宝塔服务器 - 更新监控脚本
# 用途：监听 Gitee 新 tag，自动发布更新
# 使用：在宝塔计划任务中每小时执行一次
# 注意：不编译代码，只从 Gitee 拉取已编译好的文件
##############################################

# ==================== 配置区域 ====================

# Gitee 仓库地址（已配置认证信息）
GITEE_REPO="https://15566402095:wqwq2013@gitee.com/dreams619/quant-nova.git"

# 工作目录（用于克隆仓库）
WORK_DIR="/www/wwwroot/update-builder"

# 更新文件存放目录
UPDATE_DIR="/www/wwwroot/update-builder/updates"

# 服务器地址（用于生成下载链接）
DOMAIN="updates.lingyaoai.com"

# 日志文件
LOG_FILE="/www/wwwroot/update-builder/monitor.log"

# ==================== 配置区域结束 ====================

# 日志函数
log() {
    echo "[$(date '+%Y-%m-%d %H:%M:%S')] $1" | tee -a "$LOG_FILE"
}

error_exit() {
    log "❌ 错误: $1"
    exit 1
}

# Go 环境变量设置函数
setup_go_env() {
    export PATH=$PATH:/usr/local/go/bin
    export GOPATH=/root/go
    export GOMODCACHE=/root/go/pkg/mod
    export GOCACHE=/root/.cache/go-build
    # 创建必要目录
    mkdir -p "$GOPATH" "$GOMODCACHE" "$GOCACHE"
}

# Go 安装函数
install_go() {
    GO_VERSION="1.21.5"
    GO_TAR="go${GO_VERSION}.linux-amd64.tar.gz"
    GO_URL="https://go.dev/dl/${GO_TAR}"
    
    log "◆ 未检测到 Go，开始自动安装..."
    cd /tmp
    
    # 删除可能损坏的旧文件
    rm -f "$GO_TAR"
    
    # 下载 Go（带重试）
    log "⬇️  下载 Go ${GO_VERSION}..."
    for i in 1 2 3; do
        log "   尝试第 $i 次下载..."
        wget --timeout=120 --tries=1 -O "$GO_TAR" "$GO_URL" 2>&1
        
        # 检查文件大小（Go 1.21.5 约 63MB）
        if [ -f "$GO_TAR" ]; then
            FILE_SIZE=$(stat -c%s "$GO_TAR" 2>/dev/null || echo 0)
            if [ "$FILE_SIZE" -gt 60000000 ]; then
                log "   ✅ 下载完成，文件大小: $FILE_SIZE 字节"
                break
            else
                log "   ⚠️ 文件不完整（$FILE_SIZE 字节），重试..."
                rm -f "$GO_TAR"
            fi
        fi
        
        if [ $i -eq 3 ]; then
            log "❌ 下载 Go 失败，请检查网络"
            return 1
        fi
        sleep 5
    done
    
    # 解压安装
    log "📦 解压安装 Go..."
    rm -rf /usr/local/go
    tar -C /usr/local -xzf "$GO_TAR" || {
        log "❌ 解压 Go 失败"
        rm -f "$GO_TAR"
        return 1
    }
    
    # 设置环境变量
    setup_go_env
    
    # 持久化环境变量
    if ! grep -q '/usr/local/go/bin' /etc/profile 2>/dev/null; then
        cat >> /etc/profile <<'GOENV'
export PATH=$PATH:/usr/local/go/bin
export GOPATH=/root/go
export GOMODCACHE=/root/go/pkg/mod
GOENV
    fi
    
    # 清理
    rm -f "/tmp/$GO_TAR"
    log "✅ Go 安装完成: $(/usr/local/go/bin/go version)"
    return 0
}

# 开始执行
log "=========================================="
log "🚀 开始检查更新..."

# 检查必要工具
command -v git >/dev/null 2>&1 || error_exit "未安装 git"
command -v zip >/dev/null 2>&1 || error_exit "未安装 zip"

# 创建工作目录
mkdir -p "$WORK_DIR" || error_exit "无法创建工作目录"
mkdir -p "$UPDATE_DIR/api"
mkdir -p "$UPDATE_DIR/packages"

cd "$WORK_DIR" || error_exit "无法进入工作目录"

# 克隆或更新仓库
if [ -d "repo" ]; then
    log "📦 更新现有仓库..."
    cd repo
    
    # 重要：清理本地 tags，确保与远程同步
    git tag -l | xargs -r git tag -d >/dev/null 2>&1
    
    # 获取远程最新信息（静默模式）
    git fetch --prune origin >/dev/null 2>&1 || error_exit "无法获取远程信息"
    git fetch --tags --force origin >/dev/null 2>&1 || error_exit "无法获取 tags"
else
    log "📦 首次克隆仓库..."
    git clone "$GITEE_REPO" repo || error_exit "无法克隆仓库"
    cd repo
fi

# 获取最新 tag（按 Git 提交时间排序，最新提交的 tag 就是最新版本）
LATEST_TAG=$(git for-each-ref --sort=-creatordate --format='%(refname:short)' refs/tags | head -n 1)

# 如果上面的方法失败，尝试备用方法
if [ -z "$LATEST_TAG" ]; then
    LATEST_TAG=$(git tag --sort=-creatordate 2>/dev/null | head -n 1)
fi

# 如果还是失败，使用最后的备用方法
if [ -z "$LATEST_TAG" ]; then
    LATEST_TAG=$(git log --tags --simplify-by-decoration --pretty="format:%ai %d" | grep -oP 'tag: \K[^,)]+' | head -n 1)
fi

if [ -z "$LATEST_TAG" ]; then
    log "ℹ️  仓库中没有 tag，跳过"
    exit 0
fi

log "📌 最新 tag: $LATEST_TAG"

# 检查 latest.json 是否存在
if [ ! -f "$UPDATE_DIR/api/latest.json" ]; then
    log "⚠️  latest.json 不存在，将创建初始版本"
    CURRENT_VERSION=""
else
    # 读取当前版本
    CURRENT_VERSION=$(grep -o '"version": "[^"]*"' "$UPDATE_DIR/api/latest.json" | cut -d'"' -f4)
    log "📍 当前版本: $CURRENT_VERSION"
fi

# 检查压缩包是否存在
PACKAGE_PATH="$UPDATE_DIR/packages/update_${LATEST_TAG}.zip"
PACKAGE_EXISTS=false
if [ -f "$PACKAGE_PATH" ]; then
    PACKAGE_EXISTS=true
    log "📦 压缩包已存在: $PACKAGE_PATH"
fi

# 比较版本 + 检查压缩包
if [ "$LATEST_TAG" = "$CURRENT_VERSION" ] && [ "$PACKAGE_EXISTS" = true ]; then
    log "✅ 已是最新版本，且压缩包存在，无需更新"
    exit 0
fi

# 如果版本相同但压缩包不存在，需要重新构建
if [ "$LATEST_TAG" = "$CURRENT_VERSION" ] && [ "$PACKAGE_EXISTS" = false ]; then
    log "⚠️  版本相同但压缩包丢失，重新构建..."
fi

log "🎉 发现新版本: $LATEST_TAG"
log "📦 开始处理更新包..."

# 清理编译产物，避免 checkout 冲突
log "🧹 清理编译产物..."
rm -f nofx-linux nofx.exe 2>/dev/null
rm -rf web/dist 2>/dev/null

# 保存当前 package.json 哈希（用于判断依赖是否变化）
OLD_PKG_HASH=""
if [ -f "web/package.json" ]; then
    OLD_PKG_HASH=$(sha256sum web/package.json 2>/dev/null | cut -d' ' -f1)
fi

# 重置工作目录，丢弃所有本地修改（静默）
git reset --hard HEAD >/dev/null 2>&1
git clean -fd >/dev/null 2>&1

# 切换到新版本（静默）
git checkout "$LATEST_TAG" >/dev/null 2>&1 || error_exit "无法切换到 tag $LATEST_TAG"

# ==================== 编译前端 ====================
log "🔧 编译前端..."

# 检查 Node.js 是否可用
NODE_BIN=""
NPM_BIN=""

# 宝塔面板 Node.js 路径（只匹配 v 开头后面跟数字的目录，排除 vhost 等）
if [ -d "/www/server/nodejs" ]; then
    NODE_DIR=$(ls -d /www/server/nodejs/v[0-9]* 2>/dev/null | sort -V | tail -n 1)
    if [ -n "$NODE_DIR" ] && [ -f "$NODE_DIR/bin/node" ]; then
        NODE_BIN="$NODE_DIR/bin/node"
        NPM_BIN="$NODE_DIR/bin/npm"
    fi
fi

# 标准安装路径
if [ -z "$NODE_BIN" ] && [ -f "/usr/local/node/bin/node" ]; then
    NODE_BIN="/usr/local/node/bin/node"
    NPM_BIN="/usr/local/node/bin/npm"
fi

# 系统路径
if [ -z "$NODE_BIN" ] && command -v node >/dev/null 2>&1; then
    NODE_BIN="node"
    NPM_BIN="npm"
fi

if [ -z "$NODE_BIN" ]; then
    error_exit "未找到 Node.js，请先在宝塔面板安装 Node.js"
fi

# 将 Node.js 目录加入 PATH（让 esbuild 等包能找到 node 命令）
NODE_BIN_DIR=$(dirname "$NODE_BIN")
export PATH="$NODE_BIN_DIR:$PATH"

log "   Node.js: $($NODE_BIN -v)"

# 进入前端目录编译
cd web || error_exit "无法进入 web 目录"

# 检查 package.json 是否有变化
NEW_PKG_HASH=$(sha256sum package.json 2>/dev/null | cut -d' ' -f1)
DEPS_CHANGED=false
if [ "$OLD_PKG_HASH" != "$NEW_PKG_HASH" ]; then
    DEPS_CHANGED=true
    log "   📦 检测到依赖变化，清理旧依赖..."
    rm -rf node_modules package-lock.json 2>/dev/null
fi

# 安装依赖（只在需要时安装）
if [ "$DEPS_CHANGED" = true ] || [ ! -d "node_modules" ]; then
    log "   安装依赖..."
    NPM_OUTPUT=$($NPM_BIN install --legacy-peer-deps 2>&1)
    NPM_EXIT_CODE=$?
    
    if [ $NPM_EXIT_CODE -ne 0 ]; then
        # 如果安装失败，尝试清理后重装
        log "   ⚠️ 安装失败，尝试清理后重装..."
        rm -rf node_modules package-lock.json 2>/dev/null
        NPM_OUTPUT=$($NPM_BIN install --legacy-peer-deps 2>&1)
        NPM_EXIT_CODE=$?
        
        if [ $NPM_EXIT_CODE -ne 0 ]; then
            log "❌ 前端依赖安装失败 (退出码: $NPM_EXIT_CODE)"
            log "   错误详情: $(echo "$NPM_OUTPUT" | tail -20)"
            cd ..
            exit 1
        fi
    fi
else
    log "   ✅ 依赖未变化且 node_modules 存在，跳过安装"
fi

log "   构建前端..."
BUILD_OUTPUT=$($NPM_BIN run build 2>&1)
BUILD_EXIT_CODE=$?

if [ $BUILD_EXIT_CODE -ne 0 ]; then
    log "❌ 前端构建失败 (退出码: $BUILD_EXIT_CODE)"
    log "   错误详情: $(echo "$BUILD_OUTPUT" | tail -20)"
    cd ..
    exit 1
fi

cd ..

if [ ! -d "web/dist" ]; then
    error_exit "前端构建后未找到 dist 目录"
fi

log "✅ 前端编译成功"

# ==================== 编译后端 ====================
log "🔧 编译后端..."

# 设置 Go 环境变量
setup_go_env

# 检查 Go 是否可用
GO_BIN=""
if [ -f "/usr/local/go/bin/go" ]; then
    GO_BIN="/usr/local/go/bin/go"
elif command -v go >/dev/null 2>&1; then
    GO_BIN="go"
else
    # 安装 Go
    install_go || error_exit "Go 安装失败"
    GO_BIN="/usr/local/go/bin/go"
fi

# 编译后端
$GO_BIN build -o nofx-linux || {
    log "❌ 后端编译失败"
    exit 1
}

log "✅ 后端编译成功"

# 创建更新包目录
UPDATE_PKG_DIR="$WORK_DIR/update_$LATEST_TAG"
rm -rf "$UPDATE_PKG_DIR"
mkdir -p "$UPDATE_PKG_DIR/backend"
mkdir -p "$UPDATE_PKG_DIR/frontend"
mkdir -p "$UPDATE_PKG_DIR/migrations"

log "📦 打包文件..."

# 复制文件
cp nofx-linux "$UPDATE_PKG_DIR/backend/" || error_exit "复制后端失败"
cp -r web/dist "$UPDATE_PKG_DIR/frontend/" || error_exit "复制前端失败"

# 复制迁移脚本（如果存在）
if [ -d "config/migrations" ]; then
    cp config/migrations/*.sql "$UPDATE_PKG_DIR/migrations/" 2>/dev/null || true
fi

# 获取 Git 提交信息作为更新日志
GIT_LOG=$(git log -1 --pretty=format:"%s%n%n%b" 2>/dev/null || echo "版本更新")

# 创建 manifest.json
log "📝 创建 manifest.json..."
cat > "$UPDATE_PKG_DIR/manifest.json" <<EOF
{
    "version": "$LATEST_TAG",
    "description": "Auto-generated from Git tag $LATEST_TAG",
    "files": [
        "backend/nofx-linux",
        "frontend/dist/"
    ],
    "migrations": [],
    "exclude_files": [
        "config.db",
        "config.db-shm",
        "config.db-wal",
        "config.json"
    ]
}
EOF

# 打包成 zip
log "🗜️  压缩更新包..."
cd "$UPDATE_PKG_DIR" || error_exit "无法进入更新包目录"
zip -r "../update_${LATEST_TAG}.zip" . || error_exit "压缩失败"
cd ..

# 计算文件信息
PACKAGE_FILE="update_${LATEST_TAG}.zip"
HASH=$(sha256sum "$PACKAGE_FILE" | cut -d' ' -f1)
SIZE=$(stat -c%s "$PACKAGE_FILE")

log "📊 文件信息:"
log "   大小: $SIZE 字节"
log "   哈希: $HASH"

# 移动更新包
log "📤 发布更新包..."
mv "$PACKAGE_FILE" "$UPDATE_DIR/packages/" || error_exit "移动更新包失败"

# 转义更新日志中的换行符和引号
UPDATE_LOG_ESCAPED=$(echo "$GIT_LOG" | sed 's/"/\\"/g' | sed ':a;N;$!ba;s/\n/\\n/g')

# 生成 latest.json（不包含提交者信息）
log "📝 更新 latest.json..."
cat > "$UPDATE_DIR/api/latest.json" <<EOF
{
    "version": "$LATEST_TAG",
    "release_date": "$(date -u +%Y-%m-%dT%H:%M:%SZ)",
    "update_log": "# $LATEST_TAG 更新内容\\n\\n$UPDATE_LOG_ESCAPED",
    "package_url": "https://$DOMAIN/updates/packages/update_${LATEST_TAG}.zip",
    "package_size": $SIZE,
    "package_hash": "$HASH"
}
EOF

# 设置文件权限
chmod 644 "$UPDATE_DIR/api/latest.json"
chmod 644 "$UPDATE_DIR/packages/update_${LATEST_TAG}.zip"

# 清理临时文件
log "🧹 清理临时文件..."
rm -rf "$UPDATE_PKG_DIR"

log "✅ 发布完成！"
log "📦 更新包: update_${LATEST_TAG}.zip"
log "🔗 下载地址: https://$DOMAIN/updates/packages/update_${LATEST_TAG}.zip"
log "📝 更新日志: $GIT_LOG"
log "=========================================="

# 可选：保留最近 3 个版本，删除旧版本
log "🧹 清理旧版本..."
cd "$UPDATE_DIR/packages"
ls -t update_*.zip | tail -n +4 | xargs -r rm -f
log "✅ 清理完成"

exit 0
