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

########################################
# Usage / 參數
#
# $1: ENVIRONMENT (test|live)
# $2: FILE_NAME
# $3: MODULE
# $4..: OPTIONAL ARGS
#   - SetID filter: S11 S21 S31 ... (可多個；預設不加=全處理)
#   - removebackup scope: pre | post | both (可選；預設 both)
#
# 單一基礎功能 (single module):
#   generate                     : 產生合併目錄與檔案
#   reward_before_merge          : 合併前發送獎勵 fastcgi
#   dbcheck                      : 檢查 DB 連線
#   prebackup                    : 合併前 DB 備份
#   restore_prebackup            : 由 Pre 備份 DB 還原回原 DB
#   merge_rollback_partial_worlds: rollback AccountDB worlds / delete fake_worlds（PostgreSQL）
#   merge                        : 執行合併
#   logcheck                     : 初次檢查 log
#   waitdone                     : 等待各 List 出現 ,Done
#   update_guild_race_data       : 呼叫 merge_update_guild_race_data（需帶入 merge_setup.ini 絕對路徑）
#   postbackup                   : 合併後 DB 備份
#   check                        : merge_check 比對差異
#   update_db_setup              : 更新 db_setup_for_merge (僅 live)
#   move_old_server_directory    : 移動舊 server 目錄
#   remove_source_wids_on_hosts  : 從 hosts 中移除 world (僅 live)
#   removebackup                 : 刪除 Pre/Post 備份 DB（scope: pre|post|both；預設 both；backup_flag 預設 1）
#   removebackup_wb              : 刪除 Pre/Post 備份 DB（scope: pre|post|both；預設 both；backup_flag=1）
#   removebackup_wob             : 刪除 Pre/Post 備份 DB（scope: pre|post|both；預設 both；backup_flag=0）
#
# 群組功能 (group module) 與組成:
#   exec_merge  =
#       generate
#       + dbcheck
#       + prebackup
#       + merge
#       + logcheck
#       + waitdone
#       + update_guild_race_data
#       + postbackup
#       + check
#
#   before_merge =
#       generate
#       + reward_before_merge
#
#   after_merge =
#       update_db_setup
#       + move_old_server_directory
#       + remove_source_wids_on_hosts
#
#   end_merge =
#       removebackup
#
#   re_run_exec_merge =
#       removebackup_wob post
#       + restore_prebackup
#       + merge_rollback_partial_worlds
#       + merge
#       + logcheck
#       + waitdone
#       + update_guild_race_data
#       + postbackup
#       + check
#
#   re_run_pre_merge =
#       removebackup_wob post
#       + restore_prebackup
#       + merge_rollback_partial_worlds
########################################

if [ "$#" -lt 2 ]; then
    echo "Usage: $0 <ENVIRONMENT: test|live> <FILE_NAME> [MODULE] [Sxx ...] [pre|post|both]" >&2
    echo "  單一基礎功能 (single):" >&2
    echo "    generate | reward_before_merge | dbcheck | prebackup | restore_prebackup | merge_rollback_partial_worlds | merge | logcheck | waitdone | update_guild_race_data | postbackup | check | update_db_setup | move_old_server_directory | remove_source_wids_on_hosts | removebackup | removebackup_wb | removebackup_wob" >&2
    echo "  群組功能 (group):" >&2
    echo "    exec_merge   = generate + dbcheck + prebackup + merge + logcheck + waitdone + update_guild_race_data + postbackup + check" >&2
    echo "    before_merge = generate + reward_before_merge" >&2
    echo "    after_merge  = update_db_setup + move_old_server_directory + remove_source_wids_on_hosts" >&2
    echo "    end_merge    = removebackup" >&2
    echo "    re_run_exec_merge = removebackup_wob post + restore_prebackup + merge_rollback_partial_worlds + merge + logcheck + waitdone + update_guild_race_data + postbackup + check" >&2
    echo "    re_run_pre_merge  = removebackup_wob post + restore_prebackup + merge_rollback_partial_worlds" >&2
    exit 1
fi

ENVIRONMENT="$1"
FILE_NAME="$2"
MODULE="${3:-exec_merge}"

if [ "$#" -ge 3 ]; then
    shift 3
else
    shift 2
fi

case "$ENVIRONMENT" in
    test|live) ;;
    *)
        echo "Error: ENVIRONMENT must be 'test' or 'live', got '$ENVIRONMENT'." >&2
        exit 1
        ;;
esac

# 解析 optional args：Sxx / pre|post|both
BACKUP_SCOPE="both"
declare -a SET_FILTERS_RAW=()
declare -a UNKNOWN_ARGS=()

for a in "$@"; do
    case "$a" in
        pre|post|both)
            BACKUP_SCOPE="$a"
            ;;
        S*|s*)
            if [[ "$a" =~ ^[Ss]([0-9]+)$ ]]; then
                SET_FILTERS_RAW+=( "${BASH_REMATCH[1]}" )
            else
                UNKNOWN_ARGS+=( "$a" )
            fi
            ;;
        *)
            UNKNOWN_ARGS+=( "$a" )
            ;;
    esac
done

if [ "${#UNKNOWN_ARGS[@]}" -ne 0 ]; then
    echo "Error: unknown args: ${UNKNOWN_ARGS[*]}" >&2
    exit 1
fi

# Set filter 去重
declare -A SET_FILTER_MAP=()
declare -a SET_FILTERS=()
for sid in "${SET_FILTERS_RAW[@]}"; do
    if ! [[ "$sid" =~ ^[0-9]+$ ]]; then
        echo "Error: invalid SetID in filter: '$sid'" >&2
        exit 1
    fi
    if [[ -z "${SET_FILTER_MAP[$sid]+x}" ]]; then
        SET_FILTER_MAP["$sid"]=1
        SET_FILTERS+=( "$sid" )
    fi
done

INPUT_FILE="$HOME/bin/merge_n1/${ENVIRONMENT}/${FILE_NAME}"

if [ ! -f "$INPUT_FILE" ]; then
    echo "Error: input file not found: $INPUT_FILE" >&2
    exit 1
fi
if [ ! -s "$INPUT_FILE" ]; then
    echo "Error: input file is empty: $INPUT_FILE" >&2
    exit 1
fi

########################################
# 記錄開始執行時間
########################################
START_TS=$(date +%s)
START_TIME=$(date '+%Y-%m-%d %H:%M:%S')
echo "== run_merge_n1 start at ${START_TIME} (ENV=${ENVIRONMENT}, FILE_NAME=${FILE_NAME}, MODULE=${MODULE}) =="

########################################
# 資料結構宣告
########################################
declare -A list_worlds
declare -A list_setid
declare -a all_lists_full

########################################
# 讀取檔案，建立結構 & 檢查每行 SET_ID 一致性
########################################
LINE_NO=0
while IFS= read -r line || [ -n "$line" ]; do
    LINE_NO=$((LINE_NO + 1))

    line_trimmed="${line#"${line%%[![:space:]]*}"}"
    line_trimmed="${line_trimmed%"${line_trimmed##*[![:space:]]}"}"

    if [ -z "$line_trimmed" ] || [[ "$line_trimmed" =~ ^# ]]; then
        continue
    fi

    read -r -a WORLDS <<< "$line_trimmed"
    if [ "${#WORLDS[@]}" -lt 1 ]; then
        echo "Error: line $LINE_NO has no WorldID: '$line'" >&2
        exit 1
    fi

    LINE_SET_ID=""
    for wid in "${WORLDS[@]}"; do
        if ! [[ "$wid" =~ ^[0-9]+$ ]]; then
            echo "Error: line $LINE_NO has non-numeric WorldID '$wid'." >&2
            exit 1
        fi
        wid_set_id=$(( wid / 100 ))
        if [ -z "$LINE_SET_ID" ]; then
            LINE_SET_ID="$wid_set_id"
        else
            if (( wid_set_id != LINE_SET_ID )); then
                echo "Error: line $LINE_NO has mixed SET_ID: expected $LINE_SET_ID, got $wid_set_id (WorldID: $wid)." >&2
                exit 1
            fi
        fi
    done

    LID="L${LINE_NO}"
    list_worlds["$LID"]="$line_trimmed"
    list_setid["$LID"]="$LINE_SET_ID"
    all_lists_full+=( "$LID" )
done < "$INPUT_FILE"

if [ "${#all_lists_full[@]}" -eq 0 ]; then
    echo "Error: no valid WorldID lists found in file: $INPUT_FILE" >&2
    exit 1
fi

########################################
# 依 SetID filter 建立選中結構
########################################
declare -A set_lists
declare -A set_first_list
declare -a all_lists

is_sid_selected() {
    local sid="$1"
    if [ "${#SET_FILTERS[@]}" -eq 0 ]; then
        return 0
    fi
    [[ -n "${SET_FILTER_MAP[$sid]+x}" ]]
}

for lid in "${all_lists_full[@]}"; do
    sid="${list_setid[$lid]}"
    if ! is_sid_selected "$sid"; then
        continue
    fi

    all_lists+=( "$lid" )
    if [[ -z "${set_lists[$sid]+x}" ]]; then
        set_lists["$sid"]="$lid"
        set_first_list["$sid"]="$lid"
    else
        set_lists["$sid"]+=" $lid"
    fi
done

if [ "${#all_lists[@]}" -eq 0 ]; then
    echo "Error: after SetID filter, no lists selected. (filters: ${SET_FILTERS[*]:-ALL})" >&2
    exit 1
fi

########################################
# SHOW 結構（方便除錯）
########################################
echo "Input file      : $INPUT_FILE"
echo "Environment     : $ENVIRONMENT"
echo "FILE_NAME       : $FILE_NAME"
echo "SetID filters   : ${SET_FILTERS[*]:-ALL}"
echo "Lists by SET_ID :"
for set_id in "${!set_lists[@]}"; do
    echo "  SET_ID $set_id => ${set_lists[$set_id]}"
done
echo

########################################
# 全域輔助變數（第一個 List，以「選中」後為準）
########################################
FIRST_LID_GLOBAL="${all_lists[0]}"

########################################
# 工具函式：依 SET_ID + ENV 取得 AREA / MACHINE / GENERATOR
########################################
get_area_machine_and_generator() {
    local env="$1"
    local sid="$2"
    local area=""
    local machine=""
    local generator=""

    if (( sid >= 10 && sid < 20 )); then
        area="asia"
    elif (( sid >= 20 && sid < 30 )); then
        area="us"
    elif (( sid >= 30 && sid < 40 )); then
        area="eu"
    else
        echo "Error: unsupported SET_ID '$sid' (only 10~39 supported)." >&2
        return 1
    fi

    if [ "$env" = "live" ]; then
        machine="MS${sid}"
        generator="generate_merge_preparation"
    else
        case "$area" in
            asia) machine="MERGE_ASIA" ;;
            us)   machine="MERGE_US" ;;
            eu)   machine="MERGE_EU" ;;
        esac
        generator="generate_merge_preparation_test"
    fi

    printf '%s %s %s\n' "$area" "$machine" "$generator"
}

WAIT_INTERVAL=60

get_remote_base_and_ini_by_lid() {
    local lid="$1"
    local sid="${list_setid[$lid]}"
    local area machine _
    read area machine _ < <(get_area_machine_and_generator "$ENVIRONMENT" "$sid")

    local directory_name="merge_${area}_${FILE_NAME}_${ENVIRONMENT}"
    local world_lists="${list_worlds[$lid]}"
    local world_str="${world_lists// /_}"
    local remote_base="\$HOME/servers${sid}/${directory_name}/${world_str}"
    local remote_ini="${remote_base}/merge_setup.ini"

    printf '%s %s %s %s %s\n' "$sid" "$area" "$machine" "$remote_base" "$remote_ini"
}

########################################
# 模組：產生合併目錄與檔案
########################################
run_generate() {
    echo ">>> MODULE: generate (create merge directories and files)"
    for set_id in "${!set_lists[@]}"; do
        read AREA MACHINE GENERATOR < <(get_area_machine_and_generator "$ENVIRONMENT" "$set_id")
        DIRECTORY_NAME="merge_${AREA}_${FILE_NAME}_${ENVIRONMENT}"
        read -r -a LIDS <<< "${set_lists[$set_id]}"

        echo "==== SET_ID ${set_id} / AREA=${AREA} / MACHINE=${MACHINE} / GEN=${GENERATOR} ===="
        for lid in "${LIDS[@]}"; do
            WORLD_ID_LISTS="${list_worlds[$lid]}"
            echo "[${GENERATOR}] $MACHINE $DIRECTORY_NAME :: $WORLD_ID_LISTS"
            ssh "$MACHINE" "\$HOME/bin/${GENERATOR} ${DIRECTORY_NAME} ${WORLD_ID_LISTS}"
        done
    done
    echo
}

########################################
# 模組：reward_before_merge
########################################
run_reward_before_merge() {
    echo ">>> MODULE: reward_before_merge (send_fastcgi_all_once_n1_by_world_reward_merge)"
    for lid in "${all_lists[@]}"; do
        WORLD_ID_LISTS="${list_worlds[$lid]}"
        echo "[reward_before_merge] LID=${lid} :: ${WORLD_ID_LISTS}"
        "$HOME/bin/send_fastcgi_all_once_n1_by_world_reward_merge" "$ENVIRONMENT" $WORLD_ID_LISTS
    done
    echo
}

########################################
# 模組：檢查 DB 連線
########################################
run_db_connect_verify() {
    echo ">>> MODULE: dbcheck (merge_db_connect_verify)"
    for lid in "${all_lists[@]}"; do
        read sid area machine remote_base remote_ini < <(get_remote_base_and_ini_by_lid "$lid")
        echo "[merge_db_connect_verify] $machine $remote_ini"
        ssh "$machine" "\$HOME/bin/merge_db_connect_verify \"${remote_ini}\""
    done
    echo
}

########################################
# 模組：Pre DB Backup
########################################
run_pre_backup() {
    echo ">>> MODULE: prebackup (Pre DB backup: AccountDB / GameDB / WorldDB)"

    read sid area machine remote_base remote_ini < <(get_remote_base_and_ini_by_lid "$FIRST_LID_GLOBAL")
    echo "[Pre-AccountDB] First global list: $FIRST_LID_GLOBAL (${list_worlds[$FIRST_LID_GLOBAL]}) on $machine"
    ssh "$machine" "\$HOME/bin/merge_db_backup \"${remote_ini}\" _${FILE_NAME}_Pre AccountDB"

    for set_id in "${!set_first_list[@]}"; do
        FIRST_LID="${set_first_list[$set_id]}"
        read sid2 area2 machine2 remote_base2 remote_ini2 < <(get_remote_base_and_ini_by_lid "$FIRST_LID")
        echo "[Pre-GameDB] SET_ID=${set_id} first list: $FIRST_LID (${list_worlds[$FIRST_LID]}) on $machine2"
        ssh "$machine2" "\$HOME/bin/merge_db_backup \"${remote_ini2}\" _${FILE_NAME}_Pre GameDB"
    done

    for lid in "${all_lists[@]}"; do
        read sid3 area3 machine3 remote_base3 remote_ini3 < <(get_remote_base_and_ini_by_lid "$lid")
        echo "[Pre-WorldDB] LID=${lid}, SET_ID=${sid3} (${list_worlds[$lid]}) on $machine3"
        ssh "$machine3" "\$HOME/bin/merge_db_backup \"${remote_ini3}\" _${FILE_NAME}_Pre WorldDB"
    done
    echo
}

########################################
# 模組：Restore Pre DB Backup
########################################
run_restore_prebackup() {
    echo ">>> MODULE: restore_prebackup (restore DB from Pre backup: AccountDB / GameDB / WorldDB)"

    read sid area machine remote_base remote_ini < <(get_remote_base_and_ini_by_lid "$FIRST_LID_GLOBAL")
    echo "[Restore-AccountDB] First global list: $FIRST_LID_GLOBAL (${list_worlds[$FIRST_LID_GLOBAL]}) on $machine"
    ssh "$machine" "\$HOME/bin/merge_db_restore \"${remote_ini}\" _${FILE_NAME}_Pre AccountDB"

    for set_id in "${!set_first_list[@]}"; do
        FIRST_LID="${set_first_list[$set_id]}"
        read sid2 area2 machine2 remote_base2 remote_ini2 < <(get_remote_base_and_ini_by_lid "$FIRST_LID")
        echo "[Restore-GameDB] SET_ID=${set_id} first list: $FIRST_LID (${list_worlds[$FIRST_LID]}) on $machine2"
        ssh "$machine2" "\$HOME/bin/merge_db_restore \"${remote_ini2}\" _${FILE_NAME}_Pre GameDB"
    done

    for lid in "${all_lists[@]}"; do
        read sid3 area3 machine3 remote_base3 remote_ini3 < <(get_remote_base_and_ini_by_lid "$lid")
        echo "[Restore-WorldDB] LID=${lid}, SET_ID=${sid3} (${list_worlds[$lid]}) on $machine3"
        ssh "$machine3" "\$HOME/bin/merge_db_restore \"${remote_ini3}\" _${FILE_NAME}_Pre WorldDB"
    done
    echo
}

########################################
# 模組：merge_rollback_partial_worlds
########################################
run_merge_rollback_partial_worlds() {
    echo ">>> MODULE: merge_rollback_partial_worlds (PostgreSQL rollback partial worlds)"
    read sid area machine remote_base remote_ini < <(get_remote_base_and_ini_by_lid "$FIRST_LID_GLOBAL")

    echo "[merge_rollback_partial_worlds] Use global first list: $FIRST_LID_GLOBAL (${list_worlds[$FIRST_LID_GLOBAL]}) on $machine"
    echo "[merge_rollback_partial_worlds] merge_setup.ini: $remote_ini"
    echo "[merge_rollback_partial_worlds] BACKUP_TAG     : ${FILE_NAME}_Pre"
    ssh "$machine" "\$HOME/bin/merge_rollback_partial_worlds \"${remote_ini}\" \"${FILE_NAME}_Pre\""
    echo
}

########################################
# 模組：執行合併作業 (merge_exec)
########################################
run_merge_exec() {
    echo ">>> MODULE: merge (run merge_exec)"
    for lid in "${all_lists[@]}"; do
        read sid area machine remote_base remote_ini < <(get_remote_base_and_ini_by_lid "$lid")
        echo "[merge_exec] LID=${lid}, SET_ID=${sid} (${list_worlds[$lid]}) on $machine"
        ssh "$machine" "cd ${remote_base}; nohup ./merge_exec &"
    done
    echo
}

########################################
# 模組：初次檢查 Server log
########################################
run_log_check() {
    echo ">>> MODULE: logcheck (first grep on Test.log*)"
    echo "==== [LOG CHECK - FIRST GREP] ===="
    for lid in "${all_lists[@]}"; do
        read sid area machine remote_base remote_ini < <(get_remote_base_and_ini_by_lid "$lid")
        echo "[log check] LID=${lid}, SET_ID=${sid} (${list_worlds[$lid]}) on $machine"
        ssh "$machine" "cd ${remote_base}; grep -E ',WolrdDB|,WorldDB|,GameDB|,AccountDB|,Done| autosend_info| redenvelope_info' Test.log* || echo 'No match in Test.log*'"
    done
    echo
}

########################################
# 模組：等待所有 Lists 出現 ',Done'
########################################
run_wait_done() {
    echo ">>> MODULE: waitdone (wait all lists have ',Done' in Test.log*)"
    echo "==== [WAIT ALL LISTS DONE: grep ',Done' Test.log*] ===="

    while :; do
        all_done=1
        for lid in "${all_lists[@]}"; do
            read sid area machine remote_base remote_ini < <(get_remote_base_and_ini_by_lid "$lid")
            if ssh "$machine" "cd ${remote_base}; grep -q ',Done' Test.log* 2>/dev/null"; then
                echo "[DONE] LID=${lid}, SET_ID=${sid} (${list_worlds[$lid]}) on $machine"
            else
                echo "[WAIT] LID=${lid}, SET_ID=${sid} (${list_worlds[$lid]}) on $machine still no ',Done' in Test.log*"
                all_done=0
            fi
        done

        if [ "$all_done" -eq 1 ]; then
            echo "==== ALL LISTS HAVE ',Done' IN Test.log*. ===="
            break
        fi

        echo "==== Not all lists finished. Sleep ${WAIT_INTERVAL} seconds then re-check... ===="
        sleep "$WAIT_INTERVAL"
    done
    echo
}

########################################
# 模組：update_guild_race_data
########################################
run_update_guild_race_data() {
    echo ">>> MODULE: update_guild_race_data (merge_update_guild_race_data)"
    for lid in "${all_lists[@]}"; do
        read sid area machine remote_base remote_ini < <(get_remote_base_and_ini_by_lid "$lid")
        echo "[merge_update_guild_race_data] LID=${lid}, SET_ID=${sid} (${list_worlds[$lid]}) on $machine"
        ssh "$machine" "\$HOME/bin/merge_update_guild_race_data \"${remote_ini}\""
    done
    echo
}

########################################
# 模組：Post DB Backup
########################################
run_post_backup() {
    echo ">>> MODULE: postbackup (Post DB backup: AccountDB / GameDB / WorldDB)"

    read sid area machine remote_base remote_ini < <(get_remote_base_and_ini_by_lid "$FIRST_LID_GLOBAL")
    echo "[Post-AccountDB] First global list: $FIRST_LID_GLOBAL (${list_worlds[$FIRST_LID_GLOBAL]}) on $machine"
    ssh "$machine" "\$HOME/bin/merge_db_backup \"${remote_ini}\" _${FILE_NAME}_Post AccountDB"

    for set_id in "${!set_first_list[@]}"; do
        FIRST_LID="${set_first_list[$set_id]}"
        read sid2 area2 machine2 remote_base2 remote_ini2 < <(get_remote_base_and_ini_by_lid "$FIRST_LID")
        echo "[Post-GameDB] SET_ID=${set_id} first list: $FIRST_LID (${list_worlds[$FIRST_LID]}) on $machine2"
        ssh "$machine2" "\$HOME/bin/merge_db_backup \"${remote_ini2}\" _${FILE_NAME}_Post GameDB"
    done

    for lid in "${all_lists[@]}"; do
        read sid3 area3 machine3 remote_base3 remote_ini3 < <(get_remote_base_and_ini_by_lid "$lid")
        echo "[Post-WorldDB] LID=${lid}, SET_ID=${sid3} (${list_worlds[$lid]}) on $machine3"
        ssh "$machine3" "\$HOME/bin/merge_db_backup \"${remote_ini3}\" _${FILE_NAME}_Post WorldDB"
    done
    echo
}

########################################
# 模組：merge_check Pre / Post 比對
########################################
run_check_diff() {
    echo ">>> MODULE: check (merge_check compare Pre/Post, with ENVIRONMENT)"
    echo "==== [CHECK PRE vs POST] ===="
    for lid in "${all_lists[@]}"; do
        read sid area machine remote_base remote_ini < <(get_remote_base_and_ini_by_lid "$lid")
        echo "[merge_check] LID=${lid}, SET_ID=${sid} (${list_worlds[$lid]}) on $machine (ENV=${ENVIRONMENT})"
        ssh "$machine" "\$HOME/bin/merge_check \"${remote_ini}\" _${FILE_NAME}_Pre _${FILE_NAME}_Post $ENVIRONMENT"
    done
    echo
}

########################################
# 模組：update_db_setup（only live）
########################################
run_update_db_setup() {
    echo ">>> MODULE: update_db_setup (run update_db_setup_for_merge locally, only ENV=live)"

    if [ "$ENVIRONMENT" != "live" ]; then
        echo "Error: update_db_setup can only be executed in live environment. Current ENV=${ENVIRONMENT}." >&2
        exit 1
    fi

    for lid in "${all_lists[@]}"; do
        WORLD_ID_LISTS="${list_worlds[$lid]}"
        echo "[update_db_setup_for_merge] LID=${lid} :: ${WORLD_ID_LISTS}"
        "$HOME/bin/update_db_setup_for_merge" $WORLD_ID_LISTS
    done
    echo
}

########################################
# 模組：move_old_server_directory
########################################
run_move_old_server_directory() {
    echo ">>> MODULE: move_old_server_directory (run move_source_wids_directory_after_merge locally)"

    for lid in "${all_lists[@]}"; do
        set_id="${list_setid[$lid]}"
        read AREA _ _ < <(get_area_machine_and_generator "$ENVIRONMENT" "$set_id")
        DIRECTORY_NAME="merge_${AREA}_${FILE_NAME}_${ENVIRONMENT}"
        WORLD_ID_LISTS="${list_worlds[$lid]}"

        echo "[move_source_wids_directory_after_merge] LID=${lid}, SET_ID=${set_id}, ENV=${ENVIRONMENT}, DIR=${DIRECTORY_NAME} :: ${WORLD_ID_LISTS}"
        "$HOME/bin/move_source_wids_directory_after_merge" "$ENVIRONMENT" "$DIRECTORY_NAME" $WORLD_ID_LISTS
    done
    echo
}

########################################
# 模組：remove_source_wids_on_hosts（only live）
########################################
run_remove_source_wids_on_hosts() {
    echo ">>> MODULE: remove_source_wids_on_hosts (backup hosts and call remove_source_wids_on_hosts_after_merge, ENV=live only)"

    if [ "$ENVIRONMENT" != "live" ]; then
        echo "Error: remove_source_wids_on_hosts can only be executed in live environment. Current ENV=${ENVIRONMENT}." >&2
        exit 1
    fi

    HOSTS_FILE="$HOME/bin/hosts"
    HOSTS_BAK="$HOME/bin/hosts.old"

    if [ ! -f "$HOSTS_FILE" ]; then
        echo "Error: hosts file not found: $HOSTS_FILE" >&2
        exit 1
    fi

    echo "Backup hosts: $HOSTS_FILE -> $HOSTS_BAK"
    cp -arf "$HOSTS_FILE" "$HOSTS_BAK"

    echo "Call remove_source_wids_on_hosts_after_merge for each WORLD_ID_LISTS ..."
    for lid in "${all_lists[@]}"; do
        WORLD_ID_LISTS="${list_worlds[$lid]}"
        echo "[remove_source_wids_on_hosts_after_merge] LID=${lid} :: ${WORLD_ID_LISTS}"
        "$HOME/bin/remove_source_wids_on_hosts_after_merge" $WORLD_ID_LISTS
    done
    echo
}

########################################
# removebackup family
########################################
removebackup_patterns_by_scope() {
    local scope="$1"
    case "$scope" in
        pre)  printf '%s\n' "_${FILE_NAME}_Pre" ;;
        post) printf '%s\n' "_${FILE_NAME}_Post" ;;
        both) printf '%s\n%s\n' "_${FILE_NAME}_Pre" "_${FILE_NAME}_Post" ;;
        *)
            echo "Error: invalid scope '$scope' (use pre|post|both)" >&2
            return 1
            ;;
    esac
}

run_remove_backup_core() {
    local scope="$1"
    local backup_flag="${2-}"   # ""=用 drop_database 預設
    mapfile -t patterns < <(removebackup_patterns_by_scope "$scope")

    echo ">>> MODULE: removebackup (scope=${scope}, backup_flag=${backup_flag:-default})"

    if [ "$ENVIRONMENT" = "test" ]; then
        declare -A MACH=()
        MACH["MERGE_COMMON"]=1

        if [ "${#SET_FILTERS[@]}" -eq 0 ]; then
            MACH["MERGE_ASIA"]=1
            MACH["MERGE_US"]=1
            MACH["MERGE_EU"]=1
        else
            for sid in "${SET_FILTERS[@]}"; do
                read area machine _ < <(get_area_machine_and_generator "test" "$sid")
                MACH["$machine"]=1
            done
        fi

        for m in "${!MACH[@]}"; do
            echo "[removebackup] test env on ${m} for FILE_NAME=${FILE_NAME}"
            for p in "${patterns[@]}"; do
                if [ -n "$backup_flag" ]; then
                    ssh "$m" "\$HOME/bin/drop_database_for_current_machine_by_pattern postfix ${p} ${backup_flag}"
                else
                    ssh "$m" "\$HOME/bin/drop_database_for_current_machine_by_pattern postfix ${p}"
                fi
            done
        done
    else
        if [ "${#SET_FILTERS[@]}" -eq 0 ]; then
            echo "[removebackup] live env via sendscript alldb for FILE_NAME=${FILE_NAME}"
            for p in "${patterns[@]}"; do
                if [ -n "$backup_flag" ]; then
                    sendscript alldb "\$HOME/bin/drop_database_for_current_machine_by_pattern postfix ${p} ${backup_flag}"
                else
                    sendscript alldb "\$HOME/bin/drop_database_for_current_machine_by_pattern postfix ${p}"
                fi
            done
        else
            for sid in "${SET_FILTERS[@]}"; do
                ms="MS${sid}"
                echo "[removebackup] live env on ${ms} for FILE_NAME=${FILE_NAME}"
                for p in "${patterns[@]}"; do
                    if [ -n "$backup_flag" ]; then
                        ssh "$ms" "\$HOME/bin/drop_database_for_current_machine_by_pattern postfix ${p} ${backup_flag}"
                    else
                        ssh "$ms" "\$HOME/bin/drop_database_for_current_machine_by_pattern postfix ${p}"
                    fi
                done
            done
        fi
    fi
    echo
}

run_remove_backup()     { run_remove_backup_core "$BACKUP_SCOPE" ""; }
run_remove_backup_wb()  { run_remove_backup_core "$BACKUP_SCOPE" "1"; }
run_remove_backup_wob() { run_remove_backup_core "$BACKUP_SCOPE" "0"; }

########################################
# 模組執行入口
########################################
case "$MODULE" in
    exec_merge)
        run_generate
        run_db_connect_verify
        run_pre_backup
        run_merge_exec
        run_log_check
        run_wait_done
        run_update_guild_race_data
        run_post_backup
        run_check_diff
        ;;
    before_merge)
        run_generate
        run_reward_before_merge
        ;;
    after_merge)
        run_update_db_setup
        run_move_old_server_directory
        run_remove_source_wids_on_hosts
        ;;
    end_merge)
        run_remove_backup
        ;;
    re_run_exec_merge)
        BACKUP_SCOPE="post"
        run_remove_backup_wob
        run_restore_prebackup
        run_merge_rollback_partial_worlds
        run_merge_exec
        run_log_check
        run_wait_done
        run_update_guild_race_data
        run_post_backup
        run_check_diff
        ;;
    re_run_pre_merge)
        BACKUP_SCOPE="post"
        run_remove_backup_wob
        run_restore_prebackup
        run_merge_rollback_partial_worlds
        ;;
    generate) run_generate ;;
    reward_before_merge) run_reward_before_merge ;;
    dbcheck) run_db_connect_verify ;;
    prebackup) run_pre_backup ;;
    restore_prebackup) run_restore_prebackup ;;
    merge_rollback_partial_worlds) run_merge_rollback_partial_worlds ;;
    merge) run_merge_exec ;;
    logcheck) run_log_check ;;
    waitdone) run_wait_done ;;
    update_guild_race_data) run_update_guild_race_data ;;
    postbackup) run_post_backup ;;
    check) run_check_diff ;;
    update_db_setup) run_update_db_setup ;;
    move_old_server_directory) run_move_old_server_directory ;;
    remove_source_wids_on_hosts) run_remove_source_wids_on_hosts ;;
    removebackup) run_remove_backup ;;
    removebackup_wb) run_remove_backup_wb ;;
    removebackup_wob) run_remove_backup_wob ;;
    *)
        echo "Error: unknown MODULE '${MODULE}'." >&2
        exit 1
        ;;
esac

########################################
# 結束時間 & 總耗時
########################################
END_TS=$(date +%s)
END_TIME=$(date '+%Y-%m-%d %H:%M:%S')
ELAPSED=$((END_TS - START_TS))
ELAPSED_H=$((ELAPSED / 3600))
ELAPSED_M=$(((ELAPSED % 3600) / 60))
ELAPSED_S=$((ELAPSED % 60))

echo
echo "== run_merge_n1 finished (MODULE=${MODULE}) =="
echo "Start time : ${START_TIME}"
echo "End time   : ${END_TIME}"
printf 'Elapsed    : %02d 小時 %02d 分 %02d 秒\n' "$ELAPSED_H" "$ELAPSED_M" "$ELAPSED_S"
echo "ENV=${ENVIRONMENT}, FILE_NAME=${FILE_NAME}, SetID filters=${SET_FILTERS[*]:-ALL}"

