#!/usr/bin/env bash
set -euo pipefail

project_root="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
frontend_pid=""
backend_pid=""
cleanup_done=0
run_for_seconds="${ZBT_DEV_RUN_SECONDS:-0}"

die() {
    echo "错误：$*" >&2
    exit 1
}

require_command() {
    local command_name="$1"
    command -v "$command_name" >/dev/null 2>&1 || \
        die "未找到 $command_name，请先安装并确保它位于 PATH 中。"
}

assert_port_available() {
    local port="$1"
    local application_name="$2"
    local listeners

    listeners="$(lsof -nP -iTCP:"$port" -sTCP:LISTEN 2>/dev/null || true)"
    if [[ -n "$listeners" ]]; then
        echo "$listeners" >&2
        die "$application_name 端口 $port 已被占用，请先结束上方进程后重试。"
    fi
}

process_group_is_alive() {
    local group_id="$1"
    [[ -n "$group_id" ]] && kill -0 -- "-$group_id" 2>/dev/null
}

stop_process_group() {
    local group_id="$1"
    if process_group_is_alive "$group_id"; then
        kill -TERM -- "-$group_id" 2>/dev/null || true
    fi
}

cleanup() {
    local exit_status=$?
    local deadline

    if (( cleanup_done )); then
        return "$exit_status"
    fi
    cleanup_done=1
    trap - INT TERM HUP

    if [[ -n "$frontend_pid" || -n "$backend_pid" ]]; then
        echo
        echo "正在停止智保通开发服务..."
    fi

    stop_process_group "$frontend_pid"
    stop_process_group "$backend_pid"

    deadline=$((SECONDS + 5))
    while (( SECONDS < deadline )); do
        if ! process_group_is_alive "$frontend_pid" && \
            ! process_group_is_alive "$backend_pid"; then
            break
        fi
        sleep 0.2
    done

    if process_group_is_alive "$frontend_pid"; then
        kill -KILL -- "-$frontend_pid" 2>/dev/null || true
    fi
    if process_group_is_alive "$backend_pid"; then
        kill -KILL -- "-$backend_pid" 2>/dev/null || true
    fi

    if [[ -n "$frontend_pid" ]]; then
        wait "$frontend_pid" 2>/dev/null || true
    fi
    if [[ -n "$backend_pid" ]]; then
        wait "$backend_pid" 2>/dev/null || true
    fi

    if [[ -n "$frontend_pid" || -n "$backend_pid" ]]; then
        echo "智保通开发服务已停止。"
    fi

    return "$exit_status"
}

http_ready() {
    local url="$1"
    curl --fail --silent --show-error --max-time 2 "$url" >/dev/null 2>&1
}

wait_for_services() {
    local deadline=$((SECONDS + 45))

    while (( SECONDS < deadline )); do
        kill -0 "$frontend_pid" 2>/dev/null || \
            die "前端开发服务器启动失败，请检查上方 pnpm 输出。"
        kill -0 "$backend_pid" 2>/dev/null || \
            die "后端开发服务器启动失败，请检查上方 Uvicorn 输出。"

        if http_ready "http://127.0.0.1:5173/" && \
            http_ready "http://127.0.0.1:5174/" && \
            http_ready "http://127.0.0.1:8000/docs"; then
            return 0
        fi

        sleep 0.3
    done

    die "开发服务在 45 秒内未全部就绪，请检查上方启动日志。"
}

case "$run_for_seconds" in
    ''|*[!0-9]*)
        die "ZBT_DEV_RUN_SECONDS 必须是非负整数。"
        ;;
esac

trap cleanup EXIT
trap 'exit 130' INT
trap 'exit 143' TERM
trap 'exit 129' HUP

require_command pnpm
require_command uv
require_command curl
require_command lsof

assert_port_available 5173 "H5"
assert_port_available 5174 "管理后台"
assert_port_available 8000 "后端 API"

echo "正在启动 H5、管理后台和后端 API..."

# Bash 3.2（macOS 系统自带版本）没有 setsid。临时启用 job control，
# 可让两个后台任务各自成为独立进程组，清理时连同 Vite/Uvicorn 子进程一起结束。
set -m
(
    cd "$project_root/frontend"
    exec pnpm dev
) &
frontend_pid=$!

(
    cd "$project_root/backend"
    exec uv run uvicorn zbt.main:app \
        --host 127.0.0.1 \
        --port 8000 \
        --reload
) &
backend_pid=$!
set +m

wait_for_services

echo
echo "智保通开发服务已全部启动："
echo "H5：http://127.0.0.1:5173"
echo "管理后台：http://127.0.0.1:5174"
echo "后端 API：http://127.0.0.1:8000/docs"
echo "按 Ctrl+C 可一次性停止全部服务。"

ready_at=$SECONDS
while true; do
    kill -0 "$frontend_pid" 2>/dev/null || \
        die "前端开发服务器意外退出，请检查上方日志。"
    kill -0 "$backend_pid" 2>/dev/null || \
        die "后端开发服务器意外退出，请检查上方日志。"

    if (( run_for_seconds > 0 && SECONDS - ready_at >= run_for_seconds )); then
        break
    fi

    sleep 0.5
done
