#!/usr/bin/env bash

set -Eeuo pipefail

# =================配置参数=================
DEFAULT_MAX_DEPTH=3
DEFAULT_IGNORE_DIR="compose-ignore"
# =========================================

# 变量初始化
: "${MAX_DEPTH:=$DEFAULT_MAX_DEPTH}"
: "${IGNORE_DIR:=$DEFAULT_IGNORE_DIR}"
COMPOSE_CMD=""
SCRIPT_START_TIME=$(date +%s)

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

# 状态图标
ICON_SUCCESS="✅"
ICON_FAIL="❌"
ICON_WARN="⚠️"
ICON_INFO="ℹ️"
ICON_PKG="📦"
ICON_TIME="⏱️"

# 捕获中断信号
trap 'echo -e "\n${RED}[!] 用户取消操作，正在退出...${NC}"; exit 1' SIGINT SIGTERM

# 辅助函数：打印带颜色的消息
print_info() { echo -e "${BLUE}${ICON_INFO} [INFO]${NC} $1"; }
print_success() { echo -e "${GREEN}${ICON_SUCCESS} [SUCCESS]${NC} $1"; }
print_warning() { echo -e "${YELLOW}${ICON_WARN} [WARNING]${NC} $1"; }
print_error() { echo -e "${RED}${ICON_FAIL} [ERROR]${NC} $1"; }
print_header() { echo -e "\n${MAGENTA}${BOLD}$1${NC}"; }
print_line() { echo -e "${CYAN}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${NC}"; }

# 显示帮助信息
show_help() {
    print_header "🐳 Docker Compose 自动管理工具"
    echo -e "${BOLD}用法:${NC}"
    echo "  $0 [命令]"
    echo ""
    echo -e "${BOLD}命令:${NC}"
    printf "  %-15s - %s\n" "update" "更新所有服务 (pull + down + up)"
    printf "  %-15s - %s\n" "down" "停止所有服务"
    printf "  %-15s - %s\n" "up" "启动所有服务 (up -d)"
    printf "  %-15s - %s\n" "list" "列出所有compose文件"
    printf "  %-15s - %s\n" "help" "显示此帮助信息"
    printf "  %-15s - %s\n" "alias-setup" "配置别名 'cpman' 以简化调用"
    printf "  %-15s - %s\n" "(无参数)" "进入交互式菜单"
    echo ""
    echo -e "${BOLD}环境变量:${NC}"
    echo "  MAX_DEPTH=N      - 设置搜索深度(默认:$DEFAULT_MAX_DEPTH)"
    echo "  IGNORE_DIR=name  - 设置忽略目录(默认:$DEFAULT_IGNORE_DIR)"
    echo ""
    echo -e "${BOLD}示例:${NC}"
    local script_url="https://www.mgrsc.top/http/linux/docker/cpman/cpman.sh"
    echo "  # 交互模式"
    echo "  bash <(curl -fsSL $script_url)"
    echo ""
    echo "  # 直接更新"
    echo "  bash <(curl -fsSL $script_url) update"
    echo ""
    echo "  # 自定义深度和忽略目录"
    echo "  MAX_DEPTH=5 bash <(curl -fsSL $script_url) update"
    echo ""
    echo "  # 配置别名"
    echo "  bash <(curl -fsSL $script_url) alias-setup"
    echo ""
}

# 计算耗时
format_time() {
    local duration=$1
    if ((duration < 60)); then
        echo "${duration}s"
    else
        echo "$((duration / 60))m $((duration % 60))s"
    fi
}

# 检测 Compose 命令
detect_compose_command() {
    # 优先检测 podman compose（因为 podman 可能创建 docker alias）
    if command -v podman &>/dev/null && podman compose version &>/dev/null; then
        COMPOSE_CMD="podman compose"
    elif command -v docker &>/dev/null && docker compose version &>/dev/null; then
        COMPOSE_CMD="docker compose"
    elif command -v docker-compose &>/dev/null; then
        COMPOSE_CMD="docker-compose"
    else
        print_error "未找到可用的 Compose 命令 (podman compose / docker compose / docker-compose)"
        exit 1
    fi
    # 可选：显示检测结果便于调试
    # print_info "使用: $COMPOSE_CMD"
}

# 查找 Compose 文件
find_compose_files() {
    local search_dir="${1:-.}"
    local -n result=$2

    print_info "正在搜索 Compose 文件 (深度: $MAX_DEPTH)..."

    # 使用 mapfile 提高效率 (Bash 4.0+)
    # 排除 IGNORE_DIR
    if mapfile -d $'\0' result < <(find "$search_dir" -maxdepth "$MAX_DEPTH" -type f \
        \( -name "compose.yaml" -o -name "compose.yml" -o \
        -name "docker-compose.yaml" -o -name "docker-compose.yml" \) \
        -not -path "*/$IGNORE_DIR/*" -print0 2>/dev/null); then
        : # Success
    else
        # Fallback for older bash
        while IFS= read -r -d '' file; do
            result+=("$file")
        done < <(find "$search_dir" -maxdepth "$MAX_DEPTH" -type f \
            \( -name "compose.yaml" -o -name "compose.yml" -o \
            -name "docker-compose.yaml" -o -name "docker-compose.yml" \) \
            -not -path "*/$IGNORE_DIR/*" -print0 2>/dev/null)
    fi

    if [ ${#result[@]} -eq 0 ]; then
        print_warning "未找到任何 Compose 文件"
        return 0
    fi

    print_success "找到 ${#result[@]} 个服务配置文件"
}

# 显示文件列表
show_files() {
    local -n files=$1
    print_header "📋 服务列表:"
    local idx=1
    for file in "${files[@]}"; do
        printf "  ${CYAN}%-3s${NC} %s\n" "$idx." "$file"
        ((idx++))
    done
    echo ""
}

# 新增函数：从 compose 文件获取镜像列表并计算唯一指纹（RepoDigest 优先，fallback Image ID）
get_compose_image_fingerprints() {
    local compose_file=$1

    local result=""
    local tmpfile=$(mktemp)

    # 使用 docker compose config --images 获取所有定义的镜像名（不依赖运行状态）
    $COMPOSE_CMD -f "$compose_file" config --images 2>/dev/null | sort -u >"$tmpfile"

    while IFS= read -r img; do
        if [ -n "$img" ] && [ "$img" != "<none>" ]; then
            # 优先取 RepoDigests（内容指纹，最准确）
            local digest=$(docker image inspect --format '{{index .RepoDigests 0}}' "$img" 2>/dev/null || echo "")

            if [ -n "$digest" ]; then
                result="$result$img@$digest\n"
            else
                # fallback 到 Image ID（pull 新镜像后一定变化）
                local img_id=$(docker image inspect --format '{{.Id}}' "$img" 2>/dev/null || echo "sha256:unknown")
                result="$result$img@id:$img_id\n"
            fi
        fi
    done <"$tmpfile"

    rm -f "$tmpfile"

    # 排序去重，确保比较稳定
    echo -e "$result" | sort -u
}

# 核心：执行更新
update_all() {
    local -n files=$1
    local -a report_success=()
    local -a report_skipped=()
    local -a report_failed=()
    local total=${#files[@]}
    local current=1

    print_header "🚀 开始更新所有服务..."

    for file in "${files[@]}"; do
        local start_t
        start_t=$(date +%s)
        local dir
        dir=$(dirname "$file")
        local filename
        filename=$(basename "$file")

        print_line
        echo -e "${BOLD}${ICON_PKG} [$current/$total] 正在处理: ${BLUE}$dir${NC}"

        local step_failed=false
        local err_msg=""
        local has_updates=false

        # 0. 记录 pull 前的镜像指纹（保持不变）
        local before_fingerprints
        before_fingerprints=$(cd "$dir" && get_compose_image_fingerprints "$filename")

        # 1. Pull：新增超时 + 重试机制（最多重试 3 次，每次超时 15 分钟）
        echo -n "  🔹 拉取镜像... "
        local pull_success=false
        local max_retries=3
        local retry_delay=10   # 秒，重试前等待时间
        local pull_timeout=900 # 15 分钟（900 秒），平衡值：够大多数大镜像完成，又不无限卡死

        local full_output=""
        for ((attempt = 1; attempt <= max_retries; attempt++)); do
            if output=$(timeout "$pull_timeout" bash -c "cd \"$dir\" && $COMPOSE_CMD -f \"$filename\" pull 2>&1 < /dev/null"); then
                echo -e "${GREEN}完成${NC}"
                pull_success=true
                break
            else
                local exit_code=$?
                if [[ $exit_code -eq 124 ]]; then
                    echo -e "${YELLOW}超时（${pull_timeout}s）${NC}"
                else
                    echo -e "${RED}失败（第 $attempt 次）${NC}"
                fi

                if [[ $attempt -lt $max_retries ]]; then
                    echo -e "  ${CYAN}ℹ️  ${retry_delay} 秒后重试...${NC}"
                    sleep "$retry_delay"
                fi

                # 累计错误日志
                full_output+="$output\n"
            fi
        done

        if [ "$pull_success" = false ]; then
            echo -e "${RED}拉取最终失败${NC}"
            echo -e "${YELLOW}--- 错误日志 ---${NC}"
            echo -e "$full_output"
            echo -e "${YELLOW}----------------${NC}"
            step_failed=true
            err_msg="镜像拉取失败（已重试 $max_retries 次）"
        fi

        # 2. 检测是否有内容更新（保持不变，仅在 pull 成功时执行）
        if [ "$step_failed" = false ]; then
            local after_fingerprints
            after_fingerprints=$(cd "$dir" && get_compose_image_fingerprints "$filename")

            if [ "$before_fingerprints" = "$after_fingerprints" ]; then
                echo -e "  ${CYAN}ℹ️  镜像内容无变化，指纹相同，跳过重启${NC}"
                has_updates=false
            else
                echo -e "  ${YELLOW}📦 检测到镜像内容更新，指纹变更${NC}"
                has_updates=true
            fi
        fi

        # 3. Down (仅在有更新时执行)
        if [ "$step_failed" = false ] && [ "$has_updates" = true ]; then
            echo -n "  🔹 停止服务... "
            if (cd "$dir" && $COMPOSE_CMD -f "$filename" down 2>&1 </dev/null) >/dev/null; then
                echo -e "${GREEN}完成${NC}"
            else
                echo -e "${RED}失败${NC}"
                step_failed=true
                err_msg="停止服务失败"
            fi
        fi

        # 4. Up (仅在有更新时执行)
        if [ "$step_failed" = false ] && [ "$has_updates" = true ]; then
            echo -n "  🔹 启动服务... "
            if (cd "$dir" && $COMPOSE_CMD -f "$filename" up -d 2>&1 </dev/null) >/dev/null; then
                echo -e "${GREEN}完成${NC}"
            else
                echo -e "${RED}失败${NC}"
                step_failed=true
                err_msg="启动服务失败"
            fi
        fi

        local end_t
        end_t=$(date +%s)
        local duration=$((end_t - start_t))

        if [ "$step_failed" = true ]; then
            report_failed+=("$dir - $err_msg")
        elif [ "$has_updates" = true ]; then
            report_success+=("$dir ($(format_time $duration))")
        else
            report_skipped+=("$dir")
        fi

        ((current++))
    done

    print_summary_v2 report_success report_skipped report_failed
}

# 停止所有
down_all() {
    local -n files=$1
    local -a report_success=()
    local -a report_failed=()

    print_header "🛑 开始停止所有服务..."

    for file in "${files[@]}"; do
        local dir
        dir=$(dirname "$file")
        local filename
        filename=$(basename "$file")

        echo -n "  🛑 停止 $dir ... "
        if (cd "$dir" && $COMPOSE_CMD -f "$filename" down 2>&1 </dev/null) >/dev/null; then
            echo -e "${GREEN}成功${NC}"
            report_success+=("$dir")
        else
            echo -e "${RED}失败${NC}"
            report_failed+=("$dir")
        fi
    done

    # 汇总
    echo ""
    print_line
    echo -e "${BOLD}停止完成:${NC} ${GREEN}${ICON_SUCCESS} ${#report_success[@]}${NC} | ${RED}${ICON_FAIL} ${#report_failed[@]}${NC}"
    print_line
    echo ""
}

# 启动所有
up_all() {
    local -n files=$1
    local -a report_success=()
    local -a report_failed=()

    print_header "🚀 开始启动所有服务..."

    for file in "${files[@]}"; do
        local dir
        dir=$(dirname "$file")
        local filename
        filename=$(basename "$file")

        echo -n "  🚀 启动 $dir ... "
        if (cd "$dir" && $COMPOSE_CMD -f "$filename" up -d 2>&1 </dev/null) >/dev/null; then
            echo -e "${GREEN}成功${NC}"
            report_success+=("$dir")
        else
            echo -e "${RED}失败${NC}"
            report_failed+=("$dir")
        fi
    done

    # 汇总
    echo ""
    print_line
    echo -e "${BOLD}启动完成:${NC} ${GREEN}${ICON_SUCCESS} ${#report_success[@]}${NC} | ${RED}${ICON_FAIL} ${#report_failed[@]}${NC}"
    print_line
    echo ""
}

# 打印精美汇总表（支持跳过状态）
print_summary_v2() {
    local -n success=$1
    local -n skipped=$2
    local -n failed=$3
    local total_time=$(($(date +%s) - SCRIPT_START_TIME))

    echo ""
    print_header "📊 执行报告"
    print_line
    printf "${BOLD}%-50s %-15s${NC}\n" "服务路径" "状态"
    print_line

    for item in "${success[@]}"; do
        printf "%-50s ${GREEN}✅ 已更新${NC}\n" "$item"
    done

    for item in "${skipped[@]}"; do
        printf "%-50s ${CYAN}⏭️  无变化${NC}\n" "$item"
    done

    for item in "${failed[@]}"; do
        printf "%-50s ${RED}❌ 失败${NC}\n" "$item"
    done

    print_line
    echo -e "${BOLD}总耗时:${NC} ${ICON_TIME} ${YELLOW}$(format_time $total_time)${NC} | ${BOLD}更新:${NC} ${GREEN}${#success[@]}${NC} | ${BOLD}跳过:${NC} ${CYAN}${#skipped[@]}${NC} | ${BOLD}失败:${NC} ${RED}${#failed[@]}${NC}"
    print_line
    echo ""
}

# 配置别名
setup_alias() {
    local alias_name="cpman"
    local script_url="https://www.mgrsc.top/http/linux/docker/cpman/cpman.sh"
    local alias_cmd="alias $alias_name='bash <(curl -fsSL $script_url)'"
    local config_file=""

    print_header "⚙️ 配置别名: $alias_name"

    # 1. 确定 Shell 配置文件
    if [[ "$SHELL" =~ "zsh" ]]; then
        config_file="$HOME/.zshrc"
    elif [[ "$SHELL" =~ "bash" ]]; then
        # 如果存在 .bash_profile 且 .bashrc 不存在，则使用 .bash_profile
        if [ -f "$HOME/.bash_profile" ] && [ ! -f "$HOME/.bashrc" ]; then
            config_file="$HOME/.bash_profile"
        else
            config_file="$HOME/.bashrc"
        fi
    else
        print_error "不支持或无法识别您的 Shell ($SHELL)。请手动将别名添加到您的配置文件中。"
        echo "别名命令: $alias_cmd"
        return 1
    fi

    print_info "检测到 Shell: $SHELL"
    print_info "目标配置文件: $config_file"

    if [ ! -f "$config_file" ]; then
        print_warning "配置文件 $config_file 不存在，将创建它。"
        touch "$config_file"
    fi

    # 2. 检查是否已存在别名
    if grep -q "alias $alias_name=" "$config_file"; then
        # 如果存在，则更新
        sed -i "/alias $alias_name=/c\\$alias_cmd" "$config_file"
        print_success "别名 '$alias_name' 已在 $config_file 中更新。"
    else
        # 如果不存在，则追加
        echo -e "\n# Docker Compose Manager Alias" >>"$config_file"
        echo "$alias_cmd" >>"$config_file"
        print_success "别名 '$alias_name' 已添加到 $config_file。"
    fi

    print_line
    print_info "请执行以下命令使别名立即生效:"
    echo -e "  ${YELLOW}source $config_file${NC}"
    print_line
    echo ""

    return 0
}

# 菜单
show_menu() {
    print_header "🐳 Docker Compose 批量管理工具"
    echo "1. 🔄 更新 (Pull -> Down -> Up)"
    echo "2. 🛑 停止 (Down)"
    echo "3. 🚀 启动 (Up -d)"
    echo "4. 🔍 重新扫描"
    echo "5. 🚪 退出"
    echo -n "请输入选项 [1-5]: "
}

# 主逻辑
main() {
    local compose_files=()
    local command="${1:-interactive}"

    # 1. 立即处理非操作命令 (help, alias-setup)
    case "$command" in
    help)
        show_help
        exit 0
        ;;
    alias-setup)
        setup_alias
        exit 0
        ;;
    *)
        # 对于需要操作的命令，继续执行初始化
        ;;
    esac

    # 2. 检查 Docker Compose 命令
    detect_compose_command

    # 3. 查找 Compose 文件
    find_compose_files "." compose_files

    if [ ${#compose_files[@]} -eq 0 ] && [[ "$command" != "interactive" ]]; then
        print_warning "未找到任何 Compose 文件，且未进入交互模式，脚本退出。"
        exit 0
    fi

    # 4. 执行操作命令
    case "$command" in
    update)
        show_files compose_files
        update_all compose_files
        ;;
    down)
        show_files compose_files
        down_all compose_files
        ;;
    up)
        show_files compose_files
        up_all compose_files
        ;;
    list)
        show_files compose_files
        ;;
    interactive)
        show_files compose_files
        while true; do
            show_menu
            read -r choice
            case $choice in
            1) update_all compose_files ;;
            2) down_all compose_files ;;
            3) up_all compose_files ;;
            4)
                compose_files=()
                find_compose_files "." compose_files
                show_files compose_files
                ;;
            5) exit 0 ;;
            *) print_error "无效输入" ;;
            esac
        done
        ;;
    *)
        print_error "无效命令: $command"
        echo "请使用 '$0 help' 查看完整用法。"
        exit 1
        ;;
    esac
}

main "$@"
