#!/usr/bin/env bash

# 使用方式：
#   ./merge_db_backup /absolute/path/to/merge_setup.ini DB_NAME_POSTFIX <AccountDB/GameDB/WorldDB>
#
# 流程：
#   1. source load_merge_db_setup
#   2. load_merge_db_setup <merge_setup.ini>
#   3. 依參數 3 決定要備份哪些 DB：
#        - 無參數 3：AccountDB + GameDB + 全部 WorldDB
#        - AccountDB：只備份 AccountDB
#        - GameDB：只備份 GameDB
#        - WorldDB：只備份所有 WorldDB
#   4. 對每一個要備份的 DB，在遠端 PostgreSQL 建立：
#        CREATE DATABASE "<DB_NAME><POSTFIX>" TEMPLATE "<DB_NAME>";
#
# 依賴：
#   - load_merge_db_setup 內已實作：
#       load_merge_db_setup
#       get_accountdb_value
#       get_gamedb_value
#       get_worlddb_value
#       world_order (依 setup 順序的 WorldID array)
#
#   - 每個 DB 的設定提供：
#       AccountDBIP / AccountDBName / AccountDBUser / AccountDBPW / (AccountDBPort)
#       GameDBIP / GameDB / GameDBUser / GameDBPassword / (GameDBPort)
#       WorldDBIP / WorldDBName / WorldDBUser / WorldDBPassword / (WorldDBPort)

set -u

if [[ $# -lt 2 ]]; then
    echo "Usage: $0 /absolute/path/to/merge_setup.ini DB_NAME_POSTFIX <AccountDB|GameDB|WorldDB>" >&2
    exit 1
fi

SETUP_FILE="$1"
DB_POSTFIX="$2"
TARGET_KIND="${3-}"    # 可能不存在，必須用 ${3-}

# 強制要求 setup 檔案用絕對路徑
if [[ "${SETUP_FILE:0:1}" != "/" ]]; then
    echo "請使用 setup 檔案的絕對路徑（目前：$SETUP_FILE）" >&2
    exit 1
fi

if [[ ! -f "$SETUP_FILE" ]]; then
    echo "找不到 setup 檔案：$SETUP_FILE" >&2
    exit 1
fi

# 驗證第三個參數（若有提供）
if [[ -n "$TARGET_KIND" ]]; then
    case "$TARGET_KIND" in
        AccountDB|GameDB|WorldDB)
            # 合法
            ;;
        *)
            echo "第三個參數若提供，只能是：AccountDB / GameDB / WorldDB" >&2
            echo "實際輸入：$TARGET_KIND" >&2
            exit 1
            ;;
    esac
fi

# 取得目前腳本所在目錄，假設 load_merge_db_setup 跟這支在同一個目錄
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"

# 引入 load_merge_db_setup
# shellcheck source=/dev/null
source "$SCRIPT_DIR/load_merge_db_setup"

# 載入設定
if ! load_merge_db_setup "$SETUP_FILE"; then
    echo "載入設定檔失敗：$SETUP_FILE" >&2
    exit 1
fi

########################################
# 共用：如 DB 有連線，才重啟遠端 PostgreSQL
########################################
# 參數：HOST SRC_DB USER PASS [PORT]
# 邏輯：
#   1. 用 pg_stat_activity 查 datname = SRC_DB 的連線數
#   2. 若連線數 > 0，則：
#        - 查遠端 psql 版本（主版號）
#        - sudo systemctl restart postgresql@${PG_VERSION}-main.service
#   3. 若連線數 = 0，則略過 restart
pg_check_and_restart_if_in_use() {
    local host="${1-}"
    local src_db="${2-}"
    local user="${3-}"
    local pass="${4-}"
    local port="${5-}"

    if [[ -z "$host" || -z "$src_db" || -z "$user" ]]; then
        echo "[NG]  pg_check_and_restart_if_in_use 參數不完整 (host='$host', db='$src_db', user='$user')" >&2
        return 1
    fi

    if [[ -z "$port" ]]; then
        port=5432
    fi

    echo "[INFO] 檢查 ${host}:${port} DB='${src_db}' 是否有連線佔用..."

    local conn_cnt
    conn_cnt=$(
        PGPASSWORD="$pass" PGCONNECT_TIMEOUT=3 \
        psql -h "$host" -p "$port" -U "$user" -d postgres \
             -tA -c "SELECT count(*) FROM pg_stat_activity WHERE datname = '${src_db}' AND pid <> pg_backend_pid();" \
             2>/dev/null || echo "ERROR"
    )

    conn_cnt=$(echo "$conn_cnt" | tr -d '[:space:]')

    if [[ "$conn_cnt" == "ERROR" || -z "$conn_cnt" ]]; then
        echo "[WARN] 無法取得 ${host}:${port} DB='${src_db}' 連線數，略過 restart 判斷" >&2
        return 0
    fi

    if ! [[ "$conn_cnt" =~ ^[0-9]+$ ]]; then
        echo "[WARN] 取得的連線數不是數字：'${conn_cnt}'，略過 restart 判斷" >&2
        return 0
    fi

    if (( conn_cnt == 0 )); then
        echo "[INFO] ${host}:${port} DB='${src_db}' 無活動連線，略過 PostgreSQL restart"
        return 0
    fi

    echo "[INFO] ${host}:${port} DB='${src_db}' 目前有 ${conn_cnt} 個連線，執行遠端 PostgreSQL restart..."

    local PG_VERSION
    PG_VERSION=$(ssh -o StrictHostKeyChecking=no "$host" "psql --version | awk '{print \$3}' | cut -d. -f1" 2>/dev/null || echo "")

    if [[ -z "$PG_VERSION" ]]; then
        echo "[NG]  無法在 ${host} 取得 psql 版本，無法決定 systemd 服務名稱，略過 restart" >&2
        return 1
    fi

    if ssh -o StrictHostKeyChecking=no "$host" "sudo systemctl restart postgresql@${PG_VERSION}-main.service"; then
        echo "[OK]  已在 ${host} 重啟 postgresql@${PG_VERSION}-main.service"
        # 選擇性：印一下狀態
        ssh -o StrictHostKeyChecking=no "$host" "sudo systemctl status postgresql@${PG_VERSION}-main.service --no-pager" >/dev/null 2>&1 || true
        # 等服務起來（原本 3s 太短，會跟後續 CREATE DATABASE 撞 race；10s 在大 DB 上 WAL replay 未完仍會 race，改 30s）
        sleep 30
        return 0
    else
        echo "[NG]  在 ${host} 重啟 postgresql@${PG_VERSION}-main.service 失敗" >&2
        return 1
    fi
}

########################################
# 共用：在指定主機上 clone 一個 DB
########################################
# 參數：HOST SRC_DB USER PASS [PORT] POSTFIX
# 動作：在該 host 上執行
#   CREATE DATABASE "<SRC_DB><POSTFIX>" TEMPLATE "<SRC_DB>";
#
# 若備份 DB 已存在，則跳過並顯示 [SKIP] 訊息。
pg_clone_db() {
    local host="${1-}"
    local src_db="${2-}"
    local user="${3-}"
    local pass="${4-}"
    local port="${5-}"
    local postfix="${6-}"

    if [[ -z "$host" || -z "$src_db" || -z "$user" ]]; then
        echo "[NG]  參數不完整，host/src_db/user 不可為空 (host='$host', db='$src_db')" >&2
        return 1
    fi

    if [[ -z "$port" ]]; then
        port=5432
    fi

    local new_db="${src_db}${postfix}"

    echo ">>> ${host}:${port} - clone DB '${src_db}' -> '${new_db}'"

    # 檢查新 DB 是否已存在
    local exists
    exists=$(
        PGPASSWORD="$pass" PGCONNECT_TIMEOUT=3 \
        psql -h "$host" -p "$port" -U "$user" -d postgres \
             -tA -c "SELECT 1 FROM pg_database WHERE datname = '${new_db}'" \
             2>/dev/null || true
    )

    if [[ "$exists" == "1" ]]; then
        echo "[SKIP]  ${host}:${port} - DB '${new_db}' 已存在，略過建立"
        return 0
    fi

    # 實際建立備份 DB（加 retry：吸收 postgres 重啟後短暫的連線 race）
    local sql
    sql="CREATE DATABASE \"${new_db}\" TEMPLATE \"${src_db}\";"

    local -a retry_waits=(0 5 10 20 30 60)  # 第 1 次立即試、失敗後等 5s / 10s / 20s / 30s / 60s 再試
    local attempt
    local rc=1
    local err_msg=""

    for attempt in "${!retry_waits[@]}"; do
        local wait="${retry_waits[$attempt]}"
        if (( wait > 0 )); then
            echo "[RETRY] ${host}:${port} - 等 ${wait}s 後重試建立 '${new_db}'（第 $((attempt+1))/${#retry_waits[@]} 次）"
            sleep "$wait"
        fi

        err_msg=$(
            PGPASSWORD="$pass" PGCONNECT_TIMEOUT=5 \
            psql -h "$host" -p "$port" -U "$user" -d postgres \
                 -c "$sql" 2>&1 >/dev/null
        ) && rc=0 || rc=$?

        if (( rc == 0 )); then
            if (( attempt > 0 )); then
                echo "[OK]    ${host}:${port} - 已建立備份 DB '${new_db}' (from '${src_db}')（第 $((attempt+1)) 次嘗試成功）"
            else
                echo "[OK]    ${host}:${port} - 已建立備份 DB '${new_db}' (from '${src_db}')"
            fi
            return 0
        fi
    done

    echo "[NG]    ${host}:${port} - 建立備份 DB '${new_db}' 失敗（重試 ${#retry_waits[@]} 次皆失敗）" >&2
    if [[ -n "$err_msg" ]]; then
        echo "[NG]    最後一次錯誤：${err_msg}" >&2
    fi
    return 1
}

echo "=== 建立 PostgreSQL DB 備份：$SETUP_FILE，POSTFIX='${DB_POSTFIX}'，TARGET='${TARGET_KIND:-ALL}' ==="

overall_status=0

########################################
# 1. Account DB
########################################
if [[ -z "$TARGET_KIND" || "$TARGET_KIND" == "AccountDB" ]]; then
    echo "--- AccountDB ---"
    {
        host=$(get_accountdb_value "AccountDBIP")
        dbname=$(get_accountdb_value "AccountDBName")
        user=$(get_accountdb_value "AccountDBUser")
        pass=$(get_accountdb_value "AccountDBPW")
        port=$(get_accountdb_value "AccountDBPort")

        if [[ -z "${dbname-}" ]]; then
            echo "[INFO]  未設定 AccountDBName，略過"
        else
            # 先檢查是否有連線，有的話才重啟 PostgreSQL
            if ! pg_check_and_restart_if_in_use "$host" "$dbname" "$user" "$pass" "$port"; then
                overall_status=1
            fi

            if pg_clone_db "$host" "$dbname" "$user" "$pass" "$port" "$DB_POSTFIX"; then
                :
            else
                overall_status=1
            fi
        fi
    }
    echo
fi

########################################
# 2. Game DB
########################################
if [[ -z "$TARGET_KIND" || "$TARGET_KIND" == "GameDB" ]]; then
    echo "--- GameDB ---"
    {
        host=$(get_gamedb_value "GameDBIP")
        dbname=$(get_gamedb_value "GameDB")
        user=$(get_gamedb_value "GameDBUser")
        pass=$(get_gamedb_value "GameDBPassword")
        port=$(get_gamedb_value "GameDBPort")

        if [[ -z "${dbname-}" ]]; then
            echo "[INFO]  未設定 GameDB，略過"
        else
            # 先檢查是否有連線，有的話才重啟 PostgreSQL
            if ! pg_check_and_restart_if_in_use "$host" "$dbname" "$user" "$pass" "$port"; then
                overall_status=1
            fi

            if pg_clone_db "$host" "$dbname" "$user" "$pass" "$port" "$DB_POSTFIX"; then
                :
            else
                overall_status=1
            fi
        fi
    }
    echo
fi

########################################
# 3. 所有 World DB
########################################
if [[ -z "$TARGET_KIND" || "$TARGET_KIND" == "WorldDB" ]]; then
    echo "--- WorldDB ---"
    if [[ "${#world_order[@]}" -gt 0 ]]; then
        for wid in "${world_order[@]}"; do
            host=$(get_worlddb_value "$wid" "WorldDBIP")
            dbname=$(get_worlddb_value "$wid" "WorldDBName")
            user=$(get_worlddb_value "$wid" "WorldDBUser")
            pass=$(get_worlddb_value "$wid" "WorldDBPassword")
            port=$(get_worlddb_value "$wid" "WorldDBPort")

            if [[ -z "${dbname-}" ]]; then
                echo "[INFO]  WorldID=${wid} 未設定 WorldDBName，略過"
                continue
            fi

            # 先檢查是否有連線，有的話才重啟 PostgreSQL
            if ! pg_check_and_restart_if_in_use "$host" "$dbname" "$user" "$pass" "$port"; then
                overall_status=1
            fi

            if pg_clone_db "$host" "$dbname" "$user" "$pass" "$port" "$DB_POSTFIX"; then
                :
            else
                overall_status=1
            fi
        done
    else
        echo "[INFO]  沒有任何 WorldDB 設定"
    fi
fi

echo "=== 建立備份作業結束 ==="

exit "$overall_status"

