#!/usr/bin/env bash
#
# Смена доменного имени в кластере Shturval (управления или клиентском).
# Основано на: https://docs.shturval.tech/2.13/howto/auth/domain-name/
#
# Использование:
#   export KUBECONFIG=/path/to/kubeconfig.conf
#   export OLD="old.domain.shturval.link"
#   export NEW="new.domain.example.ru"
#   # для клиентского кластера дополнительно:
#   export OLDMGMT="mgmt.old.domain.shturval.link"
#   export NEWMGMT="mgmt.new.domain.example.ru"
#   # опционально, если в кластере управления изменён корневой CA:
#   export CA=/path/to/ca.crt
#
#   ./change-domain-name.sh mgmt     # кластер управления
#   ./change-domain-name.sh client   # клиентский кластер
#

set -euo pipefail

readonly SCRIPT_NAME="${0##*/}"
readonly DOCS_URL="https://docs.shturval.tech/2.13/howto/auth/domain-name/"

# --- вывод ---

setup_colors() {
  if [[ -t 1 ]]; then
    C_RESET=$'\033[0m'
    C_BOLD=$'\033[1m'
    C_DIM=$'\033[2m'
    C_RED=$'\033[31m'
    C_GREEN=$'\033[32m'
    C_YELLOW=$'\033[33m'
    C_BLUE=$'\033[34m'
    C_MAGENTA=$'\033[35m'
    C_CYAN=$'\033[36m'
  else
    C_RESET=''
    C_BOLD=''
    C_DIM=''
    C_RED=''
    C_GREEN=''
    C_YELLOW=''
    C_BLUE=''
    C_MAGENTA=''
    C_CYAN=''
  fi
}

setup_colors

_print() {
  local fd="$1"
  local color="$2"
  local icon="$3"
  shift 3
  printf '%b%s %s%b\n' "${color}" "${icon}" "$*" "${C_RESET}" >&"${fd}"
}

die() {
  _print 2 "${C_BOLD}${C_RED}" "❌" "ОШИБКА: $*"
  exit 1
}

warn() {
  _print 2 "${C_BOLD}${C_YELLOW}" "⚠️" "$*"
}

info() {
  _print 1 "${C_CYAN}" "ℹ️" "$*"
}

success() {
  _print 1 "${C_BOLD}${C_GREEN}" "✅" "$*"
}

header() {
  echo
  printf '%b🌐 %s%b\n' "${C_BOLD}${C_MAGENTA}" "$*" "${C_RESET}"
  printf '%b━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━%b\n' "${C_DIM}${C_MAGENTA}" "${C_RESET}"
  echo
}

step() {
  _print 1 "${C_BOLD}${C_BLUE}" "🔧" "$*"
}

skip() {
  _print 1 "${C_DIM}${C_YELLOW}" "⏭️" "$*"
}

wait_msg() {
  _print 1 "${C_YELLOW}" "⏳" "$*"
}

backup_msg() {
  _print 1 "${C_CYAN}" "💾" "$*"
}

restart_msg() {
  _print 1 "${C_BLUE}" "🔄" "$*"
}

prompt_confirm() {
  printf '%b❓ %s%b' "${C_BOLD}${C_YELLOW}" "$1" "${C_RESET}" >&2
  read -r confirm
  [[ "${confirm}" =~ ^[Yy]$ ]]
}

# --- утилиты ---

usage() {
  cat <<EOF
Использование: ${SCRIPT_NAME} <mgmt|client>

Смена доменного имени в кластере Shturval.
Документация: ${DOCS_URL}

Режимы:
  mgmt    — кластер управления
  client  — клиентский кластер

Обязательные переменные окружения (оба режима):
  KUBECONFIG  — путь к статическому kubeconfig
  OLD         — текущее доменное имя кластера
  NEW         — новое доменное имя кластера

Дополнительно для режима client:
  OLDMGMT     — текущее доменное имя кластера управления
  NEWMGMT     — новое доменное имя кластера управления (можно указать то же, что OLDMGMT,
                если домен управления менять не нужно)
  CA          — (опционально) путь к корневому сертификату, если CA в mgmt изменён

Пример (кластер управления):
  export KUBECONFIG=./mgmtcluster.conf
  export OLD="mgmtcluster.ip-10-11-12-14.shturval.link"
  export NEW="mgmtcluster.mydomain.ru"
  ${SCRIPT_NAME} mgmt

Пример (клиентский кластер):
  export KUBECONFIG=./mycluster.conf
  export OLD="mycluster.ip-10-11-12-13.shturval.link"
  export NEW="mycluster.mydomain.ru"
  export OLDMGMT="mgmtcluster.ip-10-11-12-14.shturval.link"
  export NEWMGMT="mgmtcluster.mydomain.ru"
  ${SCRIPT_NAME} client
EOF
}

require_command() {
  local cmd="$1"
  if ! command -v "${cmd}" >/dev/null 2>&1; then
    missing_commands+=("${cmd}")
  fi
}

require_var() {
  local var_name="$1"
  # Проверяем, что переменная объявлена и не пуста.
  if [[ -z "${!var_name:-}" ]]; then
    missing_vars+=("${var_name}")
    return
  fi
  # Фиксируем обращение к существующей переменной (set -u уже включён).
  : "${!var_name}"
}

require_file_var() {
  local var_name="$1"
  require_var "${var_name}"
  if [[ ${#missing_vars[@]} -gt 0 ]]; then
    return
  fi
  local file_path="${!var_name}"
  if [[ ! -f "${file_path}" ]]; then
    die "Переменная ${var_name} указывает на несуществующий файл: ${file_path}"
  fi
}

check_tools() {
  local -a missing_commands=()
  local -a required_commands=(kubectl jq base64 sleep)

  for cmd in "${required_commands[@]}"; do
    require_command "${cmd}"
  done

  if [[ ${#missing_commands[@]} -gt 0 ]]; then
    warn "Не найдены обязательные утилиты: ${missing_commands[*]}"
    warn "Установите недостающие утилиты и запустите скрипт снова."
    exit 1
  fi

  info "Все обязательные утилиты найдены: ${required_commands[*]}"
  success "Проверка утилит пройдена"
}

check_vars_mgmt() {
  local -a missing_vars=()
  local -a required_vars=(KUBECONFIG OLD NEW)

  for var in "${required_vars[@]}"; do
    require_var "${var}"
  done

  if [[ ${#missing_vars[@]} -gt 0 ]]; then
    warn "Не заданы обязательные переменные окружения: ${missing_vars[*]}"
    warn "Экспортируйте переменные перед запуском (см. ${DOCS_URL})."
    exit 1
  fi

  require_file_var KUBECONFIG

  if [[ "${OLD}" == "${NEW}" ]]; then
    die "OLD и NEW совпадают (${OLD}). Укажите разные доменные имена."
  fi

  success "Переменные для режима mgmt: KUBECONFIG, OLD, NEW — заданы."
}

check_vars_client() {
  local -a missing_vars=()
  local -a required_vars=(KUBECONFIG OLD NEW OLDMGMT NEWMGMT)

  for var in "${required_vars[@]}"; do
    require_var "${var}"
  done

  if [[ ${#missing_vars[@]} -gt 0 ]]; then
    warn "Не заданы обязательные переменные окружения: ${missing_vars[*]}"
    warn "Экспортируйте переменные перед запуском (см. ${DOCS_URL})."
    exit 1
  fi

  require_file_var KUBECONFIG

  if [[ "${OLD}" == "${NEW}" ]]; then
    die "OLD и NEW совпадают (${OLD}). Укажите разные доменные имена."
  fi

  if [[ "${OLDMGMT}" == "${NEWMGMT}" ]]; then
    info "OLDMGMT и NEWMGMT совпадают (${OLDMGMT}) — домен кластера управления менять не будем."
  fi
  if [[ -n "${CA:-}" ]]; then
    require_file_var CA
    info "Переменная CA задана: ${CA}"
  fi

  success "Переменные для режима client: KUBECONFIG, OLD, NEW, OLDMGMT, NEWMGMT — заданы."
}

check_cluster_access() {
  info "Проверка доступа к кластеру (KUBECONFIG=${KUBECONFIG})..."
  if ! kubectl cluster-info >/dev/null 2>&1; then
    die "Не удалось подключиться к кластеру через KUBECONFIG=${KUBECONFIG}"
  fi
  info "Подключение к кластеру успешно."
  success "Кластер доступен"
}

resource_exists() {
  local kind="$1"
  local name="$2"
  local namespace="${3:-}"

  if [[ -n "${namespace}" ]]; then
    kubectl get "${kind}" "${name}" -n "${namespace}" >/dev/null 2>&1
  else
    kubectl get "${kind}" "${name}" >/dev/null 2>&1
  fi
}

warn_missing_resource() {
  local kind="$1"
  local name="$2"
  local namespace="${3:-}"

  if [[ -n "${namespace}" ]]; then
    skip "Ресурс ${kind}/${name} в namespace ${namespace} отсутствует — пропуск."
  else
    skip "Ресурс ${kind}/${name} отсутствует — пропуск."
  fi
}

base64_no_wrap() {
  local input_file="$1"
  if base64 --help 2>&1 | grep -q '\-w'; then
    base64 -w0 -i "${input_file}"
  else
    base64 -i "${input_file}" | tr -d '\n'
  fi
}

patch_shturvalserviceconfig_replace_domain() {
  local resource="$1"
  local old_domain="$2"
  local new_domain="$3"

  info "  ShturvalServiceConfig/${resource}: ${old_domain} → ${new_domain}"
  if ! resource_exists shturvalserviceconfig "${resource}"; then
    warn_missing_resource shturvalserviceconfig "${resource}"
    return 0
  fi

  local current_values
  current_values="$(kubectl get shturvalserviceconfig "${resource}" -o jsonpath='{.spec.customvalues}')"
  if [[ -z "${current_values}" || "${current_values}" == "null" ]]; then
    skip "ShturvalServiceConfig/${resource}: spec.customvalues пуст — пропуск замены ${old_domain}."
    return 0
  fi

  if ! echo "${current_values}" | jq --arg old "${old_domain}" 'tostring | contains($old)' | grep -q true; then
    skip "ShturvalServiceConfig/${resource}: домен ${old_domain} не найден в customvalues — пропуск."
    return 0
  fi

  local new_values
  new_values="$(echo "${current_values}" | jq --arg old "${old_domain}" --arg new "${new_domain}" \
    'tostring | gsub($old; $new) | fromjson')"

  kubectl patch shturvalserviceconfig "${resource}" --type=json \
    -p="[{'op': 'replace', 'path': '/spec/customvalues', 'value': ${new_values}}]"
}

patch_namespace_annotations_replace_domain() {
  local old_domain="$1"
  local new_domain="$2"

  info "Обновление аннотаций namespace (ArgoCD): ${old_domain} → ${new_domain}"

  local namespaces
  namespaces="$(kubectl get namespace -o jsonpath='{.items[*].metadata.name}')"

  for resource in ${namespaces}; do
    info "  namespace/${resource}..."
    local current_annotations
    current_annotations="$(kubectl get namespace "${resource}" -o jsonpath='{.metadata.annotations}' 2>/dev/null || true)"

    if [[ -z "${current_annotations}" || "${current_annotations}" == "null" ]]; then
      continue
    fi

    if ! echo "${current_annotations}" | jq --arg old "${old_domain}" 'tostring | contains($old)' | grep -q true; then
      continue
    fi

    local new_values
    new_values="$(echo "${current_annotations}" | jq --arg old "${old_domain}" --arg new "${new_domain}" \
      'tostring | gsub($old; $new) | fromjson')"

    kubectl patch namespace "${resource}" --type=json \
      -p="[{'op': 'replace', 'path': '/metadata/annotations', 'value': ${new_values}}]"
  done
}

restart_workloads() {
  local -a namespaces=("$@")

  restart_msg "Перезапуск workloads в namespace: ${namespaces[*]}"
  for ns in "${namespaces[@]}"; do
    local workloads
    workloads="$(kubectl -n "${ns}" get deployments.apps,statefulset.apps -o name 2>/dev/null || true)"
    if [[ -z "${workloads}" ]]; then
      skip "namespace/${ns}: deployments/statefulsets не найдены — пропуск."
      continue
    fi
    for workload in ${workloads}; do
      restart_msg "rollout restart ${workload} (namespace ${ns})"
      kubectl -n "${ns}" rollout restart "${workload}"
    done
  done
}

backup_resource_list() {
  local backup_dir="$1"
  shift
  local -a entries=("$@")

  mkdir -p "${backup_dir}"
  for entry in "${entries[@]}"; do
    IFS='|' read -r kind filename namespace <<<"${entry}"
    backup_msg "бэкап ${kind} → ${backup_dir}/${filename}"
    if [[ -n "${namespace}" ]]; then
      if kubectl get "${kind}" -n "${namespace}" -o yaml >"${backup_dir}/${filename}" 2>/dev/null; then
        continue
      fi
    elif kubectl get "${kind}" -o yaml >"${backup_dir}/${filename}" 2>/dev/null; then
      continue
    fi
    warn "${kind}: ресурсы не найдены или тип недоступен — пропуск бэкапа ${filename}."
    : >"${backup_dir}/${filename}"
  done
}

backup_mgmt() {
  local backup_dir="./bak"
  backup_msg "Резервное копирование ресурсов в ${backup_dir}/"
  backup_resource_list "${backup_dir}" \
    "clusterprofile|clusterprofile.yaml|kube-system" \
    "shturvalservicepatch|shturvalservicepatch.yaml|" \
    "shturvalserviceconfig|shturvalserviceconfig.yaml|" \
    "nodeconfigitem|nodeconfigitem.yaml|"
  success "Бэкап сохранён в ${backup_dir}/"
}

backup_client() {
  local backup_dir="./bak_client"
  backup_msg "Резервное копирование ресурсов в ${backup_dir}/"
  backup_resource_list "${backup_dir}" \
    "shturvalservicepatch|shturvalservicepatch.yaml|" \
    "shturvalserviceconfig|shturvalserviceconfig.yaml|" \
    "nodeconfigitem|nodeconfigitem.yaml|"
  success "Бэкап сохранён в ${backup_dir}/"
}

run_mgmt() {
  check_vars_mgmt
  check_cluster_access

  header "Смена домена кластера управления: ${OLD} → ${NEW}"
  warn "Изменение домена кластера управления заблокирует доступ к клиентским кластерам через kubeconfig."

  if ! prompt_confirm "Продолжить? [y/N] "; then
    skip "Операция отменена пользователем."
    exit 0
  fi

  backup_mgmt

  step "Patch ClusterProfile/default-client-cluster-profile"
  if resource_exists clusterprofile default-client-cluster-profile kube-system; then
    kubectl patch clusterprofile default-client-cluster-profile -n kube-system --type=json \
      -p='[{"op": "replace", "path": "/spec/loggingEndpoint", "value": "logs.'"${NEW}"'"}, {"op": "replace", "path": "/spec/monitoringEndpoint", "value": "vminsert.'"${NEW}"'"}]'
  else
    warn_missing_resource clusterprofile default-client-cluster-profile kube-system
  fi

  step "Patch ShturvalServicePatch/shturval-ingress-controller-common"
  if resource_exists shturvalservicepatch shturval-ingress-controller-common; then
    kubectl patch shturvalservicepatch shturval-ingress-controller-common --type=json \
      -p='[{"op": "replace", "path": "/spec/customvalues/controller/shturval/commonName", "value": "'"${NEW}"'"}]'
  else
    warn_missing_resource shturvalservicepatch shturval-ingress-controller-common
  fi

  step "Patch ShturvalServicePatch/shturval-frontend-oidc-client"
  if resource_exists shturvalservicepatch shturval-frontend-oidc-client; then
    kubectl patch shturvalservicepatch shturval-frontend-oidc-client --type=json \
      -p='[{"op": "replace", "path": "/spec/customvalues/auth_url_production", "value": "//shturval.'"${NEW}"'"}]'
  else
    warn_missing_resource shturvalservicepatch shturval-frontend-oidc-client
  fi

  step "Patch NodeConfigItem/controlplane-oidc-nci"
  if resource_exists nodeconfigitem controlplane-oidc-nci; then
    kubectl patch nodeconfigitem controlplane-oidc-nci --type=json \
      -p='[{"op": "replace", "path": "/spec/kubeapi/oidc/issuerurl", "value": "https://shturval.'"${NEW}"'"}]'
  else
    warn_missing_resource nodeconfigitem controlplane-oidc-nci
  fi

  local -a ssc_resources=(
    shturval-auth
    shturval-backend
    shturval-cd
    shturval-dashboards
    shturval-frontend
    shturval-metrics-collector
    shturval-monitoring
  )

  step "Замена домена в ShturvalServiceConfig (${OLD} → ${NEW})"
  for resource in "${ssc_resources[@]}"; do
    patch_shturvalserviceconfig_replace_domain "${resource}" "${OLD}" "${NEW}"
  done

  patch_namespace_annotations_replace_domain "${OLD}" "${NEW}"

  wait_msg "Ожидание 5 минут для применения изменений..."
  sleep 5m

  restart_workloads shturval-backend shturval-cd ingress

  success "Смена домена кластера управления завершена"
  info "Дождитесь перезапуска подов. При необходимости установите корпоративный сертификат."
}

run_client() {
  check_vars_client
  check_cluster_access

  local change_mgmt_domain=false
  if [[ "${OLDMGMT}" != "${NEWMGMT}" ]]; then
    change_mgmt_domain=true
  fi

  if [[ "${change_mgmt_domain}" == true ]]; then
    header "Смена домена клиентского кластера: ${OLD} → ${NEW} (mgmt: ${OLDMGMT} → ${NEWMGMT})"
  else
    header "Смена домена клиентского кластера: ${OLD} → ${NEW} (mgmt без изменений: ${OLDMGMT})"
  fi

  if ! prompt_confirm "Продолжить? [y/N] "; then
    skip "Операция отменена пользователем."
    exit 0
  fi

  backup_client

  step "Patch ShturvalServicePatch/shturval-ingress-controller-common"
  if resource_exists shturvalservicepatch shturval-ingress-controller-common; then
    kubectl patch shturvalservicepatch shturval-ingress-controller-common --type=json \
      -p='[{"op": "replace", "path": "/spec/customvalues/controller/shturval/commonName", "value": "'"${NEW}"'"}]'
  else
    warn_missing_resource shturvalservicepatch shturval-ingress-controller-common
  fi

  if [[ "${change_mgmt_domain}" == true ]]; then
    step "Patch NodeConfigItem/controlplane-oidc-nci (issuer URL → shturval.${NEWMGMT})"
    if resource_exists nodeconfigitem controlplane-oidc-nci; then
      kubectl patch nodeconfigitem controlplane-oidc-nci --type=json \
        -p='[{"op": "replace", "path": "/spec/kubeapi/oidc/issuerurl", "value": "https://shturval.'"${NEWMGMT}"'"}]'
    else
      warn_missing_resource nodeconfigitem controlplane-oidc-nci
    fi
  else
    skip "NodeConfigItem/controlplane-oidc-nci: домен управления не меняется — пропуск issuer URL."
  fi

  if [[ -n "${CA:-}" ]]; then
    step "Patch NodeConfigItem/controlplane-oidc-nci (корневой CA из ${CA})"
    if resource_exists nodeconfigitem controlplane-oidc-nci; then
      local ca_value
      ca_value="$(base64_no_wrap "${CA}")"
      kubectl patch nodeconfigitem controlplane-oidc-nci --type=json \
        -p='[{"op": "replace", "path": "/spec/kubeapi/oidc/cafile", "value": "'"${ca_value}"'"}]'
    else
      warn_missing_resource nodeconfigitem controlplane-oidc-nci
    fi
  fi

  local -a ssc_resources=(
    shturval-cd
    shturval-dashboards
    shturval-metrics-collector
    shturval-monitoring
    shturval-log-collector
  )

  step "Замена доменов в ShturvalServiceConfig"
  for resource in "${ssc_resources[@]}"; do
    patch_shturvalserviceconfig_replace_domain "${resource}" "${OLD}" "${NEW}"
    if [[ "${change_mgmt_domain}" == true ]]; then
      patch_shturvalserviceconfig_replace_domain "${resource}" "${OLDMGMT}" "${NEWMGMT}"
    fi
  done

  patch_namespace_annotations_replace_domain "${OLD}" "${NEW}"

  wait_msg "Ожидание 5 минут для применения изменений..."
  sleep 5m

  restart_workloads shturval-cd ingress

  success "Смена домена клиентского кластера завершена"
  info "Дождитесь перезапуска подов."
}

main() {
  if [[ $# -ne 1 ]]; then
    usage
    exit 1
  fi

  check_tools

  case "$1" in
    mgmt|management)
      run_mgmt
      ;;
    client)
      run_client
      ;;
    -h|--help|help)
      usage
      exit 0
      ;;
    *)
      warn "Неизвестный режим: $1"
      usage
      exit 1
      ;;
  esac
}

main "$@"
