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

BASE="/srv/l2server"

# Existing helper scripts (already created earlier)
UNPACK_SCRIPT="${BASE}/unpack_l2server_rar.sh"
POST_SCRIPT="${BASE}/post_unpack_setup.sh"

# Defaults / constants
DB_NAME="interlude"
DB_USER="l2juser"
DB_PASS="143700"

AUTH_PROPS="${BASE}/authserver/config/authserver.properties"
AUTH_DEF_RU="${BASE}/authserver/config/default_russian/authserver.properties"
AUTH_DEF_EN="${BASE}/authserver/config/default_english/authserver.properties"

# We keep a stable dump filename for compose mount:
DUMP_STABLE="${BASE}/interlude_backup.sql"

# Memory tuning for low-RAM hosts
GS_XMX="1024m"          # heap for gameserver loop
AUTH_MEM_LIMIT="384m"   # container hard limit (authserver)
GS_MEM_LIMIT="1600m"    # container hard limit (gameserver) - enough to avoid OOM at start on tiny VMs

log(){ echo "[+] $*"; }
warn(){ echo "[!] $*" >&2; }
die(){ echo "[x] $*" >&2; exit 1; }

need_root(){
  if [[ "${EUID}" -ne 0 ]]; then
    die "Запусти от root: sudo $0 [--fresh] [--no-install] [--dump /path/file.sql] [--rar /path/file.rar]"
  fi
}

have_cmd(){ command -v "$1" >/dev/null 2>&1; }

install_deps(){
  log "Установка зависимостей (docker, compose plugin, unrar, curl)..."
  apt-get update -y
  apt-get install -y docker.io docker-compose-plugin unrar curl
  systemctl enable --now docker
}

detect_external_ip(){
  # best-effort; may return empty
  local ip=""
  ip="$(timeout 5s curl -fsS ifconfig.me 2>/dev/null || true)"
  if [[ "$ip" =~ ^([0-9]{1,3}\.){3}[0-9]{1,3}$ ]]; then
    echo "$ip"
  else
    echo ""
  fi
}

find_any_rar(){
  # search up to depth 6 from / for any .rar
  find / -maxdepth 6 -type f -name "*.rar" 2>/dev/null | head -n 1 || true
}

find_any_dump(){
  # find the largest interlude_backup*.sql file in typical locations (best-effort)
  # We avoid scanning whole disk deeply; just common places.
  local best=""
  best="$(ls -1t /home/*/interlude_backup_*.sql 2>/dev/null | head -n 1 || true)"
  if [[ -n "$best" && -f "$best" ]]; then echo "$best"; return 0; fi

  best="$(ls -1t "${BASE}"/interlude_backup_*.sql 2>/dev/null | head -n 1 || true)"
  if [[ -n "$best" && -f "$best" ]]; then echo "$best"; return 0; fi

  echo ""
}

ensure_base_dirs(){
  mkdir -p "${BASE}"
  cd "${BASE}"
}

run_unpack(){
  local rar_path="${1:-}"
  ensure_base_dirs

  if [[ -n "$rar_path" ]]; then
    [[ -f "$rar_path" ]] || die "RAR не найден по пути: $rar_path"
    log "Распаковка RAR (явно задан): $rar_path"
    unrar x -o+ "$rar_path" /
  else
    if [[ -x "$UNPACK_SCRIPT" ]]; then
      log "Распаковка через существующий скрипт: $UNPACK_SCRIPT"
      # ensure script runs from a valid directory
      cd /
      "$UNPACK_SCRIPT"
    else
      log "Скрипт распаковки не найден/не исполняем. Ищу *.rar сам..."
      local found
      found="$(find_any_rar)"
      [[ -n "$found" ]] || die "Не найден ни один *.rar (искал до глубины 6 от /)."
      log "Найден архив: $found"
      unrar x -o+ "$found" /
    fi
  fi

  # After extraction we expect /srv/l2server/authserver and gameserver
  [[ -d "${BASE}/authserver" ]] || die "После распаковки нет ${BASE}/authserver"
  [[ -d "${BASE}/gameserver" ]] || die "После распаковки нет ${BASE}/gameserver"
}

run_post_unpack(){
  if [[ -x "$POST_SCRIPT" ]]; then
    log "Пост-настройка через существующий скрипт: $POST_SCRIPT"
    "$POST_SCRIPT"
  else
    log "Пост-настройка (fallback) — создаю log/, права, chmod +x *.sh"
    mkdir -p "${BASE}/authserver/log" "${BASE}/gameserver/log"
    chown -R devops:devops "${BASE}/authserver" "${BASE}/gameserver" || true
    find "${BASE}/authserver" -type f -name "*.sh" -exec chmod +x {} \; || true
    find "${BASE}/gameserver" -type f -name "*.sh" -exec chmod +x {} \; || true
  fi
}

ensure_dump_file(){
  local dump_path="${1:-}"
  ensure_base_dirs

  if [[ -z "$dump_path" ]]; then
    dump_path="$(find_any_dump)"
  fi

  [[ -n "$dump_path" ]] || die "SQL-дамп не найден. Передай явно: --dump /path/interlude_backup_*.sql"
  [[ -e "$dump_path" ]] || die "Дамп не существует: $dump_path"
  [[ -f "$dump_path" ]] || die "Дамп должен быть ФАЙЛОМ, а не каталогом/устройством: $dump_path"

  log "Использую дамп: $dump_path"
  # Copy to stable path (compose mounts stable name)
  cp -f "$dump_path" "$DUMP_STABLE"
  chmod 0644 "$DUMP_STABLE" || true

  # sanity check size > 1MB
  local sz
  sz="$(stat -c '%s' "$DUMP_STABLE" 2>/dev/null || echo 0)"
  if [[ "$sz" -lt 1000000 ]]; then
    warn "Размер дампа выглядит подозрительно маленьким (${sz} bytes). Но продолжаю."
  fi
}

patch_configs(){
  ensure_base_dirs

  # Find server.properties
  local gs_props
  gs_props="$(find "${BASE}/gameserver" -maxdepth 6 -type f -name "server.properties" | head -n 1 || true)"
  [[ -n "$gs_props" ]] || die "Не найден gameserver/config/server.properties (искал до глубины 6)."

  local ext_ip
  ext_ip="$(detect_external_ip)"
  if [[ -z "$ext_ip" ]]; then
    warn "Не смог определить внешний IP (ifconfig.me). ExternalHostname не трогаю."
  else
    log "Внешний IP: $ext_ip"
  fi

  log "Правлю GameServer server.properties: DatabaseHost=db, LoginHost=authserver, ExternalHostname=<ip>"
  sed -i \
    -e 's/^\s*DatabaseHost\s*=.*/DatabaseHost = db/' \
    -e 's/^\s*LoginHost\s*=.*/LoginHost = authserver/' \
    "$gs_props"

  if [[ -n "$ext_ip" ]]; then
    sed -i -e "s/^\s*ExternalHostname\s*=.*/ExternalHostname = ${ext_ip}/" "$gs_props"
  fi

  log "Правлю AuthServer authserver.properties: DatabaseHost=db, LoginHost=authserver, DatabaseURL->db, WhiteIpList расширить"
  [[ -f "$AUTH_PROPS" ]] || die "Не найден: $AUTH_PROPS"
  sed -i \
    -e 's/^\s*LoginHost\s*=.*/LoginHost = authserver/' \
    -e 's/^\s*DatabaseHost\s*=.*/DatabaseHost = db/' \
    -e 's#^\s*DatabaseURL\s*=.*#DatabaseURL = jdbc:mysql://db:3306/interlude?useSSL=false&allowPublicKeyRetrieval=true&serverTimezone=UTC&useUnicode=true&characterEncoding=UTF-8#' \
    -e 's/^\s*WhiteIpList\s*=.*/WhiteIpList = 127.0.0.1,0.0.0.0/' \
    "$AUTH_PROPS"

  # Also patch default configs (just in case)
  [[ -f "$AUTH_DEF_RU" ]] && sed -i \
    -e 's/^\s*LoginHost\s*=.*/LoginHost = authserver/' \
    -e 's/^\s*DatabaseHost\s*=.*/DatabaseHost = db/' \
    "$AUTH_DEF_RU" || true

  [[ -f "$AUTH_DEF_EN" ]] && sed -i \
    -e 's/^\s*LoginHost\s*=.*/LoginHost = authserver/' \
    -e 's/^\s*DatabaseHost\s*=.*/DatabaseHost = db/' \
    "$AUTH_DEF_EN" || true

  log "Ограничиваю GameServer heap в GameServer_loop.sh: -Xmx${GS_XMX}"
  local gs_loop="${BASE}/gameserver/GameServer_loop.sh"
  [[ -f "$gs_loop" ]] || die "Не найден: $gs_loop"
  # Replace any -Xmx... with desired; fallback replace known -Xmx3G
  if grep -qE -- '-Xmx' "$gs_loop"; then
    sed -i -E "s/-Xmx[0-9]+[mMgG]/-Xmx${GS_XMX}/g" "$gs_loop"
  else
    warn "В $gs_loop не найден -Xmx. Не меняю."
  fi
}

ensure_db_init(){
  ensure_base_dirs
  mkdir -p "${BASE}/db-init"

  local init="${BASE}/db-init/01-init.sql"
  if [[ ! -f "$init" ]]; then
    log "Создаю ${init}"
    cat >"$init" <<SQL
CREATE DATABASE IF NOT EXISTS ${DB_NAME}
  CHARACTER SET utf8mb4
  COLLATE utf8mb4_unicode_ci;

CREATE USER IF NOT EXISTS '${DB_USER}'@'%' IDENTIFIED BY '${DB_PASS}';
GRANT ALL PRIVILEGES ON ${DB_NAME}.* TO '${DB_USER}'@'%';
FLUSH PRIVILEGES;
SQL
  else
    log "db-init/01-init.sql уже существует — не трогаю."
  fi
}

ensure_compose(){
  ensure_base_dirs

  local yml="${BASE}/docker-compose.yml"
  if [[ ! -f "$yml" ]]; then
    die "docker-compose.yml не найден: $yml (создай или положи существующий)"
  fi

  # Ensure compose mounts stable dump file path
  # Replace any /docker-entrypoint-initdb.d/10-interlude.sql mount to use ./interlude_backup.sql
  if grep -qE '/docker-entrypoint-initdb\.d/10-interlude\.sql' "$yml"; then
    sed -i -E \
      "s#(^\s*-\s*)(\./|/srv/l2server/)?interlude_backup[^:]*\.sql(:/docker-entrypoint-initdb\.d/10-interlude\.sql:ro)#\1./interlude_backup.sql\3#g" \
      "$yml" || true
    sed -i -E \
      "s#(^\s*-\s*)/srv/l2server/interlude_backup[^:]*\.sql(:/docker-entrypoint-initdb\.d/10-interlude\.sql:ro)#\1./interlude_backup.sql\2#g" \
      "$yml" || true
  fi

  # Ensure mem_limit keys exist and correct (authserver/gameserver)
  # We will insert mem_limit if absent; if present, overwrite value.
  perl -0777 -i -pe '
    sub upsert_mem {
      my ($svc, $val, $txt) = @_;
      if ($txt =~ m/(^\s*$svc:\s*\n(?:.*\n)*?)(^\s+mem_limit:\s*)(\S+)/m) {
        $txt =~ s/(^\s*$svc:\s*\n(?:.*\n)*?)(^\s+mem_limit:\s*)\S+/$1$2$val/m;
      } else {
        $txt =~ s/(^\s*$svc:\s*\n)/$1    mem_limit: $val\n/m;
      }
      return $txt;
    }
    $_ = upsert_mem("authserver", "'"$AUTH_MEM_LIMIT"'", $_);
    $_ = upsert_mem("gameserver", "'"$GS_MEM_LIMIT"'", $_);
  ' "$yml"

  log "docker-compose.yml: mem_limit выставлен (authserver=${AUTH_MEM_LIMIT}, gameserver=${GS_MEM_LIMIT}), дамп привязан к ./interlude_backup.sql"
}

compose_fresh_start(){
  ensure_base_dirs
  log "Compose fresh start: down -v (удалит volume БД) -> up -d"
  docker compose down -v || true
  docker compose up -d
}

compose_normal_start(){
  ensure_base_dirs
  log "Compose start: up -d"
  docker compose up -d
}

health_checks(){
  ensure_base_dirs

  log "Проверка статуса контейнеров..."
  docker compose ps

  log "Проверка портов на хосте (2106/9014/7777)..."
  ss -tulnp | grep -E '2106|9014|7777' || true

  log "Проверка таблиц в БД (interlude)..."
  docker exec -it l2-db mariadb -u"${DB_USER}" -p"${DB_PASS}" -e "USE ${DB_NAME}; SHOW TABLES LIMIT 5;" || true

  log "Хвост логов GameServer (файл на хосте)..."
  tail -n 50 "${BASE}/gameserver/log/stdout.log" 2>/dev/null || true

  log "Хвост логов AuthServer (файл на хосте)..."
  tail -n 50 "${BASE}/authserver/log/stdout.log" 2>/dev/null || true
}

usage(){
  cat <<USAGE
Использование:
  sudo /srv/l2server/setup_l2_docker_all.sh [опции]

Опции:
  --fresh            Fresh start: docker compose down -v (по умолчанию)
  --no-fresh         Не удалять volume БД (не переимпортировать дамп)
  --no-install       Не ставить пакеты (docker/unrar/curl)
  --dump /path.sql   Явно указать путь к дампу interlude_backup_*.sql
  --rar  /path.rar   Явно указать путь к rar-архиву сервера
USAGE
}

main(){
  need_root

  local do_install=1
  local do_fresh=1
  local dump_path=""
  local rar_path=""

  while [[ $# -gt 0 ]]; do
    case "$1" in
      --fresh) do_fresh=1; shift ;;
      --no-fresh) do_fresh=0; shift ;;
      --no-install) do_install=0; shift ;;
      --dump) dump_path="${2:-}"; shift 2 ;;
      --rar) rar_path="${2:-}"; shift 2 ;;
      -h|--help) usage; exit 0 ;;
      *) die "Неизвестный аргумент: $1 (см. --help)" ;;
    esac
  done

  [[ -d "$BASE" ]] || mkdir -p "$BASE"

  if [[ "$do_install" -eq 1 ]]; then
    install_deps
  else
    log "Пропускаю установку пакетов (--no-install)."
  fi

  have_cmd docker || die "docker не найден"
  docker compose version >/dev/null 2>&1 || die "docker compose plugin не найден (docker compose)."

  # Unpack server files
  run_unpack "$rar_path"

  # Post-unpack: perms, logs, chmod
  run_post_unpack

  # Ensure dump file exists and is a FILE, then copy to stable name
  ensure_dump_file "$dump_path"

  # Ensure db init exists
  ensure_db_init

  # Patch configs (db/authserver hostnames, IP, whitelist, Xmx)
  patch_configs

  # Ensure compose references stable dump and has mem limits
  ensure_compose

  # Start compose
  if [[ "$do_fresh" -eq 1 ]]; then
    compose_fresh_start
  else
    compose_normal_start
  fi

  # Quick checks
  health_checks

  log "Готово. Сервер должен быть доступен по портам 2106/7777 на внешнем IP (см. ExternalHostname в server.properties)."
}

main "$@"
