#!/bin/sh
# Post-receive hook: push 后自动 build + start

# pnpm 环境变量
export NVM_DIR="$HOME/.nvm"
[ -s "$NVM_DIR/nvm.sh" ] && \. "$NVM_DIR/nvm.sh"  # This loads nvm
[ -s "$NVM_DIR/bash_completion" ] && \. "$NVM_DIR/bash_completion"  # This loads nvm bash_completion

# 全局变量统一声明。具体值由各步骤函数写入。
REPO_TOPLEVEL=""
LOG_FILE=""
CURRENT_BRANCH=""
START_PORT=""
PM2_APP_NAME=""
BUILD_EXIT=0
STOP_WAIT_SECONDS=3
STOP_VERIFY_RETRIES=3

resolve_repo_context() {
    GIT_DIR_ABS="$(git rev-parse --absolute-git-dir 2>/dev/null)"
    if [ -n "$GIT_DIR_ABS" ] && [ -d "$GIT_DIR_ABS" ]; then
        REPO_TOPLEVEL="$(cd "$GIT_DIR_ABS/.." && pwd)"
        export GIT_DIR="$GIT_DIR_ABS"
    else
        REPO_TOPLEVEL="$(git rev-parse --show-toplevel 2>/dev/null || echo "$PWD")"
    fi
    unset GIT_WORK_TREE
    cd "$REPO_TOPLEVEL" || { echo "FATAL: cannot cd to $REPO_TOPLEVEL" >&2; exit 1; }
    echo "=== REPO_TOPLEVEL=$REPO_TOPLEVEL (GIT_DIR=$GIT_DIR) ===" >&2
}

init_log_file() {
    LOG_FILE="$REPO_TOPLEVEL/logs/post-receive.log"
    mkdir -p "$(dirname "$LOG_FILE")"
}

write_metadata_header() {
    {
        echo "=== post-receive @ $(date +%Y-%m-%dT%H:%M:%S%z) ==="
        echo "branch:  $(git rev-parse --abbrev-ref HEAD)"
        echo "commit:  $(git rev-parse --short HEAD)"
        echo "cwd:     $(pwd)"
        echo ""
    } > "$LOG_FILE"
}

sync_worktree() {
    echo "=== sync_worktree: git reset --hard HEAD @ $(date +%Y-%m-%dT%H:%M:%S%z) ==="
    git reset --hard HEAD
}

copy_env_by_branch() {
    CURRENT_BRANCH=$(git rev-parse --abbrev-ref HEAD)
    echo "=== branch: $CURRENT_BRANCH ===" >> "$LOG_FILE"
    case "$CURRENT_BRANCH" in
        main)
            cp -f env-example/.env.production.example .env.production
            echo "=== env: copied .env.production (main → prod) ===" >> "$LOG_FILE"
            ;;
        test)
            cp -f env-example/.env.local.example .env.local
            echo "=== env: copied .env.local (test → test) ===" >> "$LOG_FILE"
            ;;
        dev)
            cp -f env-example/.env.development.example .env.local
            echo "=== env: copied .env.local (dev → dev) ===" >> "$LOG_FILE"
            ;;
        *)
            echo "=== env: UNKNOWN branch '$CURRENT_BRANCH', skip env copy ===" >> "$LOG_FILE"
            ;;
    esac
}

# ============================================================
# 4. select_port_by_branch —— 根据分支选择 next start 端口
#   main 分支 → 9185
#   test 分支 → 9135
#   dev 分支  → 9135
# ============================================================
select_port_by_branch() {
    case "$CURRENT_BRANCH" in
        main)
            START_PORT=9185
            PM2_APP_NAME="cozsweet-main"
            ;;
        test|dev)
            START_PORT=9135
            PM2_APP_NAME="cozsweet-$CURRENT_BRANCH"
            ;;
        *)
            START_PORT=9135
            PM2_APP_NAME="cozsweet-$CURRENT_BRANCH"
            ;;
    esac
    export START_PORT
    export PM2_APP_NAME
    echo "=== start port: $START_PORT ===" >> "$LOG_FILE"
    echo "=== pm2 app: $PM2_APP_NAME ===" >> "$LOG_FILE"
}

# ============================================================
# 5. run_build —— 跑 `pnpm install` + `pnpm run build`，输出丢，捕获退出码写日志
#   返回：build 退出码（main 用 if ! run_build 决定是否继续）
# ============================================================
run_build() {
    echo "=== build START @ $(date +%Y-%m-%dT%H:%M:%S%z) ===" >> "$LOG_FILE"
    pnpm run build:deploy > /dev/null 2>&1
    BUILD_EXIT=$?
    echo "=== build EXIT_CODE=$BUILD_EXIT @ $(date +%Y-%m-%dT%H:%M:%S%z) ===" >> "$LOG_FILE"
    echo "" >> "$LOG_FILE"
    return $BUILD_EXIT
}

# ============================================================
# 6. stop_existing_next —— 强杀当前分支端口上的旧进程
#   1) 使用 ss 查找占用 START_PORT 的进程
#   2) 直接 SIGKILL，避免旧服务 drain 太久导致新服务无法绑定端口
#   3) 等待 STOP_WAIT_SECONDS，让系统释放端口
#   4) 循环复查端口占用并再次强杀残留
# ============================================================
find_port_pids() {
    if ! command -v ss >/dev/null 2>&1; then
        echo "=== ss command not found; cannot inspect port $START_PORT ===" >> "$LOG_FILE"
        return 0
    fi

    ss -tulnp 2>/dev/null \
        | awk -v port=":$START_PORT" '$5 ~ port "$" { print }' \
        | sed -n 's/.*pid=\([0-9][0-9]*\).*/\1/p' \
        | sort -u
}

force_kill_pids() {
    PIDS_TO_KILL="$1"
    if [ -z "$PIDS_TO_KILL" ]; then
        return 0
    fi
    echo "=== force kill PIDS: $PIDS_TO_KILL ===" >> "$LOG_FILE"
    kill -9 $PIDS_TO_KILL 2>/dev/null || true
}

wait_for_port_release() {
    RETRY=0
    while [ "$RETRY" -lt "$STOP_VERIFY_RETRIES" ]; do
        REMAINING_PIDS="$(find_port_pids)"
        if [ -z "$REMAINING_PIDS" ]; then
            echo "=== port $START_PORT released ===" >> "$LOG_FILE"
            return 0
        fi

        echo "=== port $START_PORT still busy, retry=$RETRY, pids=$REMAINING_PIDS ===" >> "$LOG_FILE"
        force_kill_pids "$REMAINING_PIDS"
        sleep "$STOP_WAIT_SECONDS"
        RETRY=$((RETRY + 1))
    done

    REMAINING_PIDS="$(find_port_pids)"
    if [ -n "$REMAINING_PIDS" ]; then
        echo "=== stop FAILED: port $START_PORT still occupied by PIDS: $REMAINING_PIDS ===" >> "$LOG_FILE"
        return 1
    fi

    echo "=== port $START_PORT released ===" >> "$LOG_FILE"
    return 0
}

stop_existing_next() {
    echo "=== stop existing next on port $START_PORT @ $(date +%Y-%m-%dT%H:%M:%S%z) ===" >> "$LOG_FILE"
    PIDS="$(find_port_pids)"

    if [ -n "$PIDS" ]; then
        force_kill_pids "$PIDS"
    else
        echo "=== no process found on port $START_PORT ===" >> "$LOG_FILE"
    fi

    sleep "$STOP_WAIT_SECONDS"

    if ! wait_for_port_release; then
        echo "=== stop ABORT @ $(date +%Y-%m-%dT%H:%M:%S%z) ===" >> "$LOG_FILE"
        echo "" >> "$LOG_FILE"
        return 1
    fi

    echo "=== stop DONE @ $(date +%Y-%m-%dT%H:%M:%S%z) ===" >> "$LOG_FILE"
    echo "" >> "$LOG_FILE"
    return 0
}

stop_existing_pm2() {
    echo "=== stop existing pm2 app $PM2_APP_NAME @ $(date +%Y-%m-%dT%H:%M:%S%z) ===" >> "$LOG_FILE"

    if ! command -v pm2 >/dev/null 2>&1; then
        echo "=== pm2 command not found; install pm2 before deployment ===" >> "$LOG_FILE"
        return 1
    fi

    if pm2 describe "$PM2_APP_NAME" >/dev/null 2>&1; then
        pm2 delete "$PM2_APP_NAME" >> "$LOG_FILE" 2>&1 || true
        echo "=== pm2 app deleted: $PM2_APP_NAME ===" >> "$LOG_FILE"
    else
        echo "=== no pm2 app found: $PM2_APP_NAME ===" >> "$LOG_FILE"
    fi

    echo "" >> "$LOG_FILE"
    return 0
}

launch_next() {
    echo "=== pm2 LAUNCH app=$PM2_APP_NAME port=$START_PORT @ $(date +%Y-%m-%dT%H:%M:%S%z) ===" >> "$LOG_FILE"

    export PORT="$START_PORT"
    pm2 start pnpm --name "$PM2_APP_NAME" -- run start >> "$LOG_FILE" 2>&1
    PM2_EXIT=$?
    echo "=== pm2 START EXIT_CODE=$PM2_EXIT @ $(date +%Y-%m-%dT%H:%M:%S%z) ===" >> "$LOG_FILE"

    if [ "$PM2_EXIT" -ne 0 ]; then
        echo "=== pm2 START FAILED ===" >> "$LOG_FILE"
        return "$PM2_EXIT"
    fi

    pm2 save >> "$LOG_FILE" 2>&1 || true
    echo "=== realtime logs: pm2 logs $PM2_APP_NAME ===" >> "$LOG_FILE"
    return 0
}

# ============================================================
# 8. log_abort —— build 失败时写结束标记（不输出 START LAUNCH）
# ============================================================
log_abort() {
    echo "=== ABORTED @ $(date +%Y-%m-%dT%H:%M:%S%z) (build failed, start skipped) ===" >> "$LOG_FILE"
}

# ============================================================
# main —— 调度所有步骤
# ============================================================
main() {
    resolve_repo_context
    init_log_file
    write_metadata_header    # 先覆盖写元信息（这样 env copy 日志能保留在后面）

    sync_worktree            # 先同步工作树到新 HEAD（与 git config denyCurrentBranch 配合）
    copy_env_by_branch       # 追加 env copy 日志

    select_port_by_branch    # 根据当前分支决定 next start 端口

    if ! run_build; then
        log_abort
        exit $BUILD_EXIT       # build 失败 → 不启 start（避免用旧 .next 跑新代码）
    fi

    if ! stop_existing_pm2; then
        exit 1                # pm2 不可用 → 不启动新服务
    fi

    # if ! stop_existing_next; then
    #     exit 1                # 端口未释放 → 不启动新服务，避免 EADDRINUSE
    # fi

    if ! launch_next; then
        exit 1                # pm2 启动失败
    fi
}

main "$@"

exit 0
