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

########################################
# Usage / 參數
#
# $1: ENVIRONMENT (test|live)
# $2: FILE_NAME
# $3+: [MODULE] [removebackup_target] [SetID...]
#
# 說明：
# - MODULE 可省略，預設 exec_merge
# - SetID filter 形式為 S11 / S21 / S31 ...（可多個）
#   未加：針對 FILE_NAME 內所有 world_id_lists 全部產生指令
#   有加：僅針對指定 set_id 的 world_id_lists 產生指令（套用至所有模組，含 removebackup）
# - removebackup 模組後可接 pre/post；不加則預設 both（同時刪 Pre+Post）
#
# 單一基礎功能 (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)
#   logerror                      : grep -i 'error' Test.log*（echo，可單獨跑）
#   waitdone                      : 等待各 List 出現 ,Done (echo)
#   postbackup                    : 合併後 DB 備份 (echo)
#   check                         : merge_check 比對差異（僅當 merge_check exit=2 時才 grep -i error）(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 備份 DB (echo，可加 pre/post)
#   merge_rollback_partial_worlds : AccountDB 部分回滾 (echo, remote)
#                                  參數: <ABS merge_setup.ini> <${FILE_NAME}_Pre>
#
# 群組功能 (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
#
#   re_run_exec_merge =
#       removebackup post
#       + merge_rollback_partial_worlds
#       + merge
#       + logcheck
#       + waitdone
#       + postbackup
#       + check
########################################

hr() { printf '%s\n' "============================================================"; }
step() { hr; printf '[STEP] %s\n' "$1"; hr; }
info() { printf '[INFO] %s\n' "$1"; }
warn() { printf '[WARN] %s\n' "$1" >&2; }
err()  { printf '[ERROR] %s\n' "$1" >&2; }

print_usage() {
  cat <<'EOF' >&2
============================================================
run_merge_n1_echo Usage
============================================================
Usage:
  run_merge_n1_echo <ENVIRONMENT: test|live> <FILE_NAME> [MODULE] [removebackup_target] [SetID...]

ENVIRONMENT:
  test | live

FILE_NAME:
  input file = $HOME/bin/merge_n1/<ENVIRONMENT>/<FILE_NAME>

單一基礎功能 (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)
  logerror                      : grep -i 'error' Test.log*（echo，可單獨跑）
  waitdone                      : 等待各 List 出現 ,Done (echo)
  postbackup                    : 合併後 DB 備份 (echo)
  check                         : merge_check（僅當 exit=2 時才 grep -i error）(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 備份 DB (echo，可加 pre/post)
  merge_rollback_partial_worlds : AccountDB 部分回滾 (echo, remote)
                                 參數: <ABS merge_setup.ini> <${FILE_NAME}_Pre>

群組功能 (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
  re_run_exec_merge =
      removebackup post + merge_rollback_partial_worlds + merge + logcheck + waitdone + postbackup + check

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）

範例：
  run_merge_n1_echo live myfile
  run_merge_n1_echo live myfile exec_merge S21 S31
  run_merge_n1_echo live myfile removebackup post S21
  run_merge_n1_echo live myfile re_run_exec_merge S21
  run_merge_n1_echo live myfile logerror S21
EOF
}

########################################
# Help
########################################
if [ "${1-}" = "-h" ] || [ "${1-}" = "--help" ] || [ "${1-}" = "help" ]; then
  print_usage
  exit 0
fi

########################################
# 參數解析
########################################
KNOWN_MODULES=(
  exec_merge before_merge after_merge end_merge re_run_exec_merge
  generate reward_before_merge dbcheck prebackup restore_prebackup merge logcheck logerror 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" x
  for x in "${KNOWN_MODULES[@]}"; do
    if [ "$x" = "$m" ]; then
      return 0
    fi
  done
  return 1
}

if [ "$#" -lt 2 ]; then
  print_usage
  exit 1
fi

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

case "$ENVIRONMENT" in
  test|live) ;;
  *)
    err "ENVIRONMENT must be 'test' or 'live', got '$ENVIRONMENT'."
    print_usage
    exit 1
    ;;
esac

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

# 第三參數若是 module 則吃掉
if [ "$#" -ge 1 ] && is_known_module "${1-}"; then
  MODULE="$1"
  shift 1
fi

# 剩餘參數：removebackup_target / SetID tokens
while [ "$#" -gt 0 ]; do
  arg="$1"
  shift 1

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

  # re_run_exec_merge: 允許多帶 post（忽略）；pre 視為錯誤
  if [ "$MODULE" = "re_run_exec_merge" ] && { [ "$arg" = "pre" ] || [ "$arg" = "post" ]; }; then
    if [ "$arg" = "post" ]; then
      warn "re_run_exec_merge already includes 'removebackup post'; ignore redundant 'post' argument."
      continue
    fi
    err "re_run_exec_merge does not accept 'pre'. It always uses removebackup post."
    exit 1
  fi

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

  err "Unknown argument '$arg'."
  print_usage
  exit 1
done

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
  err "input file not found: $INPUT_FILE"
  exit 1
fi
if [ ! -s "$INPUT_FILE" ]; then
  err "input file is empty: $INPUT_FILE"
  exit 1
fi

########################################
# 記錄開始時間（dry-run）
########################################
START_TS=$(date +%s)
START_TIME=$(date '+%Y-%m-%d %H:%M:%S')

step "run_merge_n1_echo START"
info "Start time   : ${START_TIME}"
info "ENV          : ${ENVIRONMENT}"
info "FILE_NAME    : ${FILE_NAME}"
info "MODULE       : ${MODULE}"
info "INPUT_FILE   : ${INPUT_FILE}"
if [ "$MODULE" = "removebackup" ]; then
  info "BACKUP_TARGET: ${BACKUP_TARGET}"
fi
if [ "${#SET_FILTER_TOKENS[@]}" -gt 0 ]; then
  info "SetID filter : ${SET_FILTER_TOKENS[*]}"
else
  info "SetID filter : (none, all set_id)"
fi

########################################
# 資料結構
########################################
declare -A list_worlds
declare -A list_setid
declare -A set_lists
declare -A set_first_list
declare -a all_lists

########################################
# 讀取檔案，建立結構 & 檢查每行 SET_ID 一致性 + SetID 過濾
########################################
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
    err "line $LINE_NO has no WorldID: '$line'"
    exit 1
  fi

  LINE_SET_ID=""
  for wid in "${WORLDS[@]}"; do
    if ! [[ "$wid" =~ ^[0-9]+$ ]]; then
      err "line $LINE_NO has non-numeric WorldID '$wid'."
      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
        err "line $LINE_NO has mixed SET_ID: expected $LINE_SET_ID, got $wid_set_id (WorldID: $wid)."
        exit 1
      fi
    fi
  done

  # SetID filter
  if [ "${#SET_FILTER_TOKENS[@]}" -gt 0 ] && [[ -z "${SELECTED_SETIDS[$LINE_SET_ID]+x}" ]]; then
    continue
  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
  err "no valid WorldID lists found in file (after SetID filter, if any): $INPUT_FILE"
  exit 1
fi

mapfile -t SORTED_SET_IDS < <(printf '%s\n' "${!set_lists[@]}" | sort -n)

step "Parsed Lists Summary"
info "Total lists (after filter): ${#all_lists[@]}"
info "SetID groups              : ${#SORTED_SET_IDS[@]}"
info "Lists by SET_ID:"
for set_id in "${SORTED_SET_IDS[@]}"; do
  info "  SET_ID ${set_id} => ${set_lists[$set_id]}"
done

########################################
# 全域輔助變數（過濾後第一個 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="" machine="" 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
    err "unsupported SET_ID '$sid' (only 10~39 supported)."
    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

########################################
# echo 輸出（ssh / local）
########################################
ssh_echo() {
  local machine="$1"
  local lid="$2"
  local set_id="$3"
  local worlds="$4"
  local remote_dir="$5"
  local action="$6"
  local cmd="$7"

  printf '# [%s] SET_ID=%s MACHINE=%s\n' "$lid" "$set_id" "$machine"
  printf '#   worlds : %s\n' "$worlds"
  printf '#   dir    : %s\n' "$remote_dir"
  printf '#   action : %s\n' "$action"
  printf 'ssh %s "%s"\n' "$machine" "$cmd"
}

local_echo() {
  local lid="$1"
  local set_id="$2"
  local worlds="$3"
  local action="$4"
  local cmd="$5"

  printf '# [%s] SET_ID=%s\n' "$lid" "$set_id"
  printf '#   worlds : %s\n' "$worlds"
  printf '#   action : %s\n' "$action"
  printf '%s\n' "$cmd"
}

########################################
# 模組：generate (echo)
########################################
run_generate() {
  step "MODULE: generate (echo)"
  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]}"

    for lid in "${LIDS[@]}"; do
      WORLD_ID_LISTS="${list_worlds[$lid]}"
      ssh_echo "$MACHINE" "$lid" "$set_id" "$WORLD_ID_LISTS" "(n/a)" \
        "generate merge preparation" \
        "\$HOME/bin/${GENERATOR} ${DIRECTORY_NAME} ${WORLD_ID_LISTS}"
    done
  done
  echo
}

########################################
# 模組：reward_before_merge（echo）
########################################
run_reward_before_merge() {
  step "MODULE: reward_before_merge (echo)"
  for lid in "${all_lists[@]}"; do
    set_id="${list_setid[$lid]}"
    WORLD_ID_LISTS="${list_worlds[$lid]}"
    local_echo "$lid" "$set_id" "$WORLD_ID_LISTS" \
      "reward_before_merge (local)" \
      "\$HOME/bin/send_fastcgi_all_once_n1_by_world_reward_merge ${ENVIRONMENT} ${WORLD_ID_LISTS}"
  done
  echo
}

########################################
# 模組：dbcheck（echo）
########################################
run_db_connect_verify() {
  step "MODULE: dbcheck (echo)"
  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"

      ssh_echo "$MACHINE" "$lid" "$set_id" "$WORLD_ID_LISTS" "$REMOTE_BASE_DIR" \
        "merge_db_connect_verify ${REMOTE_MERGE_INI}" \
        "\$HOME/bin/merge_db_connect_verify \"${REMOTE_MERGE_INI}\""
    done
  done
  echo
}

########################################
# 模組：prebackup（echo）
########################################
run_pre_backup() {
  step "MODULE: prebackup (echo)"

  # 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"

  ssh_echo "$MACHINE" "$FIRST_LID_GLOBAL" "$FIRST_SETID_GLOBAL" "$WORLD_ID_LISTS_FIRST_GLOBAL" "$REMOTE_BASE_DIR_FIRST_GLOBAL" \
    "Pre-AccountDB backup" \
    "\$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"

    ssh_echo "$MACHINE" "$FIRST_LID" "$set_id" "$WORLD_ID_LISTS_FIRST_SET" "$REMOTE_BASE_DIR_FIRST_SET" \
      "Pre-GameDB backup" \
      "\$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"

    ssh_echo "$MACHINE" "$lid" "$set_id" "$WORLD_ID_LISTS" "$REMOTE_BASE_DIR" \
      "Pre-WorldDB backup" \
      "\$HOME/bin/merge_db_backup \"${REMOTE_MERGE_INI}\" _${FILE_NAME}_Pre WorldDB"
  done
  echo
}

########################################
# 模組：restore_prebackup（echo）
########################################
run_restore_prebackup() {
  step "MODULE: restore_prebackup (echo)"

  # 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"

  ssh_echo "$MACHINE" "$FIRST_LID_GLOBAL" "$FIRST_SETID_GLOBAL" "$WORLD_ID_LISTS_FIRST_GLOBAL" "$REMOTE_BASE_DIR_FIRST_GLOBAL" \
    "Restore-AccountDB from Pre" \
    "\$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"

    ssh_echo "$MACHINE" "$FIRST_LID" "$set_id" "$WORLD_ID_LISTS_FIRST_SET" "$REMOTE_BASE_DIR_FIRST_SET" \
      "Restore-GameDB from Pre" \
      "\$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"

    ssh_echo "$MACHINE" "$lid" "$set_id" "$WORLD_ID_LISTS" "$REMOTE_BASE_DIR" \
      "Restore-WorldDB from Pre" \
      "\$HOME/bin/merge_db_restore \"${REMOTE_MERGE_INI}\" _${FILE_NAME}_Pre WorldDB"
  done
  echo
}

########################################
# 模組：merge（echo）
########################################
run_merge_exec() {
  step "MODULE: merge (echo)"
  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}"

    ssh_echo "$MACHINE" "$lid" "$set_id" "$WORLD_ID_LISTS" "$REMOTE_BASE_DIR" \
      "run ./merge_exec (nohup)" \
      "cd ${REMOTE_BASE_DIR}; nohup ./merge_exec &"
  done
  echo
}

########################################
# 模組：logcheck（echo）
########################################
run_log_check() {
  step "MODULE: logcheck (echo)"
  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}"

    ssh_echo "$MACHINE" "$lid" "$set_id" "$WORLD_ID_LISTS" "$REMOTE_BASE_DIR" \
      "grep initial keywords in Test.log*" \
      "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
}

########################################
# 模組：logerror（echo）
########################################
run_log_error() {
  step "MODULE: logerror (echo)"
  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}"

    ssh_echo "$MACHINE" "$lid" "$set_id" "$WORLD_ID_LISTS" "$REMOTE_BASE_DIR" \
      "grep -i error in Test.log*" \
      "cd ${REMOTE_BASE_DIR}; grep -E -i 'error' Test.log* || echo 'No error in Test.log*'"
  done
  echo
}

########################################
# 模組：waitdone（echo）
########################################
run_wait_done() {
  step "MODULE: waitdone (echo)"
  info "This is dry-run; only prints the check commands (no looping)."
  info "WAIT_INTERVAL=${WAIT_INTERVAL}s (runtime version will loop with sleep)"

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

    ssh_echo "$MACHINE" "$lid" "$set_id" "$WORLD_ID_LISTS" "$REMOTE_BASE_DIR" \
      "check ',Done' in Test.log*" \
      "cd ${REMOTE_BASE_DIR}; grep -q ',Done' Test.log* 2>/dev/null"
  done
  echo
}

########################################
# 模組：postbackup（echo）
########################################
run_post_backup() {
  step "MODULE: postbackup (echo)"

  # 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"

  ssh_echo "$MACHINE" "$FIRST_LID_GLOBAL" "$FIRST_SETID_GLOBAL" "$WORLD_ID_LISTS_FIRST_GLOBAL" "$REMOTE_BASE_DIR_FIRST_GLOBAL" \
    "Post-AccountDB backup" \
    "\$HOME/bin/merge_db_backup \"${REMOTE_MERGE_INI_FIRST_GLOBAL}\" _${FILE_NAME}_Post 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"

    ssh_echo "$MACHINE" "$FIRST_LID" "$set_id" "$WORLD_ID_LISTS_FIRST_SET" "$REMOTE_BASE_DIR_FIRST_SET" \
      "Post-GameDB backup" \
      "\$HOME/bin/merge_db_backup \"${REMOTE_MERGE_INI_FIRST_SET}\" _${FILE_NAME}_Post 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"

    ssh_echo "$MACHINE" "$lid" "$set_id" "$WORLD_ID_LISTS" "$REMOTE_BASE_DIR" \
      "Post-WorldDB backup" \
      "\$HOME/bin/merge_db_backup \"${REMOTE_MERGE_INI}\" _${FILE_NAME}_Post WorldDB"
  done
  echo
}

########################################
# 模組：check（echo）
########################################
run_check_diff() {
  step "MODULE: check (echo) - merge_check; if rc==2 then grep -i error"
  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"

    ssh_echo "$MACHINE" "$lid" "$set_id" "$WORLD_ID_LISTS" "$REMOTE_BASE_DIR" \
      "merge_check compare Pre/Post; if rc==2 then grep -i error" \
      "\
\$HOME/bin/merge_check \"${REMOTE_MERGE_INI}\" _${FILE_NAME}_Pre _${FILE_NAME}_Post $ENVIRONMENT; \
rc=\$?; \
if [ \$rc -eq 2 ]; then \
  cd ${REMOTE_BASE_DIR}; \
  grep -E -i 'error' Test.log* || echo 'No error in Test.log*'; \
fi; \
exit \$rc"
  done
  echo
}

########################################
# 模組：update_db_setup（echo）
########################################
run_update_db_setup() {
  step "MODULE: update_db_setup (echo)"

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

  for lid in "${all_lists[@]}"; do
    set_id="${list_setid[$lid]}"
    WORLD_ID_LISTS="${list_worlds[$lid]}"
    local_echo "$lid" "$set_id" "$WORLD_ID_LISTS" \
      "update_db_setup_for_merge (local)" \
      "\$HOME/bin/update_db_setup_for_merge ${WORLD_ID_LISTS}"
  done
  echo
}

########################################
# 模組：move_old_server_directory（echo）
########################################
run_move_old_server_directory() {
  step "MODULE: move_old_server_directory (echo)"

  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]}"

    local_echo "$lid" "$set_id" "$WORLD_ID_LISTS" \
      "move_source_wids_directory_after_merge (local)" \
      "\$HOME/bin/move_source_wids_directory_after_merge ${ENVIRONMENT} ${DIRECTORY_NAME} ${WORLD_ID_LISTS}"
  done
  echo
}

########################################
# 模組：remove_source_wids_on_hosts（echo）
########################################
run_remove_source_wids_on_hosts() {
  step "MODULE: remove_source_wids_on_hosts (echo)"

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

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

  info "Backup hosts:"
  printf '%s\n' "cp -arf ${HOSTS_FILE} ${HOSTS_BAK}"
  echo

  info "Call remove_source_wids_on_hosts_after_merge for each WORLD_ID_LISTS:"
  for lid in "${all_lists[@]}"; do
    set_id="${list_setid[$lid]}"
    WORLD_ID_LISTS="${list_worlds[$lid]}"
    local_echo "$lid" "$set_id" "$WORLD_ID_LISTS" \
      "remove_source_wids_on_hosts_after_merge (local)" \
      "\$HOME/bin/remove_source_wids_on_hosts_after_merge ${WORLD_ID_LISTS}"
  done
  echo
}

########################################
# 模組：removebackup（echo）
########################################
run_remove_backup() {
  step "MODULE: removebackup (echo, target=${BACKUP_TARGET})"

  local 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"
      ;;
    *)
      err "invalid removebackup target '${BACKUP_TARGET}' (expected pre|post|both)."
      exit 1
      ;;
  esac

  if [ "$ENVIRONMENT" = "test" ]; then
    info "ENV=test"

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

    # set_id filter: MERGE_COMMON + 對應區域機器；未過濾則全列
    if [ "${#SET_FILTER_TOKENS[@]}" -eq 0 ]; then
      MACHINES=(MERGE_COMMON MERGE_ASIA MERGE_US MERGE_EU)
    else
      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

    for m in "${MACHINES[@]}"; do
      if [[ -z "${MACH_SEEN[$m]+x}" ]]; then
        MACH_SEEN["$m"]=1
        printf '# drop on %s (target=%s)\n' "$m" "$BACKUP_TARGET"
        printf 'ssh %s "%s"\n' "$m" "$drop_cmds"
      fi
    done
  else
    info "ENV=live"

    if [ "${#SET_FILTER_TOKENS[@]}" -eq 0 ]; then
      info "live via sendscript alldb"
      printf 'sendscript alldb "%s"\n' "$drop_cmds"
      echo
      return
    fi

    declare -A LIVE_SEEN=()
    for set_id in "${SORTED_SET_IDS[@]}"; do
      read _ MACHINE _ < <(get_area_machine_and_generator "$ENVIRONMENT" "$set_id")
      if [[ -z "${LIVE_SEEN[$MACHINE]+x}" ]]; then
        LIVE_SEEN["$MACHINE"]=1
        printf '# drop on %s (SET_ID=%s, target=%s)\n' "$MACHINE" "$set_id" "$BACKUP_TARGET"
        printf 'ssh %s "%s"\n' "$MACHINE" "$drop_cmds"
      fi
    done
  fi
  echo
}

########################################
# 模組：merge_rollback_partial_worlds（echo）
########################################
run_merge_rollback_partial_worlds() {
  step "MODULE: merge_rollback_partial_worlds (echo)"

  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"

  ssh_echo "$MACHINE" "$FIRST_LID_GLOBAL" "$FIRST_SETID_GLOBAL" "$WORLD_ID_LISTS_FIRST_GLOBAL" "$REMOTE_BASE_DIR_FIRST_GLOBAL" \
    "merge_rollback_partial_worlds (remote; args: <merge_setup.ini> <${FILE_NAME}_Pre>)" \
    "\$HOME/bin/merge_rollback_partial_worlds \"${REMOTE_MERGE_INI_FIRST_GLOBAL}\" \"${FILE_NAME}_Pre\""
  echo
}

########################################
# 群組：re_run_exec_merge（echo）
########################################
run_re_run_exec_merge() {
  step "GROUP MODULE: re_run_exec_merge (echo)"
  info "Sequence: removebackup post + merge_rollback_partial_worlds + merge + logcheck + waitdone + postbackup + check"
  echo

  local old_target="$BACKUP_TARGET"
  BACKUP_TARGET="post"
  run_remove_backup
  BACKUP_TARGET="$old_target"

  run_merge_rollback_partial_worlds
  run_merge_exec
  run_log_check
  run_wait_done
  run_post_backup
  run_check_diff
}

########################################
# 模組執行入口
########################################
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
    ;;
  re_run_exec_merge)
    run_re_run_exec_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 ;;
  logerror) run_log_error ;;
  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 ;;
  *)
    err "unknown MODULE '${MODULE}'."
    print_usage
    exit 1
    ;;
esac

########################################
# 結束時間 & 總耗時（dry-run）
########################################
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))

step "run_merge_n1_echo FINISHED"
info "Start time : ${START_TIME}"
info "End time   : ${END_TIME}"
printf 'Elapsed    : %02d 小時 %02d 分 %02d 秒\n' "$ELAPSED_H" "$ELAPSED_M" "$ELAPSED_S"
info "ENV=${ENVIRONMENT}, FILE_NAME=${FILE_NAME}, MODULE=${MODULE}"
info "Dry-run (echo) 完成，以上為所有預計執行的指令。"

