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

########################################
# Usage / 參數
#
# 基本：
#   $0 <ENVIRONMENT: test|live> <FILE_NAME> [MODULE] [removebackup_target] [SetID...]
#
# ENVIRONMENT:
#   test | live
#
# FILE_NAME:
#   對應 $HOME/bin/merge_n1/${ENVIRONMENT}/${FILE_NAME}
#
# MODULE（可省略，預設 exec_merge）：
#   單一基礎功能 (single module):
#     generate                    : 產生合併目錄與檔案
#     reward_before_merge         : 合併前發送獎勵 fastcgi
#     dbcheck                     : 檢查 DB 連線
#     prebackup                   : 合併前 DB 備份
#     restore_prebackup           : 由 Pre 備份 DB 還原回原 DB
#     merge                       : 執行合併
#     logcheck                    : 初次檢查 log
#     waitdone                    : 等待各 List 出現 ,Done
#     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（可加 pre/post）
#     merge_rollback_partial_worlds : AccountDB 部分回滾（忽略第一個 WorldID，回滾其餘 worlds + 刪 fake_worlds）
#
#   群組功能 (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 =
#         removebackup
#
# removebackup_target（僅 removebackup 模組可用，可省略）：
#   pre  : 只刪 _${FILE_NAME}_Pre
#   post : 只刪 _${FILE_NAME}_Post
#   (未提供) : 同時刪 _${FILE_NAME}_Pre & _${FILE_NAME}_Post
#
# SetID 過濾參數（可省略，可多個）：
#   形式：S11 / S21 / S31 ...
#   - 未提供：處理 input file 內全部 set_id
#   - 有提供：只處理指定 set_id 的 world_id_lists（套用至所有模組，含 removebackup）
#
# 範例：
#   # 預設（exec_merge，全部 set_id）
#   run_merge_n1 live myfile
#
#   # 只跑 set_id=11 的完整 exec_merge
#   run_merge_n1 live myfile S11
#   run_merge_n1 live myfile exec_merge S11
#
#   # 同時跑 set_id=21 與 31
#   run_merge_n1 live myfile exec_merge S21 S31
#
#   # 只刪 pre backup，且只針對 set_id=21
#   run_merge_n1 live myfile removebackup pre S21
########################################

KNOWN_MODULES=(
  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
  merge_rollback_partial_worlds
)

is_known_module() {
  local m="$1"
  for x in "${KNOWN_MODULES[@]}"; do
    if [ "$x" = "$m" ]; then
      return 0
    fi
  done
  return 1
}

if [ "$#" -lt 2 ]; then
  echo "Usage: $0 <ENVIRONMENT: test|live> <FILE_NAME> [MODULE] [removebackup_target] [SetID...]" >&2
  exit 1
fi

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

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

MODULE="exec_merge"
BACKUP_TARGET="both"   # for removebackup: both|pre|post
declare -a SET_FILTER_TOKENS=()

# 第三參數如果是已知 module 就當 module，否則視為 set filter（仍走預設 exec_merge）
if [ "$#" -ge 1 ]; then
  if is_known_module "$1"; then
    MODULE="$1"
    shift 1
  fi
fi

# 剩餘參數解析：
# - removebackup 可接受 pre/post
# - SetID token: S<digits>
while [ "$#" -gt 0 ]; do
  arg="$1"
  shift 1

  if [ "$MODULE" = "removebackup" ] && { [ "$arg" = "pre" ] || [ "$arg" = "post" ]; }; then
    BACKUP_TARGET="$arg"
    continue
  fi

  if [[ "$arg" =~ ^S[0-9]+$ ]]; then
    SET_FILTER_TOKENS+=( "$arg" )
    continue
  fi

  echo "Error: unknown argument '$arg'." >&2
  exit 1
done

# 建立 set_id filter set（若未提供則為空＝全選）
declare -A SELECTED_SETIDS=()
if [ "${#SET_FILTER_TOKENS[@]}" -gt 0 ]; then
  for t in "${SET_FILTER_TOKENS[@]}"; do
    sid="${t#S}"
    SELECTED_SETIDS["$sid"]=1
  done
fi

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      # list_worlds[LID]  = "1101 1107 1108"
declare -A list_setid       # list_setid[LID]   = 11
declare -A set_lists        # set_lists[11]     = "L1 L2"
declare -A set_first_list   # set_first_list[11]= "L1"
declare -a all_lists        # all LID, in file order

########################################
# 讀取檔案，建立結構 & 檢查每行 SET_ID 一致性
# - 若有 SetID filter，只納入指定 set_id 的 lines
########################################
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

  # SetID filter：若有指定 set_id，且此行不在清單，跳過
  if [ "${#SET_FILTER_TOKENS[@]}" -gt 0 ]; then
    if [[ -z "${SELECTED_SETIDS[$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
  echo "Error: no valid WorldID lists found in file (after SetID filter, if any): $INPUT_FILE" >&2
  exit 1
fi

########################################
# SHOW 結構（方便除錯）
########################################
echo "Input file      : $INPUT_FILE"
echo "Environment     : $ENVIRONMENT"
echo "FILE_NAME       : $FILE_NAME"
if [ "${#SET_FILTER_TOKENS[@]}" -gt 0 ]; then
  echo "SetID filter    : ${SET_FILTER_TOKENS[*]}"
else
  echo "SetID filter    : (none, all set_id)"
fi
echo "Lists by SET_ID :"
mapfile -t SORTED_SET_IDS < <(printf '%s\n' "${!set_lists[@]}" | sort -n)
for set_id in "${SORTED_SET_IDS[@]}"; do
  echo "  SET_ID $set_id => ${set_lists[$set_id]}"
done
echo

########################################
# 全域輔助變數（第一個 List）
########################################
FIRST_LID_GLOBAL="${all_lists[0]}"
FIRST_SETID_GLOBAL="${list_setid[$FIRST_LID_GLOBAL]}"

########################################
# 工具函式：依 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   # waitdone 模組每次檢查間隔秒數

########################################
# 模組：產生合併目錄與檔案
########################################
run_generate() {
  echo ">>> MODULE: generate (create merge directories and files)"
  for set_id in "${SORTED_SET_IDS[@]}"; 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 set_id in "${SORTED_SET_IDS[@]}"; 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"
      ssh "$MACHINE" "\$HOME/bin/merge_db_connect_verify \"${REMOTE_MERGE_INI}\""
    done
  done
  echo
}

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

  # a. 第一個 List (global) - 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 "[Pre-AccountDB] First global list: $FIRST_LID_GLOBAL ($WORLD_ID_LISTS_FIRST_GLOBAL) on $MACHINE"
  ssh "$MACHINE" "\$HOME/bin/merge_db_backup \"${REMOTE_MERGE_INI_FIRST_GLOBAL}\" _${FILE_NAME}_Pre AccountDB"

  # b. 每個 SetID 下的第一個 List - GameDB
  for set_id in "${SORTED_SET_IDS[@]}"; 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"
    ssh "$MACHINE" "\$HOME/bin/merge_db_backup \"${REMOTE_MERGE_INI_FIRST_SET}\" _${FILE_NAME}_Pre GameDB"
  done

  # c. 每個 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"
    ssh "$MACHINE" "\$HOME/bin/merge_db_backup \"${REMOTE_MERGE_INI}\" _${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)"

  # a. 第一個 List (global) - 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 "[Restore-AccountDB] First global list: $FIRST_LID_GLOBAL ($WORLD_ID_LISTS_FIRST_GLOBAL) on $MACHINE"
  ssh "$MACHINE" "\$HOME/bin/merge_db_restore \"${REMOTE_MERGE_INI_FIRST_GLOBAL}\" _${FILE_NAME}_Pre AccountDB"

  # b. 每個 SetID 下的第一個 List - GameDB
  for set_id in "${SORTED_SET_IDS[@]}"; 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"
    ssh "$MACHINE" "\$HOME/bin/merge_db_restore \"${REMOTE_MERGE_INI_FIRST_SET}\" _${FILE_NAME}_Pre GameDB"
  done

  # c. 每個 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"
    ssh "$MACHINE" "\$HOME/bin/merge_db_restore \"${REMOTE_MERGE_INI}\" _${FILE_NAME}_Pre WorldDB"
  done
  echo
}

########################################
# 模組：執行合併作業 (merge_exec)
########################################
run_merge_exec() {
  echo ">>> MODULE: merge (run 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"
    ssh "$MACHINE" "cd ${REMOTE_BASE_DIR}; 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
    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"
    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
}

########################################
# 模組：等待所有 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
      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}"

      if ssh "$MACHINE" "cd ${REMOTE_BASE_DIR}; grep -q ',Done' Test.log* 2>/dev/null"; then
        echo "[DONE] LID=${lid}, SET_ID=${set_id} ($WORLD_ID_LISTS) on $MACHINE"
      else
        echo "[WAIT] LID=${lid}, SET_ID=${set_id} ($WORLD_ID_LISTS) 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
}

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

  # a. 第一個 List (global) - AccountDB
  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"
  ssh "$MACHINE" "\$HOME/bin/merge_db_backup \"${REMOTE_MERGE_INI_FIRST_GLOBAL}\" _${FILE_NAME}_Post AccountDB"

  # b. 每個 SetID 下的第一個 List - GameDB
  echo "==== [POST BACKUP] Per SET_ID First List (GameDB) ===="
  for set_id in "${SORTED_SET_IDS[@]}"; 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"
    ssh "$MACHINE" "\$HOME/bin/merge_db_backup \"${REMOTE_MERGE_INI_FIRST_SET}\" _${FILE_NAME}_Post GameDB"
  done

  # c. 每個 List - WorldDB
  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"
    ssh "$MACHINE" "\$HOME/bin/merge_db_backup \"${REMOTE_MERGE_INI}\" _${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
    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})"
    ssh "$MACHINE" "\$HOME/bin/merge_check \"${REMOTE_MERGE_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（刪除 Pre/Post 備份 DB，可選 pre/post）
# - 也支援 SetID 過濾
########################################
run_remove_backup() {
  echo ">>> MODULE: removebackup (drop backup databases; target=${BACKUP_TARGET})"

  drop_cmds=""
  case "$BACKUP_TARGET" in
    pre)
      drop_cmds="\$HOME/bin/drop_database_for_current_machine_by_pattern postfix _${FILE_NAME}_Pre"
      ;;
    post)
      drop_cmds="\$HOME/bin/drop_database_for_current_machine_by_pattern postfix _${FILE_NAME}_Post"
      ;;
    both)
      drop_cmds="\$HOME/bin/drop_database_for_current_machine_by_pattern postfix _${FILE_NAME}_Pre; \
\$HOME/bin/drop_database_for_current_machine_by_pattern postfix _${FILE_NAME}_Post"
      ;;
    *)
      echo "Error: invalid removebackup target '${BACKUP_TARGET}' (expected pre|post|both)." >&2
      exit 1
      ;;
  esac

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

    declare -A MACH_SEEN=()
    declare -a MACHINES=()

    # 若未指定 set filter，保留原行為：全部 merge machines + common
    if [ "${#SET_FILTER_TOKENS[@]}" -eq 0 ]; then
      MACHINES=(MERGE_COMMON MERGE_ASIA MERGE_US MERGE_EU)
    else
      # 指定 set filter：MERGE_COMMON + 涉及到的 area
      MACHINES=(MERGE_COMMON)
      for set_id in "${SORTED_SET_IDS[@]}"; do
        read AREA _ _ < <(get_area_machine_and_generator "$ENVIRONMENT" "$set_id")
        case "$AREA" in
          asia) MACHINES+=(MERGE_ASIA) ;;
          us)   MACHINES+=(MERGE_US) ;;
          eu)   MACHINES+=(MERGE_EU) ;;
        esac
      done
    fi

    # 去重
    declare -a MACHINES_UNIQ=()
    for m in "${MACHINES[@]}"; do
      if [[ -z "${MACH_SEEN[$m]+x}" ]]; then
        MACH_SEEN["$m"]=1
        MACHINES_UNIQ+=( "$m" )
      fi
    done

    for m in "${MACHINES_UNIQ[@]}"; do
      echo "[removebackup] test env on ${m} for FILE_NAME=${FILE_NAME} target=${BACKUP_TARGET}"
      ssh "$m" "$drop_cmds"
    done
  else
    echo "==== [removebackup] ENV=live ===="

    # 若未指定 set filter，保留原行為：sendscript alldb
    if [ "${#SET_FILTER_TOKENS[@]}" -eq 0 ]; then
      echo "[removebackup] live env via sendscript alldb for FILE_NAME=${FILE_NAME} target=${BACKUP_TARGET}"
      sendscript alldb "$drop_cmds"
      echo
      return
    fi

    # 有 set filter：僅對涉及的 set_id 對應 MSxx 執行
    declare -A LIVE_MACH_SEEN=()
    for set_id in "${SORTED_SET_IDS[@]}"; do
      read _ MACHINE _ < <(get_area_machine_and_generator "$ENVIRONMENT" "$set_id")
      if [[ -z "${LIVE_MACH_SEEN[$MACHINE]+x}" ]]; then
        LIVE_MACH_SEEN["$MACHINE"]=1
        echo "[removebackup] live env on ${MACHINE} for SET_ID=${set_id}, FILE_NAME=${FILE_NAME} target=${BACKUP_TARGET}"
        ssh "$MACHINE" "$drop_cmds"
      fi
    done
  fi

  echo
}

########################################
# 模組：merge_rollback_partial_worlds
# - 使用「第一個 List(global)」的 merge_setup.ini
# - 在該 set_id 對應的 MACHINE 上執行 $HOME/bin/merge_rollback_partial_worlds
########################################
run_merge_rollback_partial_worlds() {
  echo ">>> MODULE: merge_rollback_partial_worlds (rollback AccountDB partial worlds by merge_setup.ini)"

  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 "[merge_rollback_partial_worlds] First global list: $FIRST_LID_GLOBAL ($WORLD_ID_LISTS_FIRST_GLOBAL) on $MACHINE"
  echo "[merge_rollback_partial_worlds] INI: $REMOTE_MERGE_INI_FIRST_GLOBAL"
  ssh "$MACHINE" "\$HOME/bin/merge_rollback_partial_worlds \"${REMOTE_MERGE_INI_FIRST_GLOBAL}\""
  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
    ;;
  after_merge)
    run_update_db_setup
    run_move_old_server_directory
    run_remove_source_wids_on_hosts
    ;;
  end_merge)
    run_remove_backup
    ;;

  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 ;;
  merge_rollback_partial_worlds) run_merge_rollback_partial_worlds ;;

  *)
    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}"

