#!/usr/bin/env bash

set -euo pipefail

SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
ROOT_DIR="$(cd "${SCRIPT_DIR}/.." && pwd)"
RELEASE_ENV_FILE="${ROOT_DIR}/release.env"

MAIN_DIR="${ROOT_DIR}/compose/main"
VULNDB_DIR="${ROOT_DIR}/compose/vulndb"
MAIN_COMPOSE_FILE="${MAIN_DIR}/docker-compose.prod.server-client.yml"
VULNDB_COMPOSE_FILE="${VULNDB_DIR}/docker-compose.prod.vulndb.yml"
VULNDB_DUMPS_DIR="${VULNDB_DIR}/dumps"

if [[ -t 1 ]]; then
  COLOR_RED="$(printf '\033[31m')"
  COLOR_GREEN="$(printf '\033[32m')"
  COLOR_YELLOW="$(printf '\033[33m')"
  COLOR_BLUE="$(printf '\033[34m')"
  COLOR_CYAN="$(printf '\033[36m')"
  COLOR_BOLD="$(printf '\033[1m')"
  COLOR_RESET="$(printf '\033[0m')"
else
  COLOR_RED=""
  COLOR_GREEN=""
  COLOR_YELLOW=""
  COLOR_BLUE=""
  COLOR_CYAN=""
  COLOR_BOLD=""
  COLOR_RESET=""
fi

STACK_ALL="all"
STACK_MAIN="main"
STACK_VULNDB="vulndb"

print_banner() {
  cat <<EOF
${COLOR_CYAN}${COLOR_BOLD}
========================================
         ЦЕНТР УПРАВЛЕНИЯ NRUNNER
========================================
${COLOR_RESET}
EOF
}

terminal_width() {
  local width

  width="$(tput cols 2>/dev/null || true)"
  if [[ -z "${width}" || "${width}" -lt 60 ]]; then
    width=78
  fi

  printf '%s' "${width}"
}

print_rule() {
  local char="${1:-=}"
  local width

  width="$(terminal_width)"
  printf '%*s\n' "${width}" '' | tr ' ' "${char}"
}

print_screen_title() {
  local title="$1"
  local subtitle="${2:-}"

  printf "\n%s%s" "${COLOR_CYAN}" "${COLOR_BOLD}"
  print_rule "="
  printf " %s\n" "${title}"
  if [[ -n "${subtitle}" ]]; then
    printf "%s\n" "${subtitle}"
  fi
  print_rule "="
  printf "%s" "${COLOR_RESET}"
}

print_menu_item() {
  local number="$1"
  local label="$2"
  local hint="${3:-}"

  printf "  %s%s%s  %s" "${COLOR_BOLD}" "${number}" "${COLOR_RESET}" "${label}"
  if [[ -n "${hint}" ]]; then
    printf " %s%s%s" "${COLOR_CYAN}" "${hint}" "${COLOR_RESET}"
  fi
  printf "\n"
}

MENU_NUMBERS=()
MENU_LABELS=()
MENU_HINTS=()
MENU_CHOICE=""

menu_reset() {
  MENU_NUMBERS=()
  MENU_LABELS=()
  MENU_HINTS=()
}

menu_add_item() {
  MENU_NUMBERS+=("$1")
  MENU_LABELS+=("$2")
  MENU_HINTS+=("${3:-}")
}

render_select_menu() {
  local title="$1"
  local subtitle="$2"
  local selected="$3"
  local index prefix

  clear 2>/dev/null || true
  print_banner
  if [[ "${title}" == "Главное меню" ]]; then
    print_service_dashboard
  fi
  print_screen_title "${title}" "${subtitle}"
  printf "\n"

  for index in "${!MENU_NUMBERS[@]}"; do
    if [[ "${index}" -eq "${selected}" ]]; then
      prefix="${COLOR_GREEN}${COLOR_BOLD}>${COLOR_RESET}"
    else
      prefix=" "
    fi

    printf "%s " "${prefix}"
    print_menu_item "${MENU_NUMBERS[${index}]}" "${MENU_LABELS[${index}]}" "${MENU_HINTS[${index}]}"
  done

  printf "\n%sСтрелки вверх/вниз - навигация, Enter - выбрать, цифра - быстрый выбор.%s\n" "${COLOR_CYAN}" "${COLOR_RESET}"
}

select_menu() {
  local title="$1"
  local subtitle="${2:-}"
  local selected=0
  local key rest index

  if [[ "${#MENU_NUMBERS[@]}" -eq 0 ]]; then
    error "Меню пустое."
    exit 1
  fi

  while true; do
    render_select_menu "${title}" "${subtitle}" "${selected}"

    IFS= read -rsn1 key
    case "${key}" in
      "")
        MENU_CHOICE="${MENU_NUMBERS[${selected}]}"
        return
        ;;
      [0-9])
        for index in "${!MENU_NUMBERS[@]}"; do
          if [[ "${MENU_NUMBERS[${index}]}" == "${key}" ]]; then
            MENU_CHOICE="${key}"
            return
          fi
        done
        ;;
      $'\033')
        IFS= read -rsn2 -t 0.1 rest || rest=""
        case "${rest}" in
          "[A")
            selected=$((selected - 1))
            if [[ "${selected}" -lt 0 ]]; then
              selected=$(("${#MENU_NUMBERS[@]}" - 1))
            fi
            ;;
          "[B")
            selected=$((selected + 1))
            if [[ "${selected}" -ge "${#MENU_NUMBERS[@]}" ]]; then
              selected=0
            fi
            ;;
        esac
        ;;
    esac
  done
}

print_section() {
  printf "\n%s%s[%s]%s %s\n" "${COLOR_BLUE}" "${COLOR_BOLD}" "$(date '+%H:%M:%S')" "${COLOR_RESET}" "$1"
}

info() {
  printf "%s[INFO]%s %s\n" "${COLOR_CYAN}" "${COLOR_RESET}" "$1"
}

success() {
  printf "%s[ OK ]%s %s\n" "${COLOR_GREEN}" "${COLOR_RESET}" "$1"
}

warn() {
  printf "%s[WARN]%s %s\n" "${COLOR_YELLOW}" "${COLOR_RESET}" "$1"
}

error() {
  printf "%s[ERR ]%s %s\n" "${COLOR_RED}" "${COLOR_RESET}" "$1" >&2
}

pause() {
  if [[ -t 0 ]]; then
    printf "\nНажмите Enter, чтобы продолжить..."
    read -r _
  fi
}

require_command() {
  local command_name="$1"

  if ! command -v "${command_name}" >/dev/null 2>&1; then
    error "Не найдена обязательная команда: ${command_name}"
    exit 1
  fi
}

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

is_interactive_terminal() {
  [[ -t 0 && -t 1 ]]
}

docker_compose() {
  docker compose --env-file "${RELEASE_ENV_FILE}" "$@"
}

main_compose() {
  docker compose \
    --env-file "${RELEASE_ENV_FILE}" \
    --env-file "${MAIN_DIR}/env.server" \
    --env-file "${MAIN_DIR}/env.client" \
    -f "${MAIN_COMPOSE_FILE}" \
    "$@"
}

vulndb_compose() {
  docker compose \
    --env-file "${RELEASE_ENV_FILE}" \
    --env-file "${VULNDB_DIR}/env.vulndb" \
    -f "${VULNDB_COMPOSE_FILE}" \
    --profile migrate \
    "$@"
}

vulndb_compose_with_dump() {
  local dump_name="$1"
  shift

  VULN_IMPORT_DUMP="${dump_name}" docker compose \
    --env-file "${RELEASE_ENV_FILE}" \
    --env-file "${VULNDB_DIR}/env.vulndb" \
    -f "${VULNDB_COMPOSE_FILE}" \
    --profile migrate \
    --profile import \
    "$@"
}

compose_ps_lines() {
  local stack="$1"

  case "${stack}" in
    "${STACK_MAIN}")
      docker compose --env-file "${RELEASE_ENV_FILE}" -f "${MAIN_COMPOSE_FILE}" ps --all --format '{{.Service}}\t{{.State}}' 2>/dev/null || true
      ;;
    "${STACK_VULNDB}")
      docker compose --env-file "${RELEASE_ENV_FILE}" -f "${VULNDB_COMPOSE_FILE}" ps --all --format '{{.Service}}\t{{.State}}' 2>/dev/null || true
      ;;
  esac
}

service_state_from_lines() {
  local ps_output="$1"
  local service_name="$2"

  awk -F '\t' -v service_name="${service_name}" '$1 == service_name { print $2; exit }' <<<"${ps_output}"
}

service_status_badge() {
  local raw_state="$1"
  local normalized_state

  normalized_state="$(printf '%s' "${raw_state}" | tr '[:upper:]' '[:lower:]')"

  if [[ -z "${normalized_state}" ]]; then
    printf "%s○ не поднят%s" "${COLOR_YELLOW}" "${COLOR_RESET}"
    return
  fi

  case "${normalized_state}" in
    running*|healthy*)
      printf "%s● работает%s" "${COLOR_GREEN}" "${COLOR_RESET}"
      ;;
    exited*|dead*|removing*)
      printf "%s● ошибка%s" "${COLOR_RED}" "${COLOR_RESET}"
      ;;
    *)
      printf "%s● !: %s%s" "${COLOR_BLUE}" "${raw_state}" "${COLOR_RESET}"
      ;;
  esac
}

print_service_status_line() {
  local stack_ps="$1"
  local label="$2"
  local service_name="$3"
  local raw_state

  raw_state="$(service_state_from_lines "${stack_ps}" "${service_name}")"
  printf "  %-18s %s\n" "${label}" "$(service_status_badge "${raw_state}")"
}

print_service_dashboard() {
  local main_ps vulndb_ps

  main_ps="$(compose_ps_lines "${STACK_MAIN}")"
  vulndb_ps="$(compose_ps_lines "${STACK_VULNDB}")"

  printf "%s%sСостояние сервисов%s\n" "${COLOR_BOLD}" "${COLOR_CYAN}" "${COLOR_RESET}"
  print_service_status_line "${main_ps}" "main/db" "db"
  print_service_status_line "${main_ps}" "main/cache" "cache"
  print_service_status_line "${main_ps}" "main/server" "nrunner-server"
  print_service_status_line "${main_ps}" "main/client" "nrunner-client"
  print_service_status_line "${vulndb_ps}" "vulndb/db" "db"
  print_service_status_line "${vulndb_ps}" "vulndb/api" "api-service"
  printf "\n"
}

format_publishers() {
  jq -r '
    def publishers_to_text:
      if . == null or . == [] then
        "-"
      else
        map(
          ((.URL // "0.0.0.0") | tostring) + ":" +
          ((.PublishedPort // .Published // "-") | tostring) + "->" +
          ((.TargetPort // .Target // "-") | tostring) + "/" +
          ((.Protocol // "tcp") | tostring)
        ) | join(", ")
      end;

    def as_array:
      if type == "array" then . else [.] end;

    as_array
    | if length == 0 then
        empty
      else
        (["СЕРВИС", "СОСТОЯНИЕ", "ПОРТЫ"] | @tsv),
        (.[] | [(.Service // .Name // "-"), (.State // .Status // "-"), ((.Publishers // []) | publishers_to_text)] | @tsv)
      end
  '
}

ensure_prerequisites() {
  require_command docker

  if [[ ! -f "${RELEASE_ENV_FILE}" ]]; then
    error "Не найден файл версий поставки: ${RELEASE_ENV_FILE}"
    exit 1
  fi

  if ! docker info >/dev/null 2>&1; then
    error "Служба Docker недоступна. Запустите Docker и повторите попытку."
    exit 1
  fi

  if ! docker compose version >/dev/null 2>&1; then
    error "Docker Compose v2 недоступен. Установите его и повторите попытку."
    exit 1
  fi
}

ensure_env_file() {
  local target_file="$1"
  local example_file="$2"

  if [[ -f "${target_file}" ]]; then
    info "Используется существующий файл: ${target_file}"
    return
  fi

  if [[ ! -f "${example_file}" ]]; then
    error "Не найден файл-пример: ${example_file}"
    exit 1
  fi

  cp "${example_file}" "${target_file}"
  warn "Создан файл ${target_file} из примера. Проверьте и скорректируйте значения перед использованием."
}

env_backup_path() {
  local target_file="$1"
  local timestamp

  timestamp="$(date '+%Y%m%d-%H%M%S')"
  printf '%s.bak.%s' "${target_file}" "${timestamp}"
}

backup_env_file() {
  local target_file="$1"
  local backup_file

  if [[ ! -f "${target_file}" ]]; then
    return
  fi

  backup_file="$(env_backup_path "${target_file}")"
  cp "${target_file}" "${backup_file}"
  success "Создан backup: ${backup_file}"
}

copy_default_env_file() {
  local target_file="$1"
  local example_file="$2"

  cp "${example_file}" "${target_file}"
  success "Создан файл с дефолтными значениями: ${target_file}"
}

is_sensitive_env_key() {
  local key="$1"

  [[ "${key}" =~ (SECRET|PASSWORD|TOKEN|KEY|CREDENTIAL) ]]
}

is_interactive_hidden_env_key() {
  local key="$1"

  case "${key}" in
    NRUNNER_NETWORK_NAME|\
    SERVICE_NAME|\
    APP_ENV|\
    AGENT_DEPLOYMENT_ARTIFACTS_DIR|\
    AGENT_DEPLOYMENT_CA_PATH|\
    LOCAL_DB_TYPE|\
    LOCAL_DB_SSLMODE|\
    REDIS_MAX_RETRIES|\
    REDIS_DIAL_TIMEOUT|\
    REDIS_READ_TIMEOUT|\
    REDIS_WRITE_TIMEOUT|\
    REDIS_APPENDONLY)
      return 0
      ;;
    *)
      return 1
      ;;
  esac
}

prompt_env_value() {
  local key="$1"
  local default_value="$2"
  local answer

  if is_sensitive_env_key "${key}" && [[ -n "${default_value}" ]]; then
    printf "  %s%s%s [%sскрыто%s, Enter - оставить]: " "${COLOR_BOLD}" "${key}" "${COLOR_RESET}" "${COLOR_YELLOW}" "${COLOR_RESET}" >&2
    read -rs answer
    printf "\n" >&2
  else
    printf "  %s%s%s [%s]: " "${COLOR_BOLD}" "${key}" "${COLOR_RESET}" "${default_value}" >&2
    read -r answer
  fi

  if [[ -z "${answer}" ]]; then
    printf '%s' "${default_value}"
  else
    printf '%s' "${answer}"
  fi
}

write_interactive_env_file() {
  local target_file="$1"
  local example_file="$2"
  local env_label="$3"
  local tmp_file
  local line key default_value value

  tmp_file="${target_file}.tmp.$$"
  : >"${tmp_file}"

  print_screen_title "Настройка env: ${env_label}" "Файл: ${target_file}"
  printf "\nEnter оставляет значение из шаблона. Секреты при вводе не отображаются.\n\n"

  while IFS= read -r -u 3 line || [[ -n "${line}" ]]; do
    if [[ "${line}" =~ ^[[:space:]]*$ || "${line}" =~ ^[[:space:]]*# ]]; then
      printf '%s\n' "${line}" >>"${tmp_file}"
      continue
    fi

    if [[ "${line}" =~ ^[A-Za-z_][A-Za-z0-9_]*= ]]; then
      key="${line%%=*}"
      default_value="${line#*=}"
      if is_interactive_hidden_env_key "${key}"; then
        printf '%s\n' "${line}" >>"${tmp_file}"
        continue
      fi
      value="$(prompt_env_value "${key}" "${default_value}")"
      printf '%s=%s\n' "${key}" "${value}" >>"${tmp_file}"
      continue
    fi

    printf '%s\n' "${line}" >>"${tmp_file}"
  done 3<"${example_file}"

  mv "${tmp_file}" "${target_file}"
  success "Интерактивно создан файл: ${target_file}"
}

choose_env_mode() {
  local target_file="$1"
  local example_file="$2"
  local env_label="$3"
  local choice

  if [[ ! -f "${example_file}" ]]; then
    error "Не найден файл-пример: ${example_file}"
    exit 1
  fi

  while true; do
    menu_reset

    if [[ -f "${target_file}" ]]; then
      menu_add_item "1" "Использовать существующий файл" "без изменений"
      menu_add_item "2" "Создать новый из дефолтных значений" "старый будет сохранён"
      menu_add_item "3" "Заполнить новый файл интерактивно" "старый будет сохранён"
      menu_add_item "0" "Отменить развёртывание"
      select_menu "Подготовка окружения: ${env_label}" "Файл уже существует: ${target_file}"
    else
      menu_add_item "1" "Создать из дефолтных значений"
      menu_add_item "2" "Заполнить интерактивно"
      menu_add_item "0" "Отменить развёртывание"
      select_menu "Подготовка окружения: ${env_label}" "Файл ещё не создан: ${target_file}"
    fi

    choice="${MENU_CHOICE}"

    if [[ -f "${target_file}" ]]; then
      case "${choice}" in
        1)
          info "Используется существующий файл: ${target_file}"
          return
          ;;
        2)
          backup_env_file "${target_file}"
          copy_default_env_file "${target_file}" "${example_file}"
          return
          ;;
        3)
          backup_env_file "${target_file}"
          write_interactive_env_file "${target_file}" "${example_file}" "${env_label}"
          return
          ;;
        0)
          error "Развёртывание отменено пользователем."
          exit 1
          ;;
        *)
          warn "Неизвестный пункт меню: ${choice}"
          ;;
      esac
    else
      case "${choice}" in
        1)
          copy_default_env_file "${target_file}" "${example_file}"
          return
          ;;
        2)
          write_interactive_env_file "${target_file}" "${example_file}" "${env_label}"
          return
          ;;
        0)
          error "Развёртывание отменено пользователем."
          exit 1
          ;;
        *)
          warn "Неизвестный пункт меню: ${choice}"
          ;;
      esac
    fi
  done
}

prepare_env_file() {
  local target_file="$1"
  local example_file="$2"
  local env_label="$3"

  if is_interactive_terminal; then
    choose_env_mode "${target_file}" "${example_file}" "${env_label}"
  else
    ensure_env_file "${target_file}" "${example_file}"
  fi
}

prepare_env() {
  local stack="$1"

  case "${stack}" in
    "${STACK_ALL}")
      prepare_env "${STACK_VULNDB}"
      prepare_env "${STACK_MAIN}"
      ;;
    "${STACK_MAIN}")
      prepare_env_file "${MAIN_DIR}/env.server" "${MAIN_DIR}/env.server.example" "серверный env основного стека"
      prepare_env_file "${MAIN_DIR}/env.client" "${MAIN_DIR}/env.client.example" "клиентский env основного стека"
      ;;
    "${STACK_VULNDB}")
      prepare_env_file "${VULNDB_DIR}/env.vulndb" "${VULNDB_DIR}/env.vulndb.example" "env стека VulnDB"
      ;;
    *)
      error "Неизвестный стек: ${stack}"
      exit 1
      ;;
  esac
}

validate_stack() {
  local stack="$1"

  case "${stack}" in
    "${STACK_MAIN}")
      main_compose config >/dev/null
      ;;
    "${STACK_VULNDB}")
      vulndb_compose config >/dev/null
      ;;
    *)
      error "Неизвестный стек: ${stack}"
      exit 1
      ;;
  esac
}

deploy_stack() {
  local stack="$1"

  case "${stack}" in
    "${STACK_ALL}")
      error "Развёртывание обоих стеков одной командой отключено. Выберите main или vulndb."
      exit 1
      ;;
    "${STACK_MAIN}")
      print_section "Подготовка основного стека"
      prepare_env "${STACK_MAIN}"
      validate_stack "${STACK_MAIN}"
      main_compose up -d
      success "Основной стек развёрнут."
      ;;
    "${STACK_VULNDB}")
      print_section "Подготовка стека VulnDB"
      prepare_env "${STACK_VULNDB}"
      validate_stack "${STACK_VULNDB}"
      vulndb_compose up -d
      success "Стек VulnDB развёрнут."
      ;;
    *)
      error "Неизвестный стек: ${stack}"
      exit 1
      ;;
  esac
}

list_vulndb_dumps() {
  mkdir -p "${VULNDB_DUMPS_DIR}"
  find "${VULNDB_DUMPS_DIR}" -maxdepth 1 -type f -printf '%f\n' | sort
}

choose_vulndb_dump() {
  local dumps=()
  local index answer

  mapfile -t dumps < <(list_vulndb_dumps)
  if [[ "${#dumps[@]}" -eq 0 ]]; then
    error "В каталоге ${VULNDB_DUMPS_DIR} нет дампов."
    return 1
  fi

  print_screen_title "Импорт дампа VulnDB" "Каталог: ${VULNDB_DUMPS_DIR}"
  printf "\n"
  for index in "${!dumps[@]}"; do
    printf "  %d) %s\n" "$((index + 1))" "${dumps[${index}]}"
  done
  printf "  0) Отмена\n\n"
  printf "Выберите дамп: "
  read -r answer

  if [[ "${answer}" == "0" ]]; then
    return 1
  fi
  if [[ ! "${answer}" =~ ^[0-9]+$ ]] || ((answer < 1 || answer > ${#dumps[@]})); then
    error "Некорректный номер дампа."
    return 1
  fi

  SELECTED_VULNDB_DUMP="${dumps[$((answer - 1))]}"
}

validate_vulndb_dump() {
  local dump_name="$1"

  if [[ -z "${dump_name}" || "${dump_name}" == */* || "${dump_name}" == "." || "${dump_name}" == ".." ]]; then
    error "Укажите имя файла из каталога ${VULNDB_DUMPS_DIR}, без пути."
    return 1
  fi
  if [[ ! -f "${VULNDB_DUMPS_DIR}/${dump_name}" ]]; then
    error "Дамп не найден: ${VULNDB_DUMPS_DIR}/${dump_name}"
    return 1
  fi
}

import_vulndb_dump() {
  local dump_name="${1:-}"

  print_section "Импорт дампа VulnDB"
  prepare_env "${STACK_VULNDB}"
  validate_stack "${STACK_VULNDB}"
  mkdir -p "${VULNDB_DUMPS_DIR}"

  if [[ -z "${dump_name}" ]]; then
    if ! is_interactive_terminal; then
      error "Укажите имя дампа для неинтерактивного импорта."
      return 1
    fi
    choose_vulndb_dump || return
    dump_name="${SELECTED_VULNDB_DUMP}"
  fi

  validate_vulndb_dump "${dump_name}"

  warn "pg_restore очистит и заменит существующие объекты в базе VulnDB."
  if is_interactive_terminal && ! confirm_action "Импортировать ${dump_name}?"; then
    return
  fi

  info "Проверяю запуск PostgreSQL и выполнение миграций..."
  vulndb_compose up -d db db-migrate

  info "Импортирую ${dump_name}..."
  vulndb_compose_with_dump "${dump_name}" run --rm db-import-dump
  success "Дамп ${dump_name} импортирован в VulnDB."

  vulndb_compose up -d api-service
  success "VulnDB API запущен после импорта."
}

pull_stack() {
  local stack="$1"

  case "${stack}" in
    "${STACK_ALL}")
      print_section "Обновление образов стека VulnDB"
      pull_stack "${STACK_VULNDB}"

      print_section "Обновление образов основного стека"
      pull_stack "${STACK_MAIN}"
      ;;
    "${STACK_MAIN}")
      prepare_env "${STACK_MAIN}"
      validate_stack "${STACK_MAIN}"
      main_compose pull
      success "Образы основного стека обновлены по тегам из compose-файла."
      ;;
    "${STACK_VULNDB}")
      prepare_env "${STACK_VULNDB}"
      validate_stack "${STACK_VULNDB}"
      vulndb_compose pull
      success "Образы стека VulnDB обновлены по тегам из compose-файла."
      ;;
    *)
      error "Неизвестный стек: ${stack}"
      exit 1
      ;;
  esac
}

show_stack_status() {
  local title="$1"
  local stack="$2"
  local json_output
  local table_output

  printf "\n%s%s%s\n" "${COLOR_BOLD}" "${title}" "${COLOR_RESET}"

  case "${stack}" in
    "${STACK_MAIN}")
      json_output="$(main_compose ps --all --format json 2>/dev/null || true)"
      ;;
    "${STACK_VULNDB}")
      json_output="$(vulndb_compose ps --all --format json 2>/dev/null || true)"
      ;;
    *)
      error "Неизвестный стек: ${stack}"
      return 1
      ;;
  esac

  if [[ -z "${json_output}" || "${json_output}" == "null" || "${json_output}" == "[]" ]]; then
    warn "Для этого стека контейнеры не найдены."
    return
  fi

  table_output="$(printf '%s' "${json_output}" | format_publishers 2>/dev/null || true)"

  if [[ -n "${table_output}" ]]; then
    if have_command column; then
      printf '%s\n' "${table_output}" | column -t -s $'\t'
    else
      printf '%s\n' "${table_output}"
    fi
    return
  fi

  warn "Не удалось разобрать JSON-вывод Docker Compose. Показываю стандартный статус."
  case "${stack}" in
    "${STACK_MAIN}")
      main_compose ps || true
      ;;
    "${STACK_VULNDB}")
      vulndb_compose ps || true
      ;;
  esac
}

status_stack() {
  local stack="$1"

  case "${stack}" in
    "${STACK_ALL}")
      print_section "Состояние системы"
      show_stack_status "Стек VulnDB" "${STACK_VULNDB}"
      show_stack_status "Основной стек" "${STACK_MAIN}"
      ;;
    "${STACK_MAIN}")
      print_section "Состояние основного стека"
      show_stack_status "Основной стек" "${STACK_MAIN}"
      ;;
    "${STACK_VULNDB}")
      print_section "Состояние стека VulnDB"
      show_stack_status "Стек VulnDB" "${STACK_VULNDB}"
      ;;
    *)
      error "Неизвестный стек: ${stack}"
      exit 1
      ;;
  esac
}

stop_stack() {
  local stack="$1"

  case "${stack}" in
    "${STACK_ALL}")
      print_section "Остановка основного стека"
      main_compose down
      success "Основной стек остановлен."

      print_section "Остановка стека VulnDB"
      vulndb_compose down
      success "Стек VulnDB остановлен."
      ;;
    "${STACK_MAIN}")
      print_section "Остановка основного стека"
      main_compose down
      success "Основной стек остановлен."
      ;;
    "${STACK_VULNDB}")
      print_section "Остановка стека VulnDB"
      vulndb_compose down
      success "Стек VulnDB остановлен."
      ;;
    *)
      error "Неизвестный стек: ${stack}"
      exit 1
      ;;
  esac
}

confirm_action() {
  local prompt="$1"
  local answer

  printf "%s%s%s [y/N]: " "${COLOR_YELLOW}" "${prompt}" "${COLOR_RESET}"
  read -r answer

  case "${answer}" in
    y|Y|yes|YES)
      return 0
      ;;
    *)
      info "Действие отменено."
      return 1
      ;;
  esac
}

remove_stack() {
  local stack="$1"

  if ! confirm_action "Будут удалены контейнеры, сети и тома для '${stack}'. Продолжить?"; then
    return
  fi

  case "${stack}" in
    "${STACK_ALL}")
      print_section "Удаление основного стека"
      main_compose down --volumes --remove-orphans
      success "Основной стек удалён."

      print_section "Удаление стека VulnDB"
      vulndb_compose down --volumes --remove-orphans
      success "Стек VulnDB удалён."
      warn "Локальные env-файлы сохранены."
      ;;
    "${STACK_MAIN}")
      print_section "Удаление основного стека"
      main_compose down --volumes --remove-orphans
      success "Основной стек удалён."
      warn "Локальные env-файлы сохранены."
      ;;
    "${STACK_VULNDB}")
      print_section "Удаление стека VulnDB"
      vulndb_compose down --volumes --remove-orphans
      success "Стек VulnDB удалён."
      warn "Локальные env-файлы сохранены."
      ;;
    *)
      error "Неизвестный стек: ${stack}"
      exit 1
      ;;
  esac
}

print_usage() {
  cat <<EOF
Использование:
  $(basename "$0")                 Запустить интерактивный режим
  $(basename "$0") deploy <main|vulndb>
  $(basename "$0") import [имя.dump]
  $(basename "$0") dumps
  $(basename "$0") pull [all|main|vulndb]
  $(basename "$0") status [all|main|vulndb]
  $(basename "$0") stop [all|main|vulndb]
  $(basename "$0") remove [all|main|vulndb]
EOF
}

pick_stack() {
  local action_label="$1"
  local allow_all="${2:-false}"
  local choice

  menu_reset
  menu_add_item "1" "Основной стек"
  menu_add_item "2" "Стек VulnDB"
  if [[ "${allow_all}" == "true" ]]; then
    menu_add_item "3" "Оба стека"
  fi
  menu_add_item "0" "Назад"
  select_menu "Выбор стека" "Действие: ${action_label}"
  choice="${MENU_CHOICE}"

  case "${choice}" in
    1) PICKED_STACK="${STACK_MAIN}" ;;
    2) PICKED_STACK="${STACK_VULNDB}" ;;
    3)
      if [[ "${allow_all}" == "true" ]]; then
        PICKED_STACK="${STACK_ALL}"
      else
        warn "Неизвестный пункт: ${choice}"
        PICKED_STACK=""
      fi
      ;;
    0) PICKED_STACK="" ;;
    *)
      warn "Неизвестный пункт: ${choice}"
      PICKED_STACK=""
      ;;
  esac
}

run_interactive() {
  local choice stack

  while true; do
    menu_reset
    menu_add_item "1" "Развернуть стек" "env можно оставить дефолтным или заполнить в мастере"
    menu_add_item "2" "Импортировать дамп VulnDB" "из compose/vulndb/dumps"
    menu_add_item "3" "Проверить состояние"
    menu_add_item "4" "Обновить образы" "pull тегов из compose-файлов"
    menu_add_item "5" "Остановить систему"
    menu_add_item "6" "Удалить систему"
    menu_add_item "0" "Выход"
    select_menu "Главное меню" "Выберите действие"
    choice="${MENU_CHOICE}"

    case "${choice}" in
      1)
        pick_stack "развёртывание"
        stack="${PICKED_STACK}"
        [[ -n "${stack}" ]] && deploy_stack "${stack}"
        pause
        ;;
      2)
        import_vulndb_dump
        pause
        ;;
      3)
        pick_stack "проверка состояния"
        stack="${PICKED_STACK}"
        [[ -n "${stack}" ]] && status_stack "${stack}"
        pause
        ;;
      4)
        pick_stack "обновление образов" true
        stack="${PICKED_STACK}"
        [[ -n "${stack}" ]] && pull_stack "${stack}"
        pause
        ;;
      5)
        pick_stack "остановка"
        stack="${PICKED_STACK}"
        [[ -n "${stack}" ]] && stop_stack "${stack}"
        pause
        ;;
      6)
        pick_stack "удаление"
        stack="${PICKED_STACK}"
        [[ -n "${stack}" ]] && remove_stack "${stack}"
        pause
        ;;
      0)
        break
        ;;
      *)
        warn "Неизвестный пункт меню: ${choice}"
        pause
        ;;
    esac
  done
}

main() {
  local action="${1:-interactive}"
  local stack="${2:-}"

  case "${action}" in
    interactive)
      ensure_prerequisites
      run_interactive
      ;;
    deploy)
      if [[ -z "${stack}" ]]; then
        error "Для развёртывания укажите стек: main или vulndb."
        print_usage
        exit 1
      fi
      if [[ "${stack}" == "${STACK_ALL}" ]]; then
        error "Развёртывание обоих стеков одной командой отключено. Выберите main или vulndb."
        exit 1
      fi
      ensure_prerequisites
      deploy_stack "${stack}"
      ;;
    import)
      ensure_prerequisites
      import_vulndb_dump "${2:-}"
      ;;
    dumps)
      list_vulndb_dumps
      ;;
    pull)
      ensure_prerequisites
      stack="${stack:-${STACK_ALL}}"
      pull_stack "${stack}"
      ;;
    status)
      ensure_prerequisites
      stack="${stack:-${STACK_ALL}}"
      status_stack "${stack}"
      ;;
    stop)
      ensure_prerequisites
      stack="${stack:-${STACK_ALL}}"
      stop_stack "${stack}"
      ;;
    remove)
      ensure_prerequisites
      stack="${stack:-${STACK_ALL}}"
      remove_stack "${stack}"
      ;;
    help|-h|--help)
      print_usage
      ;;
    *)
      error "Неизвестное действие: ${action}"
      print_usage
      exit 1
      ;;
  esac
}

main "$@"
