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

########################################
# Usage / 參數
#
# $1: ENVIRONMENT (test|live)
# $2: FILE_NAME
# $3: MODULE (optional; default=exec_merge)
# $4.. : optional arguments
#   - pre|post : only for MODULE=removebackup or end_merge
#               (no arg => default delete both Pre & Post)
#   - Sxx      : set filter (e.g. S11 S21 S31 ...), can be multiple
#
# 單一基礎功能 (single module):
#   generate                     : 產生合併目錄與檔案 (echo)
#   reward_before_merge          : 合併前發送獎勵 fastcgi (echo)
#   dbcheck                      : 檢查 DB 連線 (echo)
#   prebackup                    : 合併前 DB 備份 (echo)
#   restore_prebackup            : 由 Pre 備份 DB 還原回原 DB (echo)
#   merge                        : 執行合併 (echo)
#   logcheck                     : 初次檢查 log (echo)
#   waitdone                     : 等待各 List 出現 ,Done (echo)
#   postbackup                   : 合併後 DB 備份 (echo)
#   check                        : merge_check 比對差異 (echo)
#   update_db_setup              : 更新 db_setup_for_merge (echo, 僅 live 產生指令)
#   move_old_server_directory    : 移動舊 server 目錄 (echo)
#   remove_source_wids_on_hosts  : hosts 移除 world (echo, 僅 live)
#   removebackup [pre|post]      : 刪除 Pre/Post 備份 DB (echo)
#
# 群組功能 (group module) 與組成:
#   exec_merge =
#       generate
#       + dbcheck
#       + prebackup
#       + merge
#       + logcheck
#       + waitdone
#       + postbackup
#       + check
#
#   before_merge =
#       generate
#       + reward_before_merge
#
#   after_merge =
#       update_db_setup
#       + move_old_server_directory
#       + remove_source_wids_on_hosts
#
#   end_merge [pre|post] =
#       removebackup [pre|post]
#
# SetID 篩選:
#   - 預設不加 Sxx: 針對檔案內所有 SetID / world_id_lists 都產出指令
#   - 有加 Sxx: 只針對指定 SetID 的 world_id_lists 產出指令（可多個 Sxx）
########################################

if [ "$#" -lt 2 ]; then
    echo "Usage: $0 <ENVIRONMENT: test|live> <FILE_NAME> [MODULE] [pre|post] [S11 [S21 ...]]" >&2
    echo "  單一基礎功能 (single):" >&2
    echo "    generate | reward_before_merge | dbcheck | prebackup | restore_prebackup | merge | logcheck | waitdone | postbackup | check | update_db_setup | move_old_server_directory | remove_source_wids_on_hosts | removebackup" >&2
    echo "  群組功能 (group):" >&2
    echo "    exec_merge   = generate + dbcheck + prebackup + merge + logcheck + waitdone + 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 "  removebackup/end_merge only: pre 或 post（未給則預設 both）" >&2
    echo "  set filter: S11 S21 S31 ...（可多個）" >&2
    exit 1
fi

ENVIRONMENT="$1"
FILE_NAME="$2"
shift 2

is_valid_module() {
    local m="$1"
    case "$m" in
        exec_merge|before_merge|after_merge|end_merge|generate|reward_before_merge|dbcheck|prebackup|restore_prebackup|merge|logcheck|waitdone|postbackup|check|update_db_setup|move_old_server_directory|remove_source_wids_on_hosts|removebackup)
            return 0
            ;;
        *)
            return 1
            ;;
    esac
}

MODULE="exec_merge"
if [ "$#" -ge 1 ] && is_valid_module "$1"; then
    MODULE="$1"
    shift 1
fi

REMOVE_BACKUP_TARGET=""
declare -A SET_FILTERS

for arg in "$@"; do
    case "$arg" in
        pre|post)
            if [ "$MODULE" != "removebackup" ] && [ "$MODULE" != "end_merge" ]; then
                echo "Error: 'pre|post' 參數僅支援 MODULE 'removebackup' 或 'end_merge'。Current MODULE='${MODULE}'." >&2
                exit 1
            fi
            if [ -n "$REMOVE_BACKUP_TARGET" ] && [ "$REMOVE_BACKUP_TARGET" != "$arg" ]; then
                echo "Error: 'pre|post' 只能指定一次。已指定 '${REMOVE_BACKUP_TARGET}', 又收到 '${arg}'." >&2
                exit 1
            fi
            REMOVE_BACKUP_TARGET="$arg"
            ;;
        S[0-9]*)
            sid="${arg#S}"
            if ! [[ "$sid" =~ ^[0-9]+$ ]]; then
                echo "Error: invalid set filter '${arg}'. Expect format S11 / S21 / S31 ..." >&2
                exit 1
            fi
            SET_FILTERS["$sid"]=1
            ;;
        *)
            echo "Error: unknown argument '${arg}'. Supported: pre|post, Sxx (e.g., S11 S21)." >&2
            exit 1
            ;;
    esac
done

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

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_echo start at ${START_TIME} (ENV=${ENVIRONMENT}, FILE_NAME=${FILE_NAME}, MODULE=${MODULE}) =="

declare -A list_worlds
declare -A list_setid
declare -A set_lists
declare -A set_first_list
declare -a all_lists

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

    if [ "${#SET_FILTERS[@]}" -gt 0 ]; then
        if [[ -z "${SET_FILTERS[$LINE_SET_ID]+x}" ]]; then
            continue
        fi
    fi

    LID="L${LINE_NO}"
    list_worlds["$LID"]="$line_trimmed"
    list_setid["$LID"]="$LINE_SET_ID"
    all_lists+=( "$LID" )

    if [[ -z "${set_lists[$LINE_SET_ID]+x}" ]]; then
        set_lists["$LINE_SET_ID"]="$LID"
        set_first_list["$LINE_SET_ID"]="$LID"
    else
        set_lists["$LINE_SET_ID"]+=" $LID"
    fi
done < "$INPUT_FILE"

if [ "${#all_lists[@]}" -eq 0 ]; then
    if [ "${#SET_FILTERS[@]}" -gt 0 ]; then
        echo "Error: no WorldID lists matched the specified set filters in file: $INPUT_FILE" >&2
    else
        echo "Error: no valid WorldID lists found in file: $INPUT_FILE" >&2
    fi
    exit 1
fi

echo "Input file      : $INPUT_FILE"
echo "Environment     : $ENVIRONMENT"
echo "FILE_NAME       : $FILE_NAME"
if [ "${#SET_FILTERS[@]}" -gt 0 ]; then
    echo -n "Set filter      :"
    for sid in "${!SET_FILTERS[@]}"; do
        echo -n " S${sid}"
    done
    echo
else
    echo "Set filter      : (none, process all SetID)"
fi
echo "Lists by SET_ID :"
for set_id in "${!set_lists[@]}"; do
    echo "  SET_ID $set_id => ${set_lists[$set_id]}"
done
echo

FIRST_LID_GLOBAL="${all_lists[0]}"
FIRST_SETID_GLOBAL="${list_setid[$FIRST_LID_GLOBAL]}"

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

run_generate() {
    echo ">>> MODULE: generate (echo 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 "==== [GENERATE] 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"
            echo ssh "$MACHINE" "\$HOME/bin/${GENERATOR} ${DIRECTORY_NAME} ${WORLD_ID_LISTS}"
        done
    done
    echo
}

run_reward_before_merge() {
    echo ">>> MODULE: reward_before_merge (echo 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}"
        echo "\$HOME/bin/send_fastcgi_all_once_n1_by_world_reward_merge ${ENVIRONMENT} ${WORLD_ID_LISTS}"
    done
    echo
}

run_db_connect_verify() {
    echo ">>> MODULE: dbcheck (echo merge_db_connect_verify)"
    echo "==== [DB CONNECT VERIFY] ===="
    for set_id in "${!set_lists[@]}"; do
        read AREA MACHINE _ < <(get_area_machine_and_generator "$ENVIRONMENT" "$set_id")
        DIRECTORY_NAME="merge_${AREA}_${FILE_NAME}_${ENVIRONMENT}"
        read -r -a LIDS <<< "${set_lists[$set_id]}"

        for lid in "${LIDS[@]}"; do
            WORLD_ID_LISTS="${list_worlds[$lid]}"
            WORLD_ID_LISTS_STR=${WORLD_ID_LISTS// /_}
            REMOTE_BASE_DIR="\$HOME/servers${set_id}/${DIRECTORY_NAME}/${WORLD_ID_LISTS_STR}"
            REMOTE_MERGE_INI="${REMOTE_BASE_DIR}/merge_setup.ini"

            echo "# [merge_db_connect_verify] $MACHINE $REMOTE_MERGE_INI"
            echo ssh "$MACHINE" "\$HOME/bin/merge_db_connect_verify \"${REMOTE_MERGE_INI}\""
        done
    done
    echo
}

run_pre_backup() {
    echo ">>> MODULE: prebackup (echo Pre DB backup)"

    read AREA MACHINE _ < <(get_area_machine_and_generator "$ENVIRONMENT" "$FIRST_SETID_GLOBAL")
    DIRECTORY_NAME="merge_${AREA}_${FILE_NAME}_${ENVIRONMENT}"
    WORLD_ID_LISTS_FIRST_GLOBAL="${list_worlds[$FIRST_LID_GLOBAL]}"
    WORLD_ID_LISTS_STR_FIRST_GLOBAL=${WORLD_ID_LISTS_FIRST_GLOBAL// /_}
    REMOTE_BASE_DIR_FIRST_GLOBAL="\$HOME/servers${FIRST_SETID_GLOBAL}/${DIRECTORY_NAME}/${WORLD_ID_LISTS_STR_FIRST_GLOBAL}"
    REMOTE_MERGE_INI_FIRST_GLOBAL="${REMOTE_BASE_DIR_FIRST_GLOBAL}/merge_setup.ini"

    echo "==== [PRE BACKUP] Global First List (AccountDB) ===="
    echo "# [Pre-AccountDB] $FIRST_LID_GLOBAL ($WORLD_ID_LISTS_FIRST_GLOBAL) on $MACHINE"
    echo ssh "$MACHINE" "\$HOME/bin/merge_db_backup \"${REMOTE_MERGE_INI_FIRST_GLOBAL}\" _${FILE_NAME}_Pre AccountDB"
    echo

    echo "==== [PRE BACKUP] Per SET_ID First List (GameDB) ===="
    for set_id in "${!set_first_list[@]}"; do
        FIRST_LID="${set_first_list[$set_id]}"
        read AREA MACHINE _ < <(get_area_machine_and_generator "$ENVIRONMENT" "$set_id")
        DIRECTORY_NAME="merge_${AREA}_${FILE_NAME}_${ENVIRONMENT}"
        WORLD_ID_LISTS_FIRST_SET="${list_worlds[$FIRST_LID]}"
        WORLD_ID_LISTS_STR_FIRST_SET=${WORLD_ID_LISTS_FIRST_SET// /_}
        REMOTE_BASE_DIR_FIRST_SET="\$HOME/servers${set_id}/${DIRECTORY_NAME}/${WORLD_ID_LISTS_STR_FIRST_SET}"
        REMOTE_MERGE_INI_FIRST_SET="${REMOTE_BASE_DIR_FIRST_SET}/merge_setup.ini"

        echo "# [Pre-GameDB] SET_ID=${set_id} first list: $FIRST_LID ($WORLD_ID_LISTS_FIRST_SET) on $MACHINE"
        echo ssh "$MACHINE" "\$HOME/bin/merge_db_backup \"${REMOTE_MERGE_INI_FIRST_SET}\" _${FILE_NAME}_Pre GameDB"
    done
    echo

    echo "==== [PRE BACKUP] Each List (WorldDB) ===="
    for lid in "${all_lists[@]}"; do
        set_id="${list_setid[$lid]}"
        read AREA MACHINE _ < <(get_area_machine_and_generator "$ENVIRONMENT" "$set_id")
        DIRECTORY_NAME="merge_${AREA}_${FILE_NAME}_${ENVIRONMENT}"
        WORLD_ID_LISTS="${list_worlds[$lid]}"
        WORLD_ID_LISTS_STR=${WORLD_ID_LISTS// /_}
        REMOTE_BASE_DIR="\$HOME/servers${set_id}/${DIRECTORY_NAME}/${WORLD_ID_LISTS_STR}"
        REMOTE_MERGE_INI="${REMOTE_BASE_DIR}/merge_setup.ini"

        echo "# [Pre-WorldDB] LID=${lid}, SET_ID=${set_id} ($WORLD_ID_LISTS) on $MACHINE"
        echo ssh "$MACHINE" "\$HOME/bin/merge_db_backup \"${REMOTE_MERGE_INI}\" _${FILE_NAME}_Pre WorldDB"
    done
    echo
}

run_restore_prebackup() {
    echo ">>> MODULE: restore_prebackup (echo restore DB from Pre backup: AccountDB / GameDB / WorldDB)"

    read AREA MACHINE _ < <(get_area_machine_and_generator "$ENVIRONMENT" "$FIRST_SETID_GLOBAL")
    DIRECTORY_NAME="merge_${AREA}_${FILE_NAME}_${ENVIRONMENT}"
    WORLD_ID_LISTS_FIRST_GLOBAL="${list_worlds[$FIRST_LID_GLOBAL]}"
    WORLD_ID_LISTS_STR_FIRST_GLOBAL=${WORLD_ID_LISTS_FIRST_GLOBAL// /_}
    REMOTE_BASE_DIR_FIRST_GLOBAL="\$HOME/servers${FIRST_SETID_GLOBAL}/${DIRECTORY_NAME}/${WORLD_ID_LISTS_STR_FIRST_GLOBAL}"
    REMOTE_MERGE_INI_FIRST_GLOBAL="${REMOTE_BASE_DIR_FIRST_GLOBAL}/merge_setup.ini"

    echo "==== [RESTORE PRE] Global First List (AccountDB) ===="
    echo "# [Restore-AccountDB] $FIRST_LID_GLOBAL ($WORLD_ID_LISTS_FIRST_GLOBAL) on $MACHINE"
    echo ssh "$MACHINE" "\$HOME/bin/merge_db_restore \"${REMOTE_MERGE_INI_FIRST_GLOBAL}\" _${FILE_NAME}_Pre AccountDB"
    echo

    echo "==== [RESTORE PRE] Per SET_ID First List (GameDB) ===="
    for set_id in "${!set_first_list[@]}"; do
        FIRST_LID="${set_first_list[$set_id]}"
        read AREA MACHINE _ < <(get_area_machine_and_generator "$ENVIRONMENT" "$set_id")
        DIRECTORY_NAME="merge_${AREA}_${FILE_NAME}_${ENVIRONMENT}"
        WORLD_ID_LISTS_FIRST_SET="${list_worlds[$FIRST_LID]}"
        WORLD_ID_LISTS_STR_FIRST_SET=${WORLD_ID_LISTS_FIRST_SET// /_}
        REMOTE_BASE_DIR_FIRST_SET="\$HOME/servers${set_id}/${DIRECTORY_NAME}/${WORLD_ID_LISTS_STR_FIRST_SET}"
        REMOTE_MERGE_INI_FIRST_SET="${REMOTE_BASE_DIR_FIRST_SET}/merge_setup.ini"

        echo "# [Restore-GameDB] SET_ID=${set_id} first list: $FIRST_LID ($WORLD_ID_LISTS_FIRST_SET) on $MACHINE"
        echo ssh "$MACHINE" "\$HOME/bin/merge_db_restore \"${REMOTE_MERGE_INI_FIRST_SET}\" _${FILE_NAME}_Pre GameDB"
    done
    echo

    echo "==== [RESTORE PRE] Each List (WorldDB) ===="
    for lid in "${all_lists[@]}"; do
        set_id="${list_setid[$lid]}"
        read AREA MACHINE _ < <(get_area_machine_and_generator "$ENVIRONMENT" "$set_id")
        DIRECTORY_NAME="merge_${AREA}_${FILE_NAME}_${ENVIRONMENT}"
        WORLD_ID_LISTS="${list_worlds[$lid]}"
        WORLD_ID_LISTS_STR=${WORLD_ID_LISTS// /_}
        REMOTE_BASE_DIR="\$HOME/servers${set_id}/${DIRECTORY_NAME}/${WORLD_ID_LISTS_STR}"
        REMOTE_MERGE_INI="${REMOTE_BASE_DIR}/merge_setup.ini"

        echo "# [Restore-WorldDB] LID=${lid}, SET_ID=${set_id} ($WORLD_ID_LISTS) on $MACHINE"
        echo ssh "$MACHINE" "\$HOME/bin/merge_db_restore \"${REMOTE_MERGE_INI}\" _${FILE_NAME}_Pre WorldDB"
    done
    echo
}

run_merge_exec() {
    echo ">>> MODULE: merge (echo merge_exec)"
    echo "==== [MERGE EXEC] ===="
    for lid in "${all_lists[@]}"; do
        set_id="${list_setid[$lid]}"
        read AREA MACHINE _ < <(get_area_machine_and_generator "$ENVIRONMENT" "$set_id")
        DIRECTORY_NAME="merge_${AREA}_${FILE_NAME}_${ENVIRONMENT}"
        WORLD_ID_LISTS="${list_worlds[$lid]}"
        WORLD_ID_LISTS_STR=${WORLD_ID_LISTS// /_}
        REMOTE_BASE_DIR="\$HOME/servers${set_id}/${DIRECTORY_NAME}/${WORLD_ID_LISTS_STR}"

        echo "# [merge_exec] LID=${lid}, SET_ID=${set_id} ($WORLD_ID_LISTS) on $MACHINE"
        echo ssh "$MACHINE" "cd ${REMOTE_BASE_DIR}; nohup ./merge_exec &"
    done
    echo
}

run_log_check() {
    echo ">>> MODULE: logcheck (echo first grep on Test.log*)"
    echo "==== [LOG CHECK - FIRST GREP] ===="
    for lid in "${all_lists[@]}"; do
        set_id="${list_setid[$lid]}"
        read AREA MACHINE _ < <(get_area_machine_and_generator "$ENVIRONMENT" "$set_id")
        DIRECTORY_NAME="merge_${AREA}_${FILE_NAME}_${ENVIRONMENT}"
        WORLD_ID_LISTS="${list_worlds[$lid]}"
        WORLD_ID_LISTS_STR=${WORLD_ID_LISTS// /_}
        REMOTE_BASE_DIR="\$HOME/servers${set_id}/${DIRECTORY_NAME}/${WORLD_ID_LISTS_STR}"

        echo "# [log check] LID=${lid}, SET_ID=${set_id} ($WORLD_ID_LISTS) on $MACHINE"
        echo ssh "$MACHINE" "cd ${REMOTE_BASE_DIR}; grep -E ',WolrdDB|,WorldDB|,GameDB|,AccountDB|,Done| autosend_info| redenvelope_info' Test.log* || echo 'No match in Test.log*'"
    done
    echo
}

run_wait_done() {
    echo ">>> MODULE: waitdone (echo wait commands for ',Done')"
    echo "==== [WAIT ALL LISTS DONE - SAMPLE GREP COMMANDS] ===="
    for lid in "${all_lists[@]}"; do
        set_id="${list_setid[$lid]}"
        read AREA MACHINE _ < <(get_area_machine_and_generator "$ENVIRONMENT" "$set_id")
        DIRECTORY_NAME="merge_${AREA}_${FILE_NAME}_${ENVIRONMENT}"
        WORLD_ID_LISTS="${list_worlds[$lid]}"
        WORLD_ID_LISTS_STR=${WORLD_ID_LISTS// /_}
        REMOTE_BASE_DIR="\$HOME/servers${set_id}/${DIRECTORY_NAME}/${WORLD_ID_LISTS_STR}"

        echo "# [wait-check] LID=${lid}, SET_ID=${set_id} ($WORLD_ID_LISTS) on $MACHINE"
        echo ssh "$MACHINE" "cd ${REMOTE_BASE_DIR}; grep -q ',Done' Test.log* 2>/dev/null"
    done
    echo
}

run_post_backup() {
    echo ">>> MODULE: postbackup (echo Post DB backup)"

    echo "==== [POST BACKUP] Global First List (AccountDB) ===="
    read AREA MACHINE _ < <(get_area_machine_and_generator "$ENVIRONMENT" "$FIRST_SETID_GLOBAL")
    DIRECTORY_NAME="merge_${AREA}_${FILE_NAME}_${ENVIRONMENT}"
    WORLD_ID_LISTS_FIRST_GLOBAL="${list_worlds[$FIRST_LID_GLOBAL]}"
    WORLD_ID_LISTS_STR_FIRST_GLOBAL=${WORLD_ID_LISTS_FIRST_GLOBAL// /_}
    REMOTE_BASE_DIR_FIRST_GLOBAL="\$HOME/servers${FIRST_SETID_GLOBAL}/${DIRECTORY_NAME}/${WORLD_ID_LISTS_STR_FIRST_GLOBAL}"
    REMOTE_MERGE_INI_FIRST_GLOBAL="${REMOTE_BASE_DIR_FIRST_GLOBAL}/merge_setup.ini"

    echo "# [Post-AccountDB] First global list: $FIRST_LID_GLOBAL ($WORLD_ID_LISTS_FIRST_GLOBAL) on $MACHINE"
    echo ssh "$MACHINE" "\$HOME/bin/merge_db_backup \"${REMOTE_MERGE_INI_FIRST_GLOBAL}\" _${FILE_NAME}_Post AccountDB"
    echo

    echo "==== [POST BACKUP] Per SET_ID First List (GameDB) ===="
    for set_id in "${!set_first_list[@]}"; do
        FIRST_LID="${set_first_list[$set_id]}"
        read AREA MACHINE _ < <(get_area_machine_and_generator "$ENVIRONMENT" "$set_id")
        DIRECTORY_NAME="merge_${AREA}_${FILE_NAME}_${ENVIRONMENT}"
        WORLD_ID_LISTS_FIRST_SET="${list_worlds[$FIRST_LID]}"
        WORLD_ID_LISTS_STR_FIRST_SET=${WORLD_ID_LISTS_FIRST_SET// /_}
        REMOTE_BASE_DIR_FIRST_SET="\$HOME/servers${set_id}/${DIRECTORY_NAME}/${WORLD_ID_LISTS_STR_FIRST_SET}"
        REMOTE_MERGE_INI_FIRST_SET="${REMOTE_BASE_DIR_FIRST_SET}/merge_setup.ini"

        echo "# [Post-GameDB] SET_ID=${set_id} first list: $FIRST_LID ($WORLD_ID_LISTS_FIRST_SET) on $MACHINE"
        echo ssh "$MACHINE" "\$HOME/bin/merge_db_backup \"${REMOTE_MERGE_INI_FIRST_SET}\" _${FILE_NAME}_Post GameDB"
    done
    echo

    echo "==== [POST BACKUP] Each List (WorldDB) ===="
    for lid in "${all_lists[@]}"; do
        set_id="${list_setid[$lid]}"
        read AREA MACHINE _ < <(get_area_machine_and_generator "$ENVIRONMENT" "$set_id")
        DIRECTORY_NAME="merge_${AREA}_${FILE_NAME}_${ENVIRONMENT}"
        WORLD_ID_LISTS="${list_worlds[$lid]}"
        WORLD_ID_LISTS_STR=${WORLD_ID_LISTS// /_}
        REMOTE_BASE_DIR="\$HOME/servers${set_id}/${DIRECTORY_NAME}/${WORLD_ID_LISTS_STR}"
        REMOTE_MERGE_INI="${REMOTE_BASE_DIR}/merge_setup.ini"

        echo "# [Post-WorldDB] LID=${lid}, SET_ID=${set_id} ($WORLD_ID_LISTS) on $MACHINE"
        echo ssh "$MACHINE" "\$HOME/bin/merge_db_backup \"${REMOTE_MERGE_INI}\" _${FILE_NAME}_Post WorldDB"
    done
    echo
}

run_check_diff() {
    echo ">>> MODULE: check (echo merge_check, with ENVIRONMENT)"
    echo "==== [CHECK PRE vs POST] ===="
    for lid in "${all_lists[@]}"; do
        set_id="${list_setid[$lid]}"
        read AREA MACHINE _ < <(get_area_machine_and_generator "$ENVIRONMENT" "$set_id")
        DIRECTORY_NAME="merge_${AREA}_${FILE_NAME}_${ENVIRONMENT}"
        WORLD_ID_LISTS="${list_worlds[$lid]}"
        WORLD_ID_LISTS_STR=${WORLD_ID_LISTS// /_}
        REMOTE_BASE_DIR="\$HOME/servers${set_id}/${DIRECTORY_NAME}/${WORLD_ID_LISTS_STR}"
        REMOTE_MERGE_INI="${REMOTE_BASE_DIR}/merge_setup.ini"

        echo "# [merge_check] LID=${lid}, SET_ID=${set_id} ($WORLD_ID_LISTS) on $MACHINE (ENV=${ENVIRONMENT})"
        echo ssh "$MACHINE" "\$HOME/bin/merge_check \"${REMOTE_MERGE_INI}\" _${FILE_NAME}_Pre _${FILE_NAME}_Post $ENVIRONMENT"
    done
    echo
}

run_update_db_setup() {
    echo ">>> MODULE: update_db_setup (echo update_db_setup_for_merge, only ENV=live)"

    if [ "$ENVIRONMENT" != "live" ]; then
        echo "# update_db_setup_for_merge is only allowed in live environment. Current ENV=${ENVIRONMENT}."
        echo "# (No commands generated.)"
        echo
        return
    fi

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

run_move_old_server_directory() {
    echo ">>> MODULE: move_old_server_directory (echo 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}"
        echo "$HOME/bin/move_source_wids_directory_after_merge ${ENVIRONMENT} ${DIRECTORY_NAME} ${WORLD_ID_LISTS}"
    done
    echo
}

run_remove_backup() {
    local target="${1:-}"
    local mode="both"

    case "$target" in
        "" )    mode="both" ;;
        pre )   mode="pre"  ;;
        post )  mode="post" ;;
        * )
            echo "Error: removebackup target must be 'pre' or 'post' (or omitted). Got '${target}'." >&2
            exit 1
            ;;
    esac

    echo ">>> MODULE: removebackup (echo drop backup databases mode=${mode}, FILE_NAME=${FILE_NAME})"

    local cmd_pre="\$HOME/bin/drop_database_for_current_machine_by_pattern postfix _${FILE_NAME}_Pre"
    local cmd_post="\$HOME/bin/drop_database_for_current_machine_by_pattern postfix _${FILE_NAME}_Post"
    local cmd=""

    if [ "$mode" = "both" ]; then
        cmd="${cmd_pre}; ${cmd_post}"
    elif [ "$mode" = "pre" ]; then
        cmd="${cmd_pre}"
    else
        cmd="${cmd_post}"
    fi

    if [ "$ENVIRONMENT" = "test" ]; then
        echo "==== [removebackup] ENV=test ===="

        if [ "${#SET_FILTERS[@]}" -eq 0 ]; then
            echo "==== [removebackup] ENV=test, MERGE_* machines (no set filter) ===="
            for m in MERGE_COMMON MERGE_ASIA MERGE_US MERGE_EU; do
                echo "# [removebackup] test env on ${m} for FILE_NAME=${FILE_NAME} (mode=${mode})"
                echo ssh "$m" "${cmd}"
            done
        else
            echo "==== [removebackup] ENV=test, only selected set_id areas ===="
            declare -A target_machines=()
            for sid in "${!set_lists[@]}"; do
                read _area _machine _gen < <(get_area_machine_and_generator "$ENVIRONMENT" "$sid")
                target_machines["$_machine"]=1
            done

            for m in "${!target_machines[@]}"; do
                echo "# [removebackup] test env on ${m} for FILE_NAME=${FILE_NAME} (mode=${mode})"
                echo ssh "$m" "${cmd}"
            done
        fi

    else
        echo "==== [removebackup] ENV=live ===="

        if [ "${#SET_FILTERS[@]}" -eq 0 ]; then
            echo "==== [removebackup] ENV=live, sendscript alldb (no set filter) ===="
            echo "sendscript alldb \"${cmd}\""
        else
            echo "==== [removebackup] ENV=live, only selected set_id MSxx ===="
            declare -A target_machines=()
            for sid in "${!set_lists[@]}"; do
                target_machines["MS${sid}"]=1
            done

            for m in "${!target_machines[@]}"; do
                echo "# [removebackup] live env on ${m} for FILE_NAME=${FILE_NAME} (mode=${mode})"
                echo ssh "$m" "${cmd}"
            done
        fi
    fi

    echo
}

run_remove_source_wids_on_hosts() {
    echo ">>> MODULE: remove_source_wids_on_hosts (echo backup hosts and call remove_source_wids_on_hosts_after_merge, ENV=live only)"

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

    if [ "$ENVIRONMENT" != "live" ]; then
        echo "# remove_source_wids_on_hosts is only allowed in live environment. Current ENV=${ENVIRONMENT}."
        echo "# (No commands generated.)"
        echo
        return
    fi

    echo "==== [remove_source_wids_on_hosts] ENV=live ===="
    echo "# Backup hosts:"
    echo "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}"
        echo "\$HOME/bin/remove_source_wids_on_hosts_after_merge ${WORLD_ID_LISTS}"
    done
    echo
}

case "$MODULE" in
    exec_merge)
        run_generate
        run_db_connect_verify
        run_pre_backup
        run_merge_exec
        run_log_check
        run_wait_done
        run_post_backup
        run_check_diff
        ;;
    before_merge)
        run_generate
        run_reward_before_merge
        ;;
    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) run_merge_exec ;;
    logcheck) run_log_check ;;
    waitdone) run_wait_done ;;
    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 "$REMOVE_BACKUP_TARGET" ;;
    after_merge)
        run_update_db_setup
        run_move_old_server_directory
        run_remove_source_wids_on_hosts
        ;;
    end_merge)
        run_remove_backup "$REMOVE_BACKUP_TARGET"
        ;;
    *)
        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 "== run_merge_n1_echo 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}"
echo "Dry-run (echo) 完成，以上為所有預計執行的指令。"

