#!/usr/bin/env bash
#
# install_sa2_exec.sh - 下载并安装最新版本的 SA 程序（目录版本）
#
# 功能：
#   1. 检测系统架构和本地安装版本（通过软链接 sa2 指向的目录名提取）
#   2. 比较版本，决定下载更新或重新安装
#   3. 支持使用本地已下载的压缩包进行安装（跳过网络下载）
#   4. 执行当前 sa2 目录下的 killall.sh（如果存在）以停止运行中的程序
#   5. 备份旧目录（重新安装时）
#   6. 解压（下载的或本地的）压缩包到新版本目录
#   7. 执行配置迁移：若新目录下有 sa_setup.sh，则运行 ./sa_setup.sh --params-src 旧目录/conf --params-dst 新目录/conf
#   8. 移动 data、log、tmp 目录（仅当非空时）
#   9. 更新软链接 sa2 指向新目录
#   10. 安装完成后自动删除下载的压缩包（如果是网络下载的）
#   11. 可选地将 sa2 目录添加到当前用户的 PATH 中（通过 shell 配置文件），并自动 source 使立即生效
#   12. 提示清理或保留旧目录
#
# 选项：
#   -f, --force-install  强制重新安装（即使本地已是最新版本），此时会备份原目录后重新部署
#   -y, --yes            自动确认所有提示（包括重新安装、PATH 配置等），并传递给 sa_setup.sh
#   -v, --version        显示脚本版本并退出
#   -h, --help           显示此帮助信息并退出
#
# 参数：
#   可以指定一个本地压缩包文件路径作为最后一个参数，格式必须为 sa-v{version}-{os}_{arch}.tar.gz
#   例如：./install_sa2_exec.sh sa-v3.0.0.27-linux_amd64.tar.gz
#   指定本地文件后，脚本将跳过网络下载，直接使用该文件进行安装，并自动备份当前目录（如果存在）。
#
# 注意：为使 PATH 修改在当前 shell 立即生效，建议使用 source 执行本脚本：
#       source ./install_sa2_exec.sh   或   . ./install_sa2_exec.sh
#
# 版本：2.6.0
# 依赖：curl 或 wget，tar，常见 Unix 工具

set -euo pipefail

SCRIPT_VERSION="2.6.0"
INNER_BASE_URL="http://192.168.3.9/sa2/app/"
OUTER_BASE_URL="http://sc.casstk.com/sa2/app/"
VERSION_FILE="version.md"
TMP_DIR=$(mktemp -d)
trap 'rm -rf "$TMP_DIR"' EXIT

# 颜色输出（输出到标准错误）
GREEN='\033[0;32m'
YELLOW='\033[1;33m'
RED='\033[0;31m'
NC='\033[0m'

info()  { echo -e "${GREEN}[INFO]${NC} $*" >&2; }
warn()  { echo -e "${YELLOW}[WARN]${NC} $*" >&2; }
error() { echo -e "${RED}[ERROR]${NC} $*" >&2; exit 1; }

show_help() {
    cat <<EOF
用法: $0 [选项] [本地压缩包]

选项:
  -f, --force-install  强制重新安装（即使本地已是最新版本），此时会备份原目录后重新部署
  -y, --yes            自动确认所有提示（包括重新安装、PATH 配置等），并传递给 sa_setup.sh
  -v, --version        显示脚本版本并退出
  -h, --help           显示此帮助信息并退出

参数:
  本地压缩包            可选的本地文件路径，格式必须为 sa-v{version}-{os}_{arch}.tar.gz
                        例如：sa-v3.0.0.27-linux_amd64.tar.gz
                        若提供此参数，脚本将跳过网络下载，直接使用该文件进行安装，
                        并自动备份当前已安装的目录（如果存在）。

描述:
  检测系统架构（如 linux_amd64），获取最新版本号，比较本地安装的 SA 版本
  （通过脚本同目录下的符号链接 sa2 指向的目录名提取，目录名格式为 sa-v{version}-{os}_{arch}）。
  若未指定本地压缩包：
    - 若本地版本低于最新版本，则自动下载并安装最新版本；
    - 若版本相同且未指定 -f，则直接退出（无需安装）；
    - 若指定 -f，则强制重新安装（会备份原目录后重新下载部署）。
  若指定了本地压缩包：
    - 脚本将验证文件格式和平台匹配性，通过后直接使用该文件进行安装，
    - 并自动备份当前已安装的目录（如果存在），然后解压、迁移、更新软链接。

  下载优先使用内网源 ${INNER_BASE_URL}，失败则切换至外网源 ${OUTER_BASE_URL}。

  安装过程中会：
    - 如果当前 sa2 指向的目录中存在 killall.sh，则执行它以停止正在运行的程序
    - 如果新版本目录中存在 sa_setup.sh，则执行配置迁移（从旧目录 conf 到新目录 conf）
    - 移动 data、log、tmp 目录（仅当非空时）
    - 最后更新符号链接 sa2 指向新版本目录
    - 如果是网络下载的压缩包，安装完成后自动删除下载的压缩包

  安装完成后，可选择将 sa2 目录（即符号链接指向的目录）添加到当前用户的 PATH 环境变量中，
  以便直接执行其中的命令，无需输入完整路径。此操作会修改用户的 shell 配置文件（如 ~/.bashrc）
  并立即在当前 shell 中生效（前提是使用 source 执行本脚本，否则需要手动执行 source）。

  配置文件和数据迁移后，旧版本目录（或备份）需手动清理。

  若指定 -y 选项，则上述所有交互式提示均自动回答 yes，且执行 sa_setup.sh 时会附加 -y 参数。
EOF
}

# 默认不自动确认，不强制安装
AUTO_YES=false
FORCE_INSTALL=false

# 参数解析：处理所有以 - 开头的选项
while [[ $# -gt 0 ]]; do
    case $1 in
        -f|--force-install)
            FORCE_INSTALL=true
            shift
            ;;
        -y|--yes)
            AUTO_YES=true
            shift
            ;;
        -v|--version)
            echo "install_sa2_exec.sh 版本 $SCRIPT_VERSION"
            exit 0
            ;;
        -h|--help)
            show_help
            exit 0
            ;;
        -*)
            error "未知选项: $1，使用 -h 查看帮助"
            ;;
        *)
            # 遇到非选项参数，停止解析
            break
            ;;
    esac
done

# 剩余的第一个参数作为本地压缩包（如果有）
LOCAL_ARCHIVE=""
if [[ $# -gt 0 ]]; then
    LOCAL_ARCHIVE="$1"
    if [[ $# -gt 1 ]]; then
        warn "忽略多余的参数: ${*:2}"
    fi
fi

# 检测下载工具（仅在需要下载时使用）
if command -v curl >/dev/null 2>&1; then
    DOWNLOADER="curl -fsSL"
elif command -v wget >/dev/null 2>&1; then
    DOWNLOADER="wget -qO-"
else
    error "请安装 curl 或 wget 以继续"
fi

# 下载函数（仅内部使用）
download() {
    local url=$1
    local output=${2:-}
    if [ -n "$output" ]; then
        if command -v curl >/dev/null 2>&1; then
            curl -fsSL -o "$output" "$url"
        else
            wget -qO "$output" "$url"
        fi
    else
        if command -v curl >/dev/null 2>&1; then
            curl -fsSL "$url"
        else
            wget -qO- "$url"
        fi
    fi
}

# 从镜像下载（优先内网，降级外网），返回内容或保存文件
fetch_from_mirrors() {
    local rel_path=$1
    local output=${2:-}
    local inner_url="${INNER_BASE_URL}${rel_path}"
    local outer_url="${OUTER_BASE_URL}${rel_path}"

    if [ -n "$output" ]; then
        if download "$inner_url" "$output" 2>/dev/null; then
            info "从内网下载成功: $inner_url"
            return 0
        fi
        warn "内网下载失败，尝试外网: $outer_url"
        if download "$outer_url" "$output"; then
            info "从外网下载成功: $outer_url"
            return 0
        fi
    else
        local content
        content=$(download "$inner_url" 2>/dev/null) && {
            info "从内网获取成功: $inner_url"
            echo "$content"
            return 0
        }
        warn "内网获取失败，尝试外网: $outer_url"
        content=$(download "$outer_url") && {
            info "从外网获取成功: $outer_url"
            echo "$content"
            return 0
        }
    fi
    error "从任何镜像都无法下载 $rel_path"
}

# 检测系统平台
OS=$(uname -s)
ARCH=$(uname -m)

case $OS in
    Linux)      os=linux ;;
    Darwin)     os=darwin ;;
    MINGW*|MSYS*|CYGWIN*) os=windows ;;
    *)          error "不支持的操作系统: $OS" ;;
esac

case $ARCH in
    x86_64)            arch=amd64 ;;
    arm64|aarch64)     arch=arm64 ;;
    armv7l|arm)        arch=arm ;;
    *)                 error "不支持的架构: $ARCH" ;;
esac

platform="${os}_${arch}"
info "当前平台: $platform"

# 切换到脚本所在目录
SCRIPT_DIR=$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)
cd "$SCRIPT_DIR"

# 检测本地版本：通过软链接 sa2 指向的目录名提取
detect_local_version() {
    local link_path="$SCRIPT_DIR/sa2"
    if [[ -L "$link_path" ]]; then
        local target
        target=$(readlink "$link_path")
        local dirname_target
        dirname_target=$(basename "$target")
        if [[ $dirname_target =~ sa-v([0-9]+\.[0-9]+\.[0-9]+\.[0-9]+)-${platform} ]]; then
            echo "${BASH_REMATCH[1]}"
        else
            warn "软链接 sa2 指向的目录名不符合预期格式（应为 sa-v{version}-${platform}），假定未安装"
            echo "0.0.0.0"
        fi
    else
        warn "未找到软链接 sa2，假定未安装"
        echo "0.0.0.0"
    fi
}

local_version=$(detect_local_version)
info "本地版本: $local_version"

# 版本比较函数
version_gt() {
    IFS=. read -r a1 a2 a3 a4 <<< "$1"
    IFS=. read -r b1 b2 b3 b4 <<< "$2"
    [ "$a1" -gt "$b1" ] && return 0
    [ "$a1" -lt "$b1" ] && return 1
    [ "$a2" -gt "$b2" ] && return 0
    [ "$a2" -lt "$b2" ] && return 1
    [ "$a3" -gt "$b3" ] && return 0
    [ "$a3" -lt "$b3" ] && return 1
    [ "$a4" -gt "$b4" ] && return 0
    return 1
}

# 处理本地压缩包（如果提供）
USING_LOCAL_ARCHIVE=false
target_version=""
target_dir=""
do_install=false
reinstall=false

if [[ -n "$LOCAL_ARCHIVE" ]]; then
    info "检测到本地压缩包: $LOCAL_ARCHIVE"
    if [[ ! -f "$LOCAL_ARCHIVE" ]]; then
        error "本地文件不存在: $LOCAL_ARCHIVE"
    fi
    if [[ ! -r "$LOCAL_ARCHIVE" ]]; then
        error "本地文件不可读: $LOCAL_ARCHIVE"
    fi

    # 从文件名提取版本和平台，格式: sa-v{version}-{os}_{arch}.tar.gz
    filename=$(basename "$LOCAL_ARCHIVE")
    if [[ ! "$filename" =~ ^sa-v([0-9]+\.[0-9]+\.[0-9]+\.[0-9]+)-([a-z]+_[a-z0-9]+)\.tar\.gz$ ]]; then
        error "本地压缩包文件名不符合预期格式: $filename (应为 sa-v{version}-{os}_{arch}.tar.gz)"
    fi
    target_version="${BASH_REMATCH[1]}"
    target_platform="${BASH_REMATCH[2]}"
    if [[ "$target_platform" != "$platform" ]]; then
        error "本地压缩包平台 ($target_platform) 与当前系统平台 ($platform) 不匹配"
    fi
    target_dir="sa-v${target_version}-${platform}"
    info "本地压缩包版本: $target_version, 平台: $target_platform"

    USING_LOCAL_ARCHIVE=true
    do_install=true
    # 如果本地已安装，则视为重新安装（备份原目录）
    if [[ "$local_version" != "0.0.0.0" ]]; then
        reinstall=true
    fi
else
    # 无本地压缩包，从网络获取最新版本
    info "获取最新版本号..."
    latest_version=$(fetch_from_mirrors "$VERSION_FILE" | tr -d '[:space:]')
    info "最新版本: $latest_version"

    if version_gt "$latest_version" "$local_version"; then
        info "发现新版本: $latest_version (当前: $local_version)"
        do_install=true
    else
        # 版本相同
        if $FORCE_INSTALL; then
            info "强制重新安装（由于 -f 选项）"
            reinstall=true
            do_install=true
        else
            info "当前已是最新版本: $local_version，无需下载。使用 -f 可强制重新安装。"
            exit 0
        fi
    fi
    target_version="$latest_version"
    target_dir="sa-v${target_version}-${platform}"
fi

# 如果 do_install 为 true，执行安装流程
if $do_install; then
    # 先获取当前 sa2 指向的旧目录路径（用于执行 killall.sh 和后续迁移）
    current_link_target=""
    current_dir_path=""
    if [[ -L "$SCRIPT_DIR/sa2" ]]; then
        current_link_target=$(readlink "$SCRIPT_DIR/sa2")
        # 处理相对路径
        if [[ "$current_link_target" = /* ]]; then
            current_dir_path="$current_link_target"
        else
            current_dir_path="$SCRIPT_DIR/$current_link_target"
        fi
        # 确保路径存在且是目录
        if [[ ! -d "$current_dir_path" ]]; then
            warn "当前 sa2 指向的目录不存在: $current_dir_path"
            current_dir_path=""
        fi
    fi

    # 执行 killall.sh 停止旧版本程序（如果存在）
    if [[ -n "$current_dir_path" && -f "$current_dir_path/killall.sh" ]]; then
        if [[ -x "$current_dir_path/killall.sh" ]]; then
            info "执行 $current_dir_path/killall.sh 以停止运行中的程序..."
            if "$current_dir_path/killall.sh"; then
                info "killall.sh 执行成功"
            else
                warn "killall.sh 执行失败（退出码 $?），但将继续安装"
            fi
        else
            warn "killall.sh 存在但不可执行，跳过"
        fi
    else
        info "未找到可执行的 killall.sh，跳过停止程序步骤"
    fi

    # 确定旧目录路径（用于后续迁移，基于 local_version）
    old_dir_path=""
    old_dir_name="sa-v${local_version}-${platform}"
    if [[ "$local_version" != "0.0.0.0" && -d "$old_dir_name" ]]; then
        old_dir_path="$SCRIPT_DIR/$old_dir_name"
    fi

    # 备份旧目录（如果是重新安装）
    backup_name=""
    if $reinstall && [[ -n "$old_dir_path" ]]; then
        backup_name="${old_dir_name}.bak-$(date +%Y%m%dT%H%M%S)"
        info "备份原目录: $old_dir_name -> $backup_name"
        mv "$old_dir_name" "$backup_name"
        # 更新旧目录路径为备份后的路径，以便迁移使用
        old_dir_path="$SCRIPT_DIR/$backup_name"
    fi

    # 下载或使用本地压缩包
    if $USING_LOCAL_ARCHIVE; then
        archive_file="$LOCAL_ARCHIVE"
        info "使用本地压缩包: $archive_file"
    else
        file_name="${target_dir}.tar.gz"
        rel_path="${target_version}/${file_name}"
        info "开始下载: $file_name"
        if ! fetch_from_mirrors "$rel_path" "$file_name"; then
            error "下载失败"
        fi
        info "下载完成: $SCRIPT_DIR/$file_name"
        archive_file="$file_name"
    fi

    # 解压到临时目录
    info "解压中..."
    tar -xzf "$archive_file" -C "$TMP_DIR"

    # 检查解压后是否得到预期的目录
    if [[ -d "$TMP_DIR/$target_dir" ]]; then
        # 标准情况：压缩包内包含同名目录
        mv "$TMP_DIR/$target_dir" "$SCRIPT_DIR/"
    else
        # 压缩包内没有顶层目录，直接是文件
        warn "压缩包解压后未找到目录 $target_dir，将创建目录并移入文件"
        mkdir -p "$SCRIPT_DIR/$target_dir"
        # 移动所有文件（包括隐藏文件）到目标目录，注意排除临时目录本身
        shopt -s dotglob
        for item in "$TMP_DIR"/*; do
            if [[ -e "$item" && "$item" != "$TMP_DIR/.." && "$item" != "$TMP_DIR/." ]]; then
                mv "$item" "$SCRIPT_DIR/$target_dir/"
            fi
        done
        shopt -u dotglob
    fi

    # 确保新目录存在
    new_dir="$SCRIPT_DIR/$target_dir"
    if [[ ! -d "$new_dir" ]]; then
        error "新目录 $new_dir 未成功创建"
    fi

    # 给可执行权限（假设目录内所有文件都可能需要执行权限）
    chmod -R +x "$new_dir" 2>/dev/null || true
    info "解压完成，目录已就绪: $target_dir"

    # 如果是网络下载的压缩包，删除下载的文件
    if ! $USING_LOCAL_ARCHIVE && [[ -f "$archive_file" ]]; then
        rm -f "$archive_file"
        info "已删除下载的压缩包: $archive_file"
    fi

    # ---------- 迁移步骤 ----------
    if [[ -n "$old_dir_path" && -d "$old_dir_path" ]]; then
        info "开始从旧目录迁移配置和数据: $old_dir_path"

        # 1. 执行 sa_setup.sh 配置迁移
        setup_script="$new_dir/sa_setup.sh"
        if [[ -f "$setup_script" ]] && [[ -x "$setup_script" ]]; then
            src_conf="$old_dir_path/conf"
            dst_conf="$new_dir/conf"
            if [[ -d "$src_conf" ]]; then
                # 构建命令，如果 AUTO_YES 为 true，则附加 -y 参数
                setup_cmd=("$setup_script" --params-src "$src_conf" --params-dst "$dst_conf")
                if $AUTO_YES; then
                    setup_cmd+=("-y")
                fi
                info "执行配置迁移: ${setup_cmd[*]}"
                if "${setup_cmd[@]}"; then
                    info "配置迁移成功"
                else
                    warn "配置迁移失败，请手动检查"
                fi
            else
                warn "旧目录中不存在 conf 目录，跳过配置迁移"
            fi
        else
            info "新目录中未找到可执行的 sa_setup.sh，跳过配置迁移"
        fi

        # 2. 移动 data、log、tmp 目录（仅当非空）
        for sub in data log tmp; do
            src_sub="$old_dir_path/$sub"
            dst_sub="$new_dir/$sub"
            if [[ -d "$src_sub" ]]; then
                # 检查目录是否非空
                if [[ -n "$(ls -A "$src_sub" 2>/dev/null)" ]]; then
                    info "移动非空目录 $sub 到新目录"
                    # 如果目标目录已存在，先备份？根据要求直接移动覆盖
                    if [[ -e "$dst_sub" ]]; then
                        warn "目标目录 $dst_sub 已存在，将被覆盖"
                        rm -rf "$dst_sub"
                    fi
                    mv "$src_sub" "$new_dir/"
                    info "移动 $sub 完成"
                else
                    info "旧目录下的 $sub 为空，跳过移动"
                fi
            else
                info "旧目录下不存在 $sub 目录，跳过"
            fi
        done
    else
        info "未找到旧目录，跳过配置和数据迁移"
    fi

    # 更新软链接 sa2
    ln -snf "$target_dir" sa2
    info "软链接已更新: sa2 -> $target_dir"

    # 提示信息
    if $reinstall; then
        echo -e "${YELLOW}提示：旧版本目录已备份为 $backup_name。若需恢复配置文件，请手动从备份中复制所需文件至新目录 $target_dir。${NC}"
    else
        if [[ -n "$old_dir_path" && -d "$old_dir_path" ]]; then
            echo -e "${YELLOW}提示：旧版本目录仍然保留：$old_dir_name，如需清理可手动删除。配置文件和数据已迁移至新目录。${NC}"
        fi
    fi

    # ---------- 可选：将 sa2 目录添加到 PATH ----------
    # 如果指定了 -y，则自动添加 PATH
    add_path=false
    if $AUTO_YES; then
        info "自动将 sa2 目录添加到 PATH（由于 -y 选项）"
        add_path=true
    else
        echo
        read -p "是否将 sa2 目录添加到当前用户的 PATH 中，以便直接执行其中的命令？(y/N) " -n 1 -r
        echo
        if [[ $REPLY =~ ^[Yy]$ ]]; then
            add_path=true
        fi
    fi

    if $add_path; then
        # 获取当前实际用户（考虑 sudo 场景）
        target_user="${SUDO_USER:-$USER}"
        target_home=$(eval echo "~$target_user")
        # 检测 shell 配置文件
        shell_config=""
        if [[ -f "$target_home/.bashrc" ]]; then
            shell_config="$target_home/.bashrc"
        elif [[ -f "$target_home/.zshrc" ]]; then
            shell_config="$target_home/.zshrc"
        elif [[ -f "$target_home/.profile" ]]; then
            shell_config="$target_home/.profile"
        else
            warn "无法确定 shell 配置文件，请手动将以下行添加到您的 shell 配置文件中："
            echo "export PATH=\"$SCRIPT_DIR/sa2:\$PATH\""
        fi

        if [[ -n "$shell_config" ]]; then
            # 检查是否已添加过
            if grep -q "export PATH=.*$SCRIPT_DIR/sa2" "$shell_config"; then
                info "PATH 中已包含 sa2 目录，无需重复添加"
            else
                # 追加配置
                echo "" >> "$shell_config"
                echo "# Added by install_sa2_exec.sh on $(date)" >> "$shell_config"
                echo "export PATH=\"$SCRIPT_DIR/sa2:\$PATH\"" >> "$shell_config"
                info "已将 sa2 目录添加到 $shell_config"
                # 立即 source 使配置在当前 shell 生效（如果脚本是被 source 执行的）
                if [[ "${BASH_SOURCE[0]}" != "${0}" ]]; then
                    # 脚本是被 source 的，可以直接 source 配置文件
                    # shellcheck source=/dev/null
                    source "$shell_config"
                    info "已执行 source $shell_config，PATH 已更新"
                else
                    # 脚本是作为子进程执行的，无法影响父 shell，提示用户手动 source
                    echo -e "${YELLOW}请执行 'source $shell_config' 或重新登录以使 PATH 生效。${NC}"
                fi
            fi
        fi
        # 即使配置文件中已存在，也尝试在当前会话中临时添加 PATH，方便后续命令使用
        export PATH="$SCRIPT_DIR/sa2:$PATH"
        info "已在当前脚本会话中临时添加 sa2 到 PATH"
    else
        info "跳过 PATH 配置。您也可以手动将以下行添加到 shell 配置文件中："
        echo "export PATH=\"$SCRIPT_DIR/sa2:\$PATH\""
    fi
fi

info "安装完毕"
