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

ROOT=""

OLD_PREFIX="remnawave-tg-shop"
NEW_PREFIX="remnawave-minishop"
OLD_DB_VOLUME="${OLD_PREFIX}-db-data"
NEW_DB_VOLUME="${NEW_PREFIX}-db-data"
OLD_CADDY_VOLUMES=("${OLD_PREFIX}-caddy-data" "${OLD_PREFIX}-caddy-config")
NEW_CADDY_VOLUMES=("${NEW_PREFIX}-caddy-data" "${NEW_PREFIX}-caddy-config")
# Container names span three eras: original ``remnawave-tg-shop*`` (≤ v2.7.0),
# the renamed but still single-container ``remnawave-minishop*`` (v3.1.0 –
# v3.3.x), and the split-arch stack introduced in v3.4.0 (backend / worker /
# frontend / migrate / postgres / redis, plus optional caddy). The list is
# used only to stop existing containers before migration, so it is safe —
# and idempotent — to include every known name from every era.
KNOWN_CONTAINERS=(
  # v2.7.0 (upstream remnawave-tg-shop):
  "${OLD_PREFIX}"
  "${OLD_PREFIX}-db"
  "${OLD_PREFIX}-caddy"
  # v3.1.0 – v3.2.x (renamed but still one container):
  "${NEW_PREFIX}"
  "${NEW_PREFIX}-db"
  "${NEW_PREFIX}-caddy"
  # v3.4.0+ (split architecture):
  "${NEW_PREFIX}-backend"
  "${NEW_PREFIX}-worker"
  "${NEW_PREFIX}-frontend"
  "${NEW_PREFIX}-migrate"
  "${NEW_PREFIX}-postgres"
  "${NEW_PREFIX}-redis"
)

log() {
  printf '%s\n' "$*"
}

die() {
  printf 'Ошибка: %s\n' "$*" >&2
  exit 1
}

require_cmd() {
  command -v "$1" >/dev/null 2>&1 || die "Не найдено обязательное средство \`$1\` в PATH."
}

resolve_root() {
  if [[ -n "${PROJECT_ROOT:-}" ]]; then
    [[ -d "$PROJECT_ROOT" ]] || die "PROJECT_ROOT не существует: $PROJECT_ROOT"
    (cd -- "$PROJECT_ROOT" >/dev/null && pwd -P)
    return
  fi

  local git_root
  if git_root="$(git rev-parse --show-toplevel 2>/dev/null)"; then
    printf '%s\n' "$git_root"
    return
  fi

  pwd -P
}

run() {
  log "+ $*"
  "$@"
}

container_exists() {
  docker inspect "$1" >/dev/null 2>&1
}

container_running() {
  [[ "$(docker inspect -f '{{.State.Running}}' "$1" 2>/dev/null || true)" == "true" ]]
}

stop_container() {
  local name="$1"

  if ! container_exists "$name"; then
    return 1
  fi

  if container_running "$name"; then
    docker stop "$name" >/dev/null
  fi
  docker rm "$name" >/dev/null
}

volume_exists() {
  docker volume inspect "$1" >/dev/null 2>&1
}

volume_is_empty() {
  docker run --rm -v "$1:/data" alpine sh -c 'test -z "$(find /data -mindepth 1 -print -quit)"' >/dev/null 2>&1
}

copy_volume() {
  local source="$1"
  local target="$2"

  if ! volume_exists "$source"; then
    log "  - Пропускаю том \`$source\`: исходный том не найден."
    return 1
  fi

  if volume_exists "$target" && ! volume_is_empty "$target"; then
    log "  - Пропускаю том \`$target\`: он уже не пустой."
    return 1
  fi

  if ! volume_exists "$target"; then
    die "Целевой том \`$target\` не создан Compose. Сначала нужно подготовить новый стек в режиме \`--no-start\`."
  fi

  docker run --rm -v "$source:/from:ro" -v "$target:/to" alpine sh -c 'cd /from && cp -a . /to/'
}

is_old_postgres_host() {
  grep -Eq "^[[:space:]]*POSTGRES_HOST[[:space:]]*=[[:space:]]*${OLD_PREFIX}-db[[:space:]]*(#.*)?$" "$ROOT/.env"
}

is_new_postgres_host() {
  grep -Eq "^[[:space:]]*POSTGRES_HOST[[:space:]]*=[[:space:]]*${NEW_PREFIX}-db[[:space:]]*(#.*)?$" "$ROOT/.env"
}

update_postgres_host() {
  if is_old_postgres_host; then
    sed -i.bak -E "s|^([[:space:]]*POSTGRES_HOST[[:space:]]*=[[:space:]]*)${OLD_PREFIX}-db([[:space:]]*(#.*)?)$|\\1${NEW_PREFIX}-db\\2|" "$ROOT/.env"
    log "  - \`.env\` обновлён, резервная копия сохранена в \`.env.bak\`."
  elif is_new_postgres_host; then
    log "  - \`POSTGRES_HOST\` уже указывает на новый контейнер, ничего менять не нужно."
  else
    log "  - \`POSTGRES_HOST\` не похож на старую схему, пропускаю изменение."
  fi
}

main() {
  require_cmd git
  require_cmd docker
  docker info >/dev/null

  ROOT="$(resolve_root)"

  local compose_file="${COMPOSE_FILE:-docker-compose.yml}"
  local target_branch="${TARGET_BRANCH:-main}"
  local git_remote="${GIT_REMOTE:-origin}"
  local new_origin_url="${NEW_ORIGIN_URL:-}"
  local assume_yes="${ASSUME_YES:-0}"
  local current_origin
  local current_branch
  local remote_ref
  local head_commit
  local compose_has_build=0
  local compose_has_caddy=0
  local -a compose_cmd
  local -a running_containers=()
  local -a summary=()
  local -a up_args
  local name
  local source
  local target
  local answer

  if [[ $compose_file != /* ]]; then
    compose_file="$ROOT/$compose_file"
  fi
  [[ -f "$compose_file" ]] || die "Compose-файл не найден: $compose_file"
  [[ -e "$ROOT/.git" ]] || die "Скрипт нужно запускать из корня git-репозитория."
  [[ -f "$ROOT/.env" ]] || die "Не найден \`.env\` в корне репозитория."

  if docker compose version >/dev/null 2>&1; then
    compose_cmd=(docker compose)
  elif command -v docker-compose >/dev/null 2>&1; then
    compose_cmd=(docker-compose)
  else
    die "Не найден ни \`docker compose\`, ни \`docker-compose\`."
  fi

  if [[ -n "$(git -C "$ROOT" status --porcelain=v1)" ]]; then
    die "В рабочем дереве есть незакоммиченные изменения. Сначала сохраните их, чтобы миграция не затёрла чужие правки."
  fi

  if grep -Eq '^[[:space:]]*build:[[:space:]]*' "$compose_file"; then
    compose_has_build=1
  fi
  if grep -Eq '^[[:space:]]*caddy:[[:space:]]*$' "$compose_file"; then
    compose_has_caddy=1
  fi

  for name in "${KNOWN_CONTAINERS[@]}"; do
    if container_exists "$name"; then
      running_containers+=("$name")
    fi
  done

  current_origin="$(git -C "$ROOT" remote get-url "$git_remote")"
  if [[ -n "$new_origin_url" && "$current_origin" != "$new_origin_url" ]]; then
    summary+=( "обновить $git_remote с \`$current_origin\` на \`$new_origin_url\`" )
  fi
  summary+=( "скачать ветку \`$target_branch\` из \`$git_remote\`" )
  if volume_exists "$OLD_DB_VOLUME"; then
    summary+=( "проверить том БД \`$OLD_DB_VOLUME\` и перенести в \`$NEW_DB_VOLUME\` при необходимости" )
  fi
  if ((compose_has_caddy)); then
    for i in 0 1; do
      source="${OLD_CADDY_VOLUMES[$i]}"
      target="${NEW_CADDY_VOLUMES[$i]}"
      if volume_exists "$source"; then
        summary+=( "проверить том \`$source\` и перенести в \`$target\` при необходимости" )
      fi
    done
  fi
  if is_old_postgres_host; then
    summary+=( "обновить \`POSTGRES_HOST\` в \`.env\`" )
  fi
  summary+=( "подготовить новый стек через Compose в режиме \`--no-start\`" )
  summary+=( "запустить compose-файл \`$(basename "$compose_file")\`" )

  if [[ "$assume_yes" != "1" ]]; then
    if [[ ! -t 0 ]]; then
      die "Скрипт ожидает интерактивное подтверждение. Запустите с \`ASSUME_YES=1\` для неинтерактивного режима."
    fi
    log "План миграции:"
    for name in "${summary[@]}"; do
      log "  - $name"
    done
    read -r -p "Продолжить? [y/N]: " answer
    case "$answer" in
      y|Y|yes|YES|Yes)
        ;;
      *)
        die "Миграция отменена пользователем."
        ;;
    esac
  fi

  log "1. Останавливаю старый стек"
  if ((${#running_containers[@]})); then
    for name in "${running_containers[@]}"; do
      stop_container "$name"
      log "  - контейнер \`$name\` остановлен/удалён"
    done
  else
    log "  - запущенных контейнеров старой схемы не найдено"
  fi

  if [[ -n "$new_origin_url" && "$current_origin" != "$new_origin_url" ]]; then
    log "2. Обновляю origin"
    run git -C "$ROOT" remote set-url "$git_remote" "$new_origin_url"
  else
    log "2. Origin уже актуален, пропускаю"
  fi

  log "3. Обновляю git до ветки \`$target_branch\`"
  run git -C "$ROOT" fetch "$git_remote" "$target_branch"

  current_branch="$(git -C "$ROOT" branch --show-current || true)"
  if [[ -z "$current_branch" ]]; then
    current_branch="$(git -C "$ROOT" rev-parse --abbrev-ref HEAD)"
  fi

  if [[ "$current_branch" != "$target_branch" ]]; then
    if git -C "$ROOT" show-ref --verify --quiet "refs/heads/$target_branch"; then
      run git -C "$ROOT" switch "$target_branch"
    else
      run git -C "$ROOT" switch -c "$target_branch" --track "$git_remote/$target_branch"
    fi
  else
    log "  - уже на ветке \`$target_branch\`"
  fi

  remote_ref="$(git -C "$ROOT" rev-parse "$git_remote/$target_branch")"
  head_commit="$(git -C "$ROOT" rev-parse HEAD)"
  if [[ "$head_commit" != "$remote_ref" ]]; then
    run git -C "$ROOT" pull --ff-only "$git_remote" "$target_branch"
  else
    log "  - локальная ветка уже совпадает с удалённой, \`git pull\` не нужен"
  fi

  log "4. Обновляю \`.env\`"
  update_postgres_host

  log "5. Подготавливаю новый стек через Compose"
  if ((compose_has_build)); then
    run "${compose_cmd[@]}" -f "$compose_file" up --no-start --build
  else
    run "${compose_cmd[@]}" -f "$compose_file" up --no-start
  fi

  log "6. Переношу тома"
  if copy_volume "$OLD_DB_VOLUME" "$NEW_DB_VOLUME"; then
    log "  - БД перенесена в \`$NEW_DB_VOLUME\`"
  fi
  if ((compose_has_caddy)); then
    for i in 0 1; do
      source="${OLD_CADDY_VOLUMES[$i]}"
      target="${NEW_CADDY_VOLUMES[$i]}"
      if copy_volume "$source" "$target"; then
        log "  - \`$source\` перенесён в \`$target\`"
      fi
    done
  fi

  log "7. Запускаю новый стек"
  if ((compose_has_build)); then
    up_args=(up -d --build --remove-orphans)
  else
    up_args=(up -d --remove-orphans)
  fi
  run "${compose_cmd[@]}" -f "$compose_file" "${up_args[@]}"
  run "${compose_cmd[@]}" -f "$compose_file" ps

  log "Готово."
}

main "$@"
