#!/usr/bin/env bash
#
# Copyright (c) 2022-2026, Cloudera, Inc. All Rights Reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
#     http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
#

export ROOT_DIR=${CMA_HOME:-$(dirname $(cd $(dirname $(readlink -f "$0"))/ && pwd))}
VENV_DIR="${CMA_VENV_DIR:-${ROOT_DIR}/venv}"
LOGS_DIR="${CMA_LOG_DIR:-${ROOT_DIR}/logs}"
DATA_DIR="${CMA_DATA_DIR:-$ROOT_DIR/data}"

CMA_TEMPLATES_DIR=$ROOT_DIR/templates
VAULT_SERVER_BIN_DIR="$ROOT_DIR/vault-local/scripts/bin"

CMA_SERVER_HOME=${ROOT_DIR}/cma-server
CMA_SERVER_CONF_DIR=${CMA_SERVER_CONF_DIR:-${CMA_CONF_DIR:-${CMA_SERVER_HOME}}}
CMA_SERVER_ENV_FILE=${CMA_SERVER_ENV_FILE:-"${CMA_SERVER_CONF_DIR}/cma-server.env"}
CMA_SERVER_ENV_FILE_BAK="${CMA_SERVER_ENV_FILE}.bak"
export CMA_SERVER_SSL_CONF_FILE=${CMA_SERVER_SSL_CONF_FILE:-"${CMA_SERVER_CONF_DIR}/ssl-server.properties"}
CMA_SERVER_SSL_CONF_FILE_BAK="${CMA_SERVER_SSL_CONF_FILE}.bak"
export CMA_CLIENT_SSL_CONF_FILE=${CMA_CLIENT_SSL_CONF_FILE:-"${CMA_SERVER_CONF_DIR}/ssl-client.properties"}
CMA_CLIENT_SSL_CONF_FILE_BAK="${CMA_CLIENT_SSL_CONF_FILE}.bak"
CMA_SERVER_CONF_FILE=${CMA_SERVER_CONF_FILE:-"${CMA_SERVER_CONF_DIR}/cma-server.properties"}
CMA_SSL_CONF_FILE=${CMA_SSL_CONF_FILE:-"${CMA_SERVER_CONF_DIR}/cma-ssl.properties"}
CMA_SERVER_CONF_FILE_BAK="${CMA_SERVER_CONF_FILE}.bak"

CMA_AGENT_HOME=${ROOT_DIR}/cma-agent
CMA_AGENT_CONF_DIR=${CMA_CONF_DIR:-${CMA_AGENT_HOME}}
CMA_AGENT_CONF_FILE=${CMA_AGENT_CONF_FILE:-"${CMA_AGENT_CONF_DIR}/cma-agent.properties"}
CMA_AGENT_CONF_FILE_BAK="${CMA_AGENT_CONF_FILE}.bak"

CMA_PYPI_SERVER_CONF_DIR=${CMA_CONF_DIR:-$ROOT_DIR/pypi-server]}
CMA_PYPI_SERVER_CONF_FILE=${CMA_PYPI_SERVER_CONF_FILE:-"${CMA_PYPI_SERVER_CONF_DIR}/cma-pypi-server.properties"}
CMA_PYPI_SERVER_CONF_FILE_BAK="${CMA_PYPI_SERVER_CONF_FILE}.bak"

VAULT_DIR=${CMA_VAULT_DIR:-$ROOT_DIR/vault}
VAULT_BIN=$ROOT_DIR/vault-local/vault/vault
VAULT_CONF_DIR=${CMA_CONF_DIR:-${VAULT_DIR}}
VAULT_CONF_FILE=${VAULT_CONF_DIR}/vault.hcl
VAULT_DATA_DIR=${DATA_DIR}/vault
VAULT_SERVER_HOST=${VAULT_SERVER_HOST:-"cma-vault.cma.local"}
VAULT_SERVER_PORT=${VAULT_SERVER_PORT:-"8210"}
VAULT_SERVER_SCHEME=${VAULT_SERVER_SCHEME:-"http"}
VAULT_SERVER_PID_DIR=${VAULT_SERVER_PID_DIR:-${CMA_INSTANCE_DIR}}
VAULT_SERVER_PID_FILE=${VAULT_SERVER_PID_FILE:-${VAULT_SERVER_PID_DIR}/vault.pid}
VAULT_SERVER_LOG_DIR=${VAULT_SERVER_LOG_DIR:-${LOGS_DIR}}
VAULT_SERVER_CONF_DIR=${VAULT_SERVER_CONF_DIR:-${CMA_CONF_DIR:-${VAULT_DIR}}}
VAULT_SERVER_VERSION=${VAULT_SERVER_VERSION:-"1.14.8"}
VAULT_TOKEN_FILE="${VAULT_SERVER_CONF_DIR}/.vault-token.json"
CMA_VAULT_SERVER_ACCESS_FILE="${VAULT_SERVER_CONF_DIR}/cma-vault-access.properties"
CMA_VAULT_SERVER_CONF_FILE=${CMA_VAULT_SERVER_CONF_FILE:-"${VAULT_SERVER_CONF_DIR}/cma-vault-server.properties"}
CMA_VAULT_SERVER_CONF_FILE_BAK="${CMA_VAULT_SERVER_CONF_FILE}.bak"
CMA_VAULT_SERVER_SSL_FILE=${CMA_VAULT_SERVER_SSL_FILE:-"${VAULT_SERVER_CONF_DIR}/cma-vault-ssl-server.properties"}

CMA_SERVER_DB_DIR=${DATA_DIR}/db

CMA_BACKUP_DIR=${DATA_DIR}/backups
CMA_BACKUP_HISTORY_DIR=${CMA_BACKUP_DIR}/history

CMA_SERVER_HTTP_PORT=${CMA_SERVER_HTTP_PORT:-8090}
CMA_SERVER_HTTPS_PORT=${CMA_SERVER_HTTPS_PORT:-8093}
DEFAULT_CMA_SERVER_PROFILE=prod

PYPI_WEBSERVER_PORT=${PYPI_WEBSERVER_PORT:-${CMA_PYPI_WEBSERVER_PORT:-9003}}
PYPI_WEBSERVER_HOST=${PYPI_WEBSERVER_HOST:-${CMA_PYPI_WEBSERVER_HOST:-localhost}}
PYPI_SERVER_PID_DIR=${PYPI_SERVER_PID_DIR:-${CMA_INSTANCE_DIR}}
PYPI_SERVER_PID_FILE=${PYPI_SERVER_PID_FILE:-"${PYPI_SERVER_PID_DIR}/pypi_server.pid"}

PYTHON_MINIMUM_MAJOR=3
PYTHON_MINIMUM_MINOR=11
PYTHON_MINIMUM_PATCH=0

JAVA_MINIMUM_MAJOR=17

CMA_UPGRADE_INITIAL_VERSION=3.3.0
CMA_VERSION_INFO_FILE=${ROOT_DIR}/version.info

CMA_SSL_BIN=${ROOT_DIR}/cma-tools/cma-ssl/cma-ssl.sh
SSL_TOOL=${SSL_TOOL:-cma-ssl}

source "${SCRIPT_PATH:-$ROOT_DIR/bin}"/utils.sh

create_logs_dir() {
  mkdir -p "${LOGS_DIR}"
}

# Local .env
load_env_file() {
  env_file=${1:-.env1}
  if [ -f "${env_file}" ]; then
    # Load Environment Variables
    source "${env_file}"
    echo "${env_file} is loaded"
  fi
}

# $1 the default python executable. Defaults to 'python3'
get_python_executable() {
  local default_python_executable=${1:-python3}
  read -r -p "Enter the path for python3 executable [${default_python_executable}]:" python_executable
  python_executable=${python_executable:-${default_python_executable}}
  echo "${python_executable}"
}

delete_virtual_env()
{
  echo "Deleting virtualenv in ${VENV_DIR}"
  if ! rm -rf "${VENV_DIR}"; then
    echo "Failed to delete ${VENV_DIR}"
    return 1
  fi
  return 0
}

function is_dev_tty_available() {
  if sh -c ": >/dev/tty" >/dev/null 2>&1; then
    return 0
  fi
  return 1
}

function wait_for_pid() {

  local pid=$1
  while kill -0 "${pid}" >/dev/null 2>&1; do
    if is_dev_tty_available; then
      printf '.' > /dev/tty
    fi
    sleep 2
  done

}

function xtimeout {
  timeout=$1
  shift
  command=("$@")
  (
    "${command[@]}" &
    runner_pid=$!
    trap -- '' SIGTERM
    ( # killer job
      sleep "$timeout"
      if ps -p $runner_pid > /dev/null; then
        kill -9 $runner_pid 2>/dev/null 2>&1
      fi
    ) &
    killer_pid=$!
    wait_for_pid $runner_pid 2> /dev/null
    kill -9 $killer_pid >/dev/null 2>&1
  )
}

wait_for_file() {
  local file="$1"; shift
  local wait_seconds="${1:-10}"; shift # 10 seconds as default timeout

  until test $((wait_seconds--)) -eq 0 -o -f "$file" ; do
    if is_dev_tty_available; then
      printf '.' > /dev/tty;
    fi
    sleep 2;
  done

  if [ "$wait_seconds" -le 0 ]; then
    return 1
  fi
  return 0
}

wait_str() {
  local file="$1"; shift
  local initial_term="$1"; shift
  local search_term="$1"; shift
  local wait_time="${1:-5}"; shift # 5 minutes as default timeout

  if ! wait_for_file "$file"; then
    echo "Failed!"
    echo "ERROR: $file is missing!" > /dev/tty
    exit 1
  fi

  # Check for the last occurrence of the initial term, or default to 1 (1st line) if it's not found
  #
  # Default scenario can happen if the file has been created already, but the initial term would be on the 1st line
  # which has not yet been written into the file.
  local initial_line_number=$(grep -n "${initial_term}" "$file" | tail -n1 | cut -d: -f1)
  initial_line_number=${initial_line_number:-1}

  (xtimeout "$wait_time" tail -F -n +"$initial_line_number" "$file") | grep -q "$search_term" && echo "Done!" && return 0

  echo "Failed!"
  echo "Timeout of $wait_time reached. Unable to find '$search_term' in '$file'"
  echo "Please check the $file for more details!"
  return 1
}

wait_for_cma_server_to_stop() {
  local log_file="$1"; shift
  local timeout="$1"; shift
  local appName="${1:-Application}"; shift

  # check if cma_server stopped
  if ! wait_str $log_file "Started $appName\|lastSseEvent is NULL" "Shutdown completed" $timeout; then
    return 1
  fi
}

wait_for_cma_server_to_start() {
  local log_file="$1"; shift
  local timeout="$1"; shift
  local appName="${1:-Application}"; shift

  # check if cma_server started
  if ! wait_str $log_file "Starting $appName" "Started $appName" $timeout; then
    exit 1
  fi
}

# Normalize deprecated CMA_DB_TYPE values.
# 'cm_postgres' was used in earlier versions; treat it as 'postgres'.
normalize_db_type() {
  if [ "${CMA_DB_TYPE}" = "cm_postgres" ]; then
    echo "WARNING: CMA_DB_TYPE='cm_postgres' is deprecated. Using 'postgres' instead." >&2
    export CMA_DB_TYPE="postgres"
  fi
}

# Validate and export PostgreSQL connection variables.
# Expects CMA_DB_HOST, CMA_DBA_USER, CMA_DBA_PASS to be set (e.g. via CSD parameters).
# Returns 0 on success, 1 if required parameters are missing.
configure_postgres() {
  if [ -z "${CMA_DB_HOST}" ]; then
    echo "ERROR: 'Database Host' is required when Database Type is 'postgres'." >&2
    echo "Set the Database Host in the CMA service configuration in Cloudera Manager." >&2
    return 1
  fi
  if [ -z "${CMA_DBA_PASS}" ]; then
    echo "ERROR: 'Database Password' is required when Database Type is 'postgres'." >&2
    echo "Set the Database Password in the CMA service configuration in Cloudera Manager." >&2
    return 1
  fi

  export CMA_DB_PORT="${CMA_DB_PORT:-5432}"
  export CMA_DB_NAME="${CMA_DB_NAME:-cma}"
  export CMA_DBA_USER="${CMA_DBA_USER:-cma}"

  # Auth Server DB defaults to same host/port/credentials, different database name
  export CMA_AUTH_DB_HOST="${CMA_AUTH_DB_HOST:-$CMA_DB_HOST}"
  export CMA_AUTH_DB_PORT="${CMA_AUTH_DB_PORT:-$CMA_DB_PORT}"
  export CMA_AUTH_DB_NAME="${CMA_AUTH_DB_NAME:-cma-auth}"
  export CMA_AUTH_DBA_USER="${CMA_AUTH_DBA_USER:-$CMA_DBA_USER}"
  export CMA_AUTH_DBA_PASS="${CMA_AUTH_DBA_PASS:-$CMA_DBA_PASS}"

  echo "PostgreSQL configuration: ${CMA_DB_HOST}:${CMA_DB_PORT} (user: ${CMA_DBA_USER}, db: ${CMA_DB_NAME})" >&2
  return 0
}

# Agent-specific PostgreSQL configuration.
# The agent uses CMA_AGENT_DB_* env vars to allow a completely separate DB instance.
configure_agent_postgres() {
  if [ -z "${CMA_AGENT_DB_HOST}" ]; then
    echo "ERROR: 'Database Host' is required when Database Type is 'postgres'." >&2
    echo "Set the Database Host in the CMA Agent service configuration in Cloudera Manager." >&2
    return 1
  fi
  if [ -z "${CMA_AGENT_DBA_PASS}" ]; then
    echo "ERROR: 'Database Password' is required when Database Type is 'postgres'." >&2
    echo "Set the Database Password in the CMA Agent service configuration in Cloudera Manager." >&2
    return 1
  fi

  export CMA_AGENT_DB_PORT="${CMA_AGENT_DB_PORT:-5432}"
  export CMA_AGENT_DB_NAME="${CMA_AGENT_DB_NAME:-cma-agent}"
  export CMA_AGENT_DBA_USER="${CMA_AGENT_DBA_USER:-cma-agent}"

  echo "Agent PostgreSQL configuration: ${CMA_AGENT_DB_HOST}:${CMA_AGENT_DB_PORT} (user: ${CMA_AGENT_DBA_USER}, db: ${CMA_AGENT_DB_NAME})" >&2
  return 0
}

# Validate and export MySQL connection variables.
# Expects CMA_DB_HOST, CMA_DBA_USER, CMA_DBA_PASS to be set (e.g. via CSD parameters).
# Returns 0 on success, 1 if required parameters are missing.
configure_mysql() {
  if [ -z "${CMA_DB_HOST}" ]; then
    echo "ERROR: 'Database Host' is required when Database Type is 'mysql'." >&2
    echo "Set the Database Host in the CMA service configuration in Cloudera Manager." >&2
    return 1
  fi
  if [ -z "${CMA_DBA_PASS}" ]; then
    echo "ERROR: 'Database Password' is required when Database Type is 'mysql'." >&2
    echo "Set the Database Password in the CMA service configuration in Cloudera Manager." >&2
    return 1
  fi

  export CMA_DB_PORT="${CMA_DB_PORT:-3306}"
  export CMA_DB_NAME="${CMA_DB_NAME:-cma}"
  export CMA_DBA_USER="${CMA_DBA_USER:-cma}"

  # Auth Server DB defaults to same host/port/credentials, different database name
  export CMA_AUTH_DB_HOST="${CMA_AUTH_DB_HOST:-$CMA_DB_HOST}"
  export CMA_AUTH_DB_PORT="${CMA_AUTH_DB_PORT:-$CMA_DB_PORT}"
  export CMA_AUTH_DB_NAME="${CMA_AUTH_DB_NAME:-cma_auth}"
  export CMA_AUTH_DBA_USER="${CMA_AUTH_DBA_USER:-$CMA_DBA_USER}"
  export CMA_AUTH_DBA_PASS="${CMA_AUTH_DBA_PASS:-$CMA_DBA_PASS}"

  echo "MySQL configuration: ${CMA_DB_HOST}:${CMA_DB_PORT} (user: ${CMA_DBA_USER}, db: ${CMA_DB_NAME})" >&2
  return 0
}

# Sets DBA user/pass defaults and dispatches to configure_postgres or configure_mysql
# based on CMA_DB_TYPE. Call this in the LOGIC phase before CREATE phase.
configure_db_type() {
  CMA_DBA_USER="${CMA_DBA_USER:-cma}"
  export CMA_DBA_USER
  CMA_AUTH_DBA_USER="${CMA_AUTH_DBA_USER:-${CMA_DBA_USER}}"
  export CMA_AUTH_DBA_USER
  CMA_AUTH_DBA_PASS="${CMA_AUTH_DBA_PASS:-${CMA_DBA_PASS}}"
  export CMA_AUTH_DBA_PASS

  local db_type="${CMA_DB_TYPE:-h2}"
  if [ "${db_type}" = "postgres" ]; then
    if [ -n "${CMA_AGENT_DB_HOST}" ]; then
      configure_agent_postgres
    elif [ -n "${CMA_DB_HOST}" ]; then
      configure_postgres
    fi
  elif [ "${db_type}" = "mysql" ]; then
    if [ -n "${CMA_AGENT_DB_HOST}" ]; then
      configure_agent_mysql
    elif [ -n "${CMA_DB_HOST}" ]; then
      configure_mysql
    fi
  else
    # H2: embedded DB, no host/port/name configuration needed
    # User/pass defaults are already exported above; create_*_db_conf handles the rest
    :
  fi
}

# Agent-specific MySQL configuration.
# The agent uses CMA_AGENT_DB_* env vars to allow a completely separate DB instance.
configure_agent_mysql() {
  if [ -z "${CMA_AGENT_DB_HOST}" ]; then
    echo "ERROR: 'Database Host' is required when Database Type is 'mysql'." >&2
    echo "Set the Database Host in the CMA Agent service configuration in Cloudera Manager." >&2
    return 1
  fi
  if [ -z "${CMA_AGENT_DBA_PASS}" ]; then
    echo "ERROR: 'Database Password' is required when Database Type is 'mysql'." >&2
    echo "Set the Database Password in the CMA Agent service configuration in Cloudera Manager." >&2
    return 1
  fi

  export CMA_AGENT_DB_PORT="${CMA_AGENT_DB_PORT:-3306}"
  export CMA_AGENT_DB_NAME="${CMA_AGENT_DB_NAME:-cma_agent}"
  export CMA_AGENT_DBA_USER="${CMA_AGENT_DBA_USER:-cma_agent}"

  echo "Agent MySQL configuration: ${CMA_AGENT_DB_HOST}:${CMA_AGENT_DB_PORT} (user: ${CMA_AGENT_DBA_USER}, db: ${CMA_AGENT_DB_NAME})" >&2
  return 0
}

# Kept for backward compatibility with standalone/docker scripts that may still call this.
auto_detect_cm_postgres() {
  configure_postgres
}

get_server_profiles() {
  normalize_db_type
  local profiles=${DEFAULT_CMA_SERVER_PROFILE}
  if [ "${CMA_VAULT}" = true ]; then
    profiles+=",vault"
  fi
  if [ "${CMA_SERVER_SSL}" = true ]; then
    profiles+=",ssl"
  fi
  if [ "${CMA_SERVER_LDAP}" = true ]; then
    profiles+=",embedded-ldap"
  fi
  local db_type="${CMA_DB_TYPE:-h2}"
  if [ "${db_type}" = "postgres" ] || [ "${db_type}" = "mysql" ]; then
    # The configure_postgres/configure_mysql functions must be called
    # in the parent shell BEFORE this function (which runs inside $(...) subshell,
    # where exports would be lost). The CSD and local/docker callers handle this.
    profiles+=",${db_type}"
  fi
  if [ -n "${CMA_SERVER_EXTRA_PROFILE}" ]; then
    profiles+=",${CMA_SERVER_EXTRA_PROFILE}"
  fi

  echo "${profiles}"
}

get_docker_server_profiles() {
  # It's important to add the docker profile to the end of the active profiles!!!
  echo "$(get_server_profiles),docker"
}

get_extra_profiles() {
  if [ -n "${CMA_SERVER_EXTRA_PROFILE}" ]; then
    echo "${CMA_SERVER_EXTRA_PROFILE}"
    return
  fi

  if [ -f "${CMA_SERVER_CONF_FILE}" ]; then
    local active_profiles=$(get_cma_conf_value "cma.server.profiles")
    local profiles=($(echo $active_profiles | tr ',' ' '))

    local extra_profile=''
    local delim=''
    for profile in ${profiles[@]}; do
      case $profile in
        "${DEFAULT_CMA_SERVER_PROFILE}"|"vault"|"ssl")
          continue
          ;;
        *)
          extra_profile+="$delim$profile"
          delim=','
          ;;
      esac
    done
    echo "${extra_profile}"
  fi

}

get_cma_server_protocol() {
  local protocol=http
  if [ -f "${CMA_SERVER_CONF_FILE}" ] && is_ssl_active; then
    protocol=https
  elif [ "$CMA_SERVER_SSL" = true ]; then
    protocol=https
  fi
  echo "${protocol}"
}

is_ssl_active() {
  if [ -f "${CMA_SERVER_CONF_FILE}" ]; then
    # get active profiles
    local active_profiles=$(get_cma_conf_value "cma.server.profiles")
    [[ "${active_profiles}" =~ "ssl" ]]
  elif [ -n "${CMA_SSL_CONF_FILE}" ] && [ -f "${CMA_SSL_CONF_FILE}" ]; then
    local active_profiles=$(get_cma_conf_value "cma.profile" "${CMA_SSL_CONF_FILE}")
    [[ "${active_profiles}" =~ "ssl" ]]
  else
    [ "${CMA_SERVER_SSL}" = true ]
  fi
}

get_cma_server_port() {
  local port=$CMA_SERVER_PORT
  if [ -z "${CMA_SERVER_PORT}" ]; then
    if is_ssl_active; then
      port=$(get_cma_server_https_port)
    else
      port=$(get_cma_server_http_port)
    fi
  fi
  echo "${port}"
}

get_cma_server_http_port() {
  local port=$CMA_SERVER_PORT
  if [ -z "${CMA_SERVER_PORT}" ]; then
    if [ -f "${CMA_SERVER_CONF_FILE}" ]; then
      port=$(get_cma_conf_value "cma.server.http.port")
    else
      port="${CMA_SERVER_HTTP_PORT}"
    fi
  fi
  echo "${port}"
}

get_cma_server_https_port() {
  local port=$CMA_SERVER_PORT
  if [ -z "${CMA_SERVER_PORT}" ]; then
    if [ -f "${CMA_SERVER_CONF_FILE}" ]; then
      port=$(get_cma_conf_value "cma.server.https.port")
    else
      port="${CMA_SERVER_HTTPS_PORT}"
    fi
  fi
  echo "${port}"
}

# Checks if the specified port is not in use by other process
# $1: port number
# $2: pid, default the cma-server pid
verify_port() {
  local port=$1
  local pid=${2:-$(get_cma_server_pid)}

  if which lsof > /dev/null; then
    res=$(lsof -i:$port | grep LISTEN | awk '{print $2}')
    [ -z "$res" ] || [ "$res" = "$pid" ]
  else
    echo "Failed to check port $port because lsof is not installed!"
  fi
}

verify_cma_ports() {
  local port=$(get_cma_server_http_port)
  if ! verify_port "${port}"; then
    echo "HTTP port $port is being used by another process!"
    exit 1
  fi
  if is_ssl_active; then
    port=$(get_cma_server_https_port)
    if ! verify_port "${port}"; then
      echo "HTTPS port $port is being used by another process!"
      exit 1
    fi
  fi
}

get_cma_version() {
  if [ -f ${CMA_VERSION_INFO_FILE} ]; then
    echo $(cat ${CMA_VERSION_INFO_FILE} | grep "cma.version" | awk -F = {'print $2'})
  else
    echo "UNKNOWN"
  fi
}

get_java_major_version() {
  if [ -z $JAVA_HOME/bin/java ]; then
    echo "JAVA_HOME must be set"
    exit 1
  fi
  local VERSION_STRING=`$JAVA_HOME/bin/java -version 2>&1`
  local JAVA_MAJOR_VERSION=$(echo ${VERSION_STRING} | sed -E -n 's/java|openjdk version "1.([^.-]*).*"/\1/p' | cut -d' ' -f1 | tr '\n' ' ')
  # trim version string and check for string emptiness
  local JAVA_MAJOR_VERSION="${JAVA_MAJOR_VERSION// /}"
  if [ "$JAVA_MAJOR_VERSION" = '' ]; then
    JAVA_MAJOR_VERSION=$(echo ${VERSION_STRING} | sed -E -n 's/java|openjdk version "([^.-]*).*"/\1/p' | cut -d' ' -f1 | tr '\n' ' ')
    eval $1=$JAVA_MAJOR_VERSION
  else
    eval $1=$JAVA_MAJOR_VERSION
  fi
}

verify_cma_java_home() {
  echo "$JAVA_HOME"
  if [ -z "$JAVA_HOME" ]; then
    echo "Java verification failed!"
    cat <<EOF
+==========================================================================+
|      Error: Unable to find a compatible version of Java on this host,    |
|             either because JAVA_HOME has not been set or because a       |
|             compatible version of Java is not installed.                 |
+--------------------------------------------------------------------------+
| Please install either:                                                   |
| - a supported version of the Oracle JDK from the Oracle Java web         |
|   site:                                                                  |
|   > http://www.oracle.com/technetwork/java/javase/index.html <           |
| OR                                                                       |
| - a supported version of the OpenJDK from your OS vendor.  Help for      |
|   some OSes are available at:                                            |
|   > http://openjdk.java.net/install/ <                                   |
|                                                                          |
| Cloudera Migration Assistant requires Oracle JDK or OpenJDK ${JAVA_MINIMUM_MAJOR} or later. |
| NOTE: Cloudera Migration Assistant will find the Oracle JDK              |
|       when starting, regardless of whether you installed the JDK         |
|       using a binary installer or the RPM-based installer.               |
|                                                                          |
+==========================================================================+
EOF
    exit 1
  fi

  get_java_major_version JAVA_VERSION
  if [ $JAVA_VERSION -lt ${JAVA_MINIMUM_MAJOR} ]; then
    echo "Java verification failed! Java ${JAVA_VERSION} detected at ${JAVA_HOME}. Cloudera Migration Assistant requires Oracle JDK or OpenJDK ${JAVA_MINIMUM_MAJOR} or later."
    exit 1
  fi

  echo "JAVA_HOME=$JAVA_HOME"
}


locate_cma_java_home() {
  local JAVA21_HOME_CANDIDATES=(
      '/usr/java/jdk-21'
      '/usr/lib/jvm/jdk-21'
      '/usr/lib/jvm/java-21-oracle'
    )

  local OPENJAVA21_HOME_CANDIDATES=(
      '/usr/lib/jvm/java-21'
      '/usr/java/jdk-21'
      '/usr/lib/jvm/jdk-21'
      '/usr/lib64/jvm/jdk-21'
      '/usr/lib/jvm/zulu-21'
      '/usr/lib/jvm/zulu21'
      '/usr/lib/jvm/java-21-zulu-openjdk'
      '/usr/lib/jvm/java-21-openjdk'
      "$HOME/.sdkman/candidates/java/21"
    )

  local JAVA17_HOME_CANDIDATES=(
      '/usr/java/jdk-17'
      '/usr/lib/jvm/jdk-17'
      '/usr/lib/jvm/java-17-oracle'
    )

  local OPENJAVA17_HOME_CANDIDATES=(
    '/usr/lib/jvm/java-17'
    '/usr/lib/jvm/jdk-17'
    '/usr/lib/jvm/jdk1.17'
    '/usr/lib/jvm/zulu-17'
    '/usr/lib/jvm/zulu17'
    '/usr/lib/jvm/java-17-openjdk'
    '/usr/lib64/jvm/java-17'
    '/usr/lib64/jvm/jdk1.17'
    "$HOME/.sdkman/candidates/java/17"
  )

  local MISCJAVA_HOME_CANDIDATES=(
    '/Library/Java/Home'
    '/usr/java/default'
    '/usr/lib/jvm/default-java'
    '/usr/lib/jvm/java-openjdk'
    '/usr/lib/jvm/jre-openjdk'
  )

  JAVA_HOME_CANDIDATES=(
                       ${JAVA21_HOME_CANDIDATES[@]}
                       ${OPENJAVA21_HOME_CANDIDATES[@]}
                       ${JAVA17_HOME_CANDIDATES[@]}
                       ${OPENJAVA17_HOME_CANDIDATES[@]}
                       ${MISCJAVA_HOME_CANDIDATES[@]}
                       )

  # find java
  for candidate in ${JAVA_HOME_CANDIDATES[@]}; do
    local candidates=($(ls -v ${candidate}*/bin/java 2>/dev/null))
    if [ ${#candidates[@]} -gt 0 ]; then
      export JAVA_HOME=$(sed 's/\/bin\/java//' <<< "${candidates[0]}")
      break;
    fi
  done
}

# Attempts to locate java home, prints an error and exits if no
# java can be found.
locate_and_verify_cma_java_home() {
  if [ -z $JAVA_HOME ]; then
    locate_cma_java_home
  fi
  verify_cma_java_home
}


get_python_version() {
  local py_exec="${1:-${PYTHON_EXECUTABLE}}"
  if [ -z $py_exec ]; then
     echo "Python executable must be specified!"
     exit 1
  fi

  if [ ! `which $py_exec` ]; then
    echo "0 0 0"
    return 1
  fi

  echo $($py_exec -c 'import platform; major, minor, patch = platform.python_version_tuple(); print(major); print(minor); print(patch);')
}

verify_python_version() {
  local py_exec="${1:-${PYTHON_EXECUTABLE}}"
  if [ ! `which $py_exec` ]; then
    echo "$py_exec is not found in the path."
    return 1
  fi

  local version=($(get_python_version $py_exec))

  if [ ${version[0]} -lt $PYTHON_MINIMUM_MAJOR ]; then
    return 1
  elif [ ${version[0]} -eq $PYTHON_MINIMUM_MAJOR ] && [ ${version[1]} -lt $PYTHON_MINIMUM_MINOR ]; then
    return 1
  elif [ ${version[0]} -eq $PYTHON_MINIMUM_MAJOR ] && [ ${version[1]} -eq $PYTHON_MINIMUM_MINOR ] && [ ${version[2]} -lt $PYTHON_MINIMUM_PATCH ]; then
    return 1
  fi

  export PYTHON_EXECUTABLE=$py_exec
  return 0
}

locate_cma_python_executable() {

  PYTHON_EXECUTABLE_CANDIDATES=(
    'python3.11'
    'python3.10'
    'python3.9'
    'python3.8'
    'python3'
    'python'
    )

  # find python
  for candidate in ${PYTHON_EXECUTABLE_CANDIDATES[@]}; do
    if [ $(which $candidate) ]; then
      echo $candidate
      break;
    fi
  done
}

# Attempts to locate java home, prints an error and exits if no
# java can be found.
locate_and_verify_cma_python_executable() {
  local py_exec="${1:-${PYTHON_EXECUTABLE}}"
  if [ -z $py_exec ]; then
    py_exec=$(locate_cma_python_executable)
    echo "Found $py_exec on the host!"
  fi

  echo "Checking the version of $py_exec.."
  if ! verify_python_version $py_exec; then
    echo "Python$PYTHON_MINIMUM_MAJOR.$PYTHON_MINIMUM_MINOR.$PYTHON_MINIMUM_PATCH+ is not installed."
    exit 1
  fi
}

setup_control_node_python_with_dependencies() {

  # Docker images pre-build the venv with uv during image creation (see cma-agent.dockerfile).
  # Skip venv setup here to avoid destroying the pre-built environment.
  if [ -f "${VENV_DIR}/.docker-prebuilt" ]; then
    echo "Using pre-built virtualenv in ${VENV_DIR}" | tee -a "$LOG_FILE"
    return 0
  fi

  cleanup() {
    if ! delete_virtual_env; then
      echo "The ${VENV_DIR} must be deleted manually!"
    fi
    exit 1
  }

  download_software_dependencies_for_offline_use() {
    source "${VENV_DIR}"/bin/activate
    if [ "$VIRTUAL_ENV" = "" ]; then
      echo "Virtualenv could not be activated in ${VENV_DIR}" | tee -a "$LOG_FILE"
      exit 1;
    fi
    dependency_install_playbooks=( download_jdbc_drivers )
    cd "${ROOT_DIR}"/cma-ansible/cma-setup || exit 1
    export AM2CM_ROOT="${ROOT_DIR:-$PWD}"
    export AIRGAPPED="${AIRGAPPED}"
    export JDBC_DRIVERS_TARGET="${CMA_JDBC_DRIVERS_DIR}"
    for i in "${dependency_install_playbooks[@]}"
    do
     echo "Running playbook $i" | tee -a "$LOG_FILE"
     ansible-playbook -e "@inventories/group_vars/all.yml" "playbooks/$i.yml" | tee -a "$LOG_FILE"
     if [ "${PIPESTATUS[0]}" -ne "0" ]; then
       echo "Failed to download dependencies! Exiting..." | tee -a "$LOG_FILE"
       deactivate
       exit 1
     fi
    done
    deactivate
  }

  trap cleanup SIGINT

  echo "Creating virtualenv in ${VENV_DIR} ..." | tee -a "$LOG_FILE"
  "${PYTHON_EXECUTABLE}" -m venv "${VENV_DIR}" | tee -a "$LOG_FILE"
  if [ "${PIPESTATUS[0]}" -ne "0" ]; then
    echo "Setting up virtualenv has failed! Exiting..." | tee -a "$LOG_FILE"
    exit 1
  fi

  export CMA_JDBC_DRIVERS_DIR="${CMA_JDBC_DRIVERS_DIR:-${ROOT_DIR}/jdbc-drivers}"

  echo "Installing requirements for cma-setup in ${VENV_DIR} ..." | tee -a "$LOG_FILE"
  source "${VENV_DIR}"/bin/activate
  USER_PYTHON_VERSION=$(python -V 2>&1 | grep -o -m1 '[0-9]\.[0-9]')  # we only need major & minor version e.g. 3.9 from the following example output: Python 3.9.17
  if [ "$AIRGAPPED" = true ]; then
    echo "Unarchiving CMA extras GPL tar" | tee -a "$LOG_FILE"
    tar -xzf "$CMA_EXTRAS_GPL_TAR_LOCATION" --directory "${ROOT_DIR}"/cma-ansible/cma-setup/python_dependencies --strip-components=1 python
    echo "Create links to the python dependencies" | tee -a "$LOG_FILE"
    mkdir -p "${ROOT_DIR}"/python_dependencies_links
    find "${ROOT_DIR}"/cma-ansible/cma-setup/python_dependencies  \( -name "*.whl" -o -name "*.tar.gz" \) -exec ln -vs "{}" "${ROOT_DIR}"/python_dependencies_links/ ';'
    # requirements for the control node now have the format: control_node_PYTHON_VERSION_requirements.txt
    REQUIREMENTS="${ROOT_DIR}/cma-ansible/cma-setup/python_requirements/control_node_${USER_PYTHON_VERSION}_requirements.txt"
    echo "Using requirements file: $REQUIREMENTS" | tee -a "$LOG_FILE"
    python -m pip install -r "${REQUIREMENTS}" --no-index --find-links "${ROOT_DIR}"/python_dependencies_links/ | tee -a "$LOG_FILE"

    # extract jars
    echo "Extracting jdbc-drivers from : $CMA_EXTRAS_GPL_TAR_LOCATION to $CMA_JDBC_DRIVERS_DIR" | tee -a "$LOG_FILE"
    mkdir "${CMA_JDBC_DRIVERS_DIR}"
    tar -xzf "$CMA_EXTRAS_GPL_TAR_LOCATION" --directory "${CMA_JDBC_DRIVERS_DIR}" --strip-components=1 jdbc-drivers

  else
    # Custom PyPI index: always trust the host since CMA deploys inside customer
    # datacenters where the provided mirror is assumed safe. The sed chain extracts
    # the hostname by stripping the protocol prefix, then everything after the first : or /
    python -m pip install --upgrade setuptools -r "${ROOT_DIR}"/cma-ansible/cma-setup/requirements.txt \
      ${CMA_PIP_INDEX_URL:+--index-url "${CMA_PIP_INDEX_URL}" --trusted-host "$(echo "${CMA_PIP_INDEX_URL}" | sed -e 's|^https://||' -e 's|^http://||' -e 's|[:/].*||')"} \
      | tee -a "$LOG_FILE"
    VENV_PYTHON_VERSION=$(ls "${VENV_DIR}"/lib)
    ansible-galaxy collection install --force "${ROOT_DIR}"/cma-ansible/collections/ansible_collections/cma -p "${VENV_DIR}"/lib/"${VENV_PYTHON_VERSION}"/site-packages/ansible_collections
    source "${SCRIPT_PATH:-$ROOT_DIR/bin}"/remove-unused-collections.sh
    remove_unused_ansible_collections "${VENV_DIR}"/lib/"${VENV_PYTHON_VERSION}"/site-packages/ansible_collections
  fi

  if [ "${PIPESTATUS[0]}" -ne "0" ]; then
    echo "Installing pip requirements have failed! Exiting..." | tee -a "$LOG_FILE"
    deactivate
    exit 1
  fi
  deactivate

}

# S1 env file
# $2 name of the env var
# $3 value of the env var
create_or_update_cma_environment_variable() {
  local env_file=$1
  local env_name=$2
  local env_value=$3

  if ! [ -f "$env_file" ]; then
    echo "Creating the file $env_file because it does not exist!"
    echo '# This file is generated by the CMA!' > $env_file
  fi

  if grep -q "export $env_name="; then
    sed -i '' -E "s/(export $env_name=).*/\1$env_value/" $env_file
  else
    echo "export $env_name=$env_value" >> $env_file
  fi


}

get_vault_token() {
  if [ ! -f "${VAULT_TOKEN_FILE}" ]; then
    exit 1
  fi
  echo $(cat ${VAULT_TOKEN_FILE} | ${PYTHON_EXECUTABLE} -c "import sys, json; print(json.load(sys.stdin)['root_token'])")
}

create_cma_server_env() {
  CMA_VERSION=${CMA_VERSION:-$(get_cma_version)}

  mkdir -p $CMA_SERVER_CONF_DIR
  echo 'Create cma-server.env ...' | tee -a "$LOG_FILE"
  cat >"${CMA_SERVER_ENV_FILE}" <<EOF
# This file is generated by the CMA!'
export AM2CM_ROOT="${ROOT_DIR:-$PWD}"
export CMA_HOME="${ROOT_DIR:-$PWD}"
export JAVA_HOME=${JAVA_HOME}
export CMA_VERSION=${CMA_VERSION}
export CMA_UPGRADE_FROM=${CMA_UPGRADE_FROM}
export CMA_INSTANCE_NAME=${CMA_INSTANCE_NAME}
export CMA_VAULT_BIN=${VAULT_BIN}
export LC_ALL="en_US.UTF-8"
EOF

  # secure config file
  chmod 600 ${CMA_SERVER_ENV_FILE}
}

read_cma_ssl_conf() {
  if [ -n "${CMA_SSL_CONF_FILE}" ] && [ -f "${CMA_SSL_CONF_FILE}" ]; then
    echo "Reading properties from ${CMA_SSL_CONF_FILE} and removing it"
    SSL_PROFILE=$(get_cma_conf_value "cma.profile" "${CMA_SSL_CONF_FILE}")
    CMA_SERVER_SSL_CONF_FILE=$(get_cma_conf_value "cma.server.ssl.conf.file" "${CMA_SSL_CONF_FILE}")
    CMA_CLIENT_SSL_CONF_FILE=$(get_cma_conf_value "cma.client.ssl.conf.file" "${CMA_SSL_CONF_FILE}")
    rm -f "${CMA_SSL_CONF_FILE}"
  fi
}

create_cma_server_conf() {
  CMA_SERVER_PROFILES=$(get_server_profiles)

  mkdir -p $CMA_SERVER_CONF_DIR
  echo 'Create cma-server.properties ...' | tee -a "$LOG_FILE"
  cat >"${CMA_SERVER_CONF_FILE}" <<EOF
# cma
cma.data.dir=${DATA_DIR}
cma.venv.dir=${VENV_DIR}
cma.log.dir=${LOGS_DIR}

# cma server
cma.server.ssl.conf.file=${CMA_SERVER_SSL_CONF_FILE}
cma.client.ssl.conf.file=${CMA_CLIENT_SSL_CONF_FILE}
cma.server.http.port=${CMA_SERVER_HTTP_PORT}
cma.server.https.port=${CMA_SERVER_HTTPS_PORT}
cma.server.pid.file=${CMA_SERVER_PID_FILE}
cma.server.log.file=${CMA_SERVER_LOG_FILE}
cma.server.profiles=${CMA_SERVER_PROFILES}
EOF

  # secure config file
  chmod 600 ${CMA_SERVER_CONF_FILE}
}

# Creates db.properties with cma.db.* Spring Boot properties.
# For PostgreSQL, call after configure_postgres() has exported CMA_DB_* variables.
# For H2, host/port/name default to localhost/5432/master and are ignored by the datasource URL.
create_master_db_conf() {
  local conf_dir="${CMA_SERVER_CONF_DIR:-${CMA_CONF_DIR}}"
  local db_conf_file="${CMA_DB_CONF_FILE:-${conf_dir}/db.properties}"
  if [ -f "${db_conf_file}" ]; then
    return
  fi
  mkdir -p "$(dirname "${db_conf_file}")"
  echo 'Create db.properties ...' | tee -a "$LOG_FILE"
  cat >"${db_conf_file}" <<EOF
# CMA Master Server database configuration
cma.db.host=${CMA_DB_HOST:-localhost}
cma.db.port=${CMA_DB_PORT:-5432}
cma.db.name=${CMA_DB_NAME:-cma-master}
cma.db.user=${CMA_DBA_USER}
cma.db.pass=${CMA_DBA_PASS}
EOF
  chmod 600 "${db_conf_file}"
}

# Creates auth-db.properties with cma-auth.db.* Spring Boot properties.
# Must be called after configure_postgres() has exported CMA_AUTH_DB_* variables.
create_auth_db_conf() {
  local conf_dir="${CMA_SERVER_CONF_DIR:-${CMA_CONF_DIR}}"
  local auth_db_conf_file="${CMA_AUTH_DB_CONF_FILE:-${conf_dir}/auth-db.properties}"
  if [ -f "${auth_db_conf_file}" ]; then
    return
  fi
  mkdir -p "$(dirname "${auth_db_conf_file}")"
  echo 'Create auth-db.properties ...' | tee -a "$LOG_FILE"
  cat >"${auth_db_conf_file}" <<EOF
# CMA Auth Server database configuration
cma-auth.db.host=${CMA_AUTH_DB_HOST:-localhost}
cma-auth.db.port=${CMA_AUTH_DB_PORT:-5432}
cma-auth.db.name=${CMA_AUTH_DB_NAME:-cma-auth}
cma-auth.db.user=${CMA_AUTH_DBA_USER}
cma-auth.db.pass=${CMA_AUTH_DBA_PASS}
EOF
  chmod 600 "${auth_db_conf_file}"
}

# Loads cma.db.* credentials from an existing db.properties into env vars.
# Called before prompt_for_master_secrets so a persisted password is reused on restart.
load_master_db_conf() {
  local conf_dir="${CMA_SERVER_CONF_DIR:-${CMA_CONF_DIR}}"
  local db_conf_file="${CMA_DB_CONF_FILE:-${conf_dir}/db.properties}"
  if [ ! -f "${db_conf_file}" ]; then
    return
  fi
  local saved_user saved_pass
  saved_user=$(get_cma_conf_value "cma.db.user" "${db_conf_file}")
  saved_pass=$(get_cma_conf_value "cma.db.pass" "${db_conf_file}")
  CMA_DBA_USER="${CMA_DBA_USER:-${saved_user}}"
  CMA_DBA_PASS="${CMA_DBA_PASS:-${saved_pass}}"
}

# Loads cma-auth.db.* credentials from an existing auth-db.properties into env vars.
# Called before prompt_for_master_secrets so persisted auth DB credentials are reused on restart.
load_auth_db_conf() {
  local conf_dir="${CMA_SERVER_CONF_DIR:-${CMA_CONF_DIR}}"
  local auth_db_conf_file="${CMA_AUTH_DB_CONF_FILE:-${conf_dir}/auth-db.properties}"
  if [ ! -f "${auth_db_conf_file}" ]; then
    return
  fi
  local saved_user saved_pass
  saved_user=$(get_cma_conf_value "cma-auth.db.user" "${auth_db_conf_file}")
  saved_pass=$(get_cma_conf_value "cma-auth.db.pass" "${auth_db_conf_file}")
  CMA_AUTH_DBA_USER="${CMA_AUTH_DBA_USER:-${saved_user}}"
  CMA_AUTH_DBA_PASS="${CMA_AUTH_DBA_PASS:-${saved_pass}}"
}

# Prompts interactively for secrets required by the CMA distributed stack when they
# are not already set via CLI flags or environment variables.
# Must be called BEFORE load_master_auth_conf so that an explicit flag value
# (already in CMA_CLIENT_SECRET) takes precedence over a persisted file value.
prompt_for_master_secrets() {
  if [ -z "${CMA_DBA_PASS}" ]; then
    if [ -t 0 ]; then
      while [ -z "${CMA_DBA_PASS}" ]; do
        read -r -s -p "Enter database password: " CMA_DBA_PASS
        echo
        if [ -z "${CMA_DBA_PASS}" ]; then
          echo "Error: database password cannot be empty." >&2
        fi
      done
      export CMA_DBA_PASS
    else
      echo "Error: database password is required. Set CMA_DBA_PASS or use --db-pass." >&2
      exit 1
    fi
  fi

  if [ -z "${CMA_AUTH_DBA_PASS}" ]; then
    if [ -t 0 ]; then
      read -r -s -p "Enter auth server database password (leave empty to use database password): " CMA_AUTH_DBA_PASS
      echo
      CMA_AUTH_DBA_PASS="${CMA_AUTH_DBA_PASS:-${CMA_DBA_PASS}}"
      export CMA_AUTH_DBA_PASS
    else
      echo "Error: auth server database password is required. Set CMA_AUTH_DBA_PASS or use --auth-db-pass." >&2
      exit 1
    fi
  fi

  if [ -z "${CMA_CLIENT_SECRET}" ]; then
    if [ -t 0 ]; then
      while [ -z "${CMA_CLIENT_SECRET}" ]; do
        read -r -s -p "Enter Master OAuth2 client secret: " CMA_CLIENT_SECRET
        echo
        if [ -z "${CMA_CLIENT_SECRET}" ]; then
          echo "Error: OAuth2 client secret cannot be empty." >&2
        fi
      done
      export CMA_CLIENT_SECRET
    else
      echo "Error: OAuth2 client secret is required. Set CMA_CLIENT_SECRET or use --client-secret." >&2
      exit 1
    fi
  fi

  prompt_for_agent_client_secret
}


# Loads cma.client.* credentials from an existing master-auth.properties into env vars.
# Called before generate_random_secret so a persisted secret is reused on restart.
load_master_auth_conf() {
  local conf_dir="${CMA_SERVER_CONF_DIR:-${CMA_CONF_DIR}}"
  local master_auth_conf_file="${CMA_MASTER_AUTH_CONF_FILE:-${conf_dir}/master-auth.properties}"
  if [ ! -f "${master_auth_conf_file}" ]; then
    return
  fi
  local saved_client_id
  local saved_client_secret
  saved_client_id=$(get_cma_conf_value "cma.client.id" "${master_auth_conf_file}")
  saved_client_secret=$(get_cma_conf_value "cma.client.secret" "${master_auth_conf_file}")
  CMA_CLIENT_ID="${CMA_CLIENT_ID:-${saved_client_id}}"
  CMA_CLIENT_SECRET="${CMA_CLIENT_SECRET:-${saved_client_secret}}"
}

# Creates master-auth.properties with cma.client.* Spring Boot properties (OAuth2 client
# credentials shared by the auth server and gateway server).
create_master_auth_conf() {
  local conf_dir="${CMA_SERVER_CONF_DIR:-${CMA_CONF_DIR}}"
  local master_auth_conf_file="${CMA_MASTER_AUTH_CONF_FILE:-${conf_dir}/master-auth.properties}"
  if [ -f "${master_auth_conf_file}" ]; then
    return
  fi
  mkdir -p "$(dirname "${master_auth_conf_file}")"
  echo 'Create master-auth.properties ...' | tee -a "$LOG_FILE"
  cat >"${master_auth_conf_file}" <<EOF
# CMA OAuth2 client credentials (auth server ↔ gateway)
cma.client.id=${CMA_CLIENT_ID:-cma}
cma.client.secret=${CMA_CLIENT_SECRET}
EOF
  chmod 600 "${master_auth_conf_file}"
}

backup_cma_server_conf() {
  yes | cp "${CMA_SERVER_CONF_FILE}" "${CMA_SERVER_CONF_FILE_BAK}"
}

ensure_vault_address_and_token() {
  if [ -z "$VAULT_TOKEN" ]; then
    export VAULT_TOKEN=$(get_vault_token)
    if [ -z "$VAULT_TOKEN" ]; then
      echo "Failed to get vault token!" | tee -a "$LOG_FILE"
      exit 1
    fi
  fi

  # read vault server address from config file if available
  if [ -f "${CMA_VAULT_SERVER_CONF_FILE}" ]; then
    VAULT_SERVER_HOST=$(get_cma_conf_value "vault.server.host" "${CMA_VAULT_SERVER_CONF_FILE}")
    VAULT_SERVER_PORT=$(get_cma_conf_value "vault.server.port" "${CMA_VAULT_SERVER_CONF_FILE}")
    VAULT_SERVER_SCHEME=$(get_cma_conf_value "vault.server.scheme" "${CMA_VAULT_SERVER_CONF_FILE}")
  fi

  export VAULT_ADDR="${VAULT_SERVER_SCHEME}://${VAULT_SERVER_HOST}:${VAULT_SERVER_PORT}"
}

ensure_vault_authentication() {
  echo "Checking vault authentication ..." | tee -a "$LOG_FILE"
  if [ -f "${CMA_VAULT_SERVER_ACCESS_FILE}" ]; then
    if [ -z "$CMA_VAULT_SECRET_ID" ]; then
      export CMA_VAULT_SECRET_ID=$(get_cma_conf_value "vault.server.secret.id" "${CMA_VAULT_SERVER_ACCESS_FILE}")
    fi
    if [ -z "$CMA_VAULT_ROLE_ID" ]; then
      export CMA_VAULT_ROLE_ID=$(get_cma_conf_value "vault.server.role.id" "${CMA_VAULT_SERVER_ACCESS_FILE}")
    fi
  fi

  if [ -z "$CMA_VAULT_ROLE_ID" ] || [ -z "$CMA_VAULT_SECRET_ID" ]; then
    echo "Failed to enable vault authentication: CMA_VAULT_ROLE_ID or CMA_VAULT_SECRET_ID is empty!" | tee -a "$LOG_FILE"
    exit 1
  fi

  local RUNNER_TYPE="$1"
  if [ "$RUNNER_TYPE" = "local" ]; then
    ensure_vault_address_and_token

    VAULT_CACERT=$(get_cma_conf_value "server.ssl.cert.pem" "${CMA_VAULT_SERVER_SSL_FILE}")
    curl_opts=""
    if [ -n "$VAULT_CACERT" ]; then
      curl_opts="--cacert $VAULT_CACERT"
    fi

    local login_response
    if ! login_response=$(curl ${curl_opts} -sS -X POST \
      -H "Content-Type: application/json" \
      --data "{\"role_id\":\"$CMA_VAULT_ROLE_ID\", \"secret_id\":\"$CMA_VAULT_SECRET_ID\"}" \
      "$VAULT_ADDR/v1/auth/approle/login"); then
      echo "Failed to authenticate (curl failed)" | tee -a "$LOG_FILE"
      exit 1
    fi
    if [[ "$login_response" == *'"errors":'* ]]; then
      echo "Failed to authenticate to Vault with current ROLE_ID and SECRET_ID!" | tee -a "$LOG_FILE"
      echo "Response: $login_response" | tee -a "$LOG_FILE"
      exit 1
    fi
    echo "Vault authentication was checked." | tee -a "$LOG_FILE"
  fi
}

create_cma_vault_server_conf() {

  mkdir -p ${VAULT_SERVER_CONF_DIR}
  echo 'Create cma-vault-server.properties ...' | tee -a "$LOG_FILE"
  cat >"${CMA_VAULT_SERVER_CONF_FILE}" <<EOF
# vault server
vault.server.host=${VAULT_SERVER_HOST}
vault.server.port=${VAULT_SERVER_PORT}
vault.server.scheme=${VAULT_SERVER_SCHEME}
vault.server.pid.file=${VAULT_SERVER_PID_FILE}
vault.server.log.dir=${VAULT_SERVER_LOG_DIR}
vault.server.version=${VAULT_SERVER_VERSION}
EOF

  # secure config file
  chmod 600 ${CMA_VAULT_SERVER_CONF_FILE}
}

create_cma_vault_server_ssl_conf() {

  mkdir -p ${VAULT_SERVER_CONF_DIR}
  echo 'Create cma-vault-ssl-server.properties ...' | tee -a "$LOG_FILE"
  cat >"${CMA_VAULT_SERVER_SSL_FILE}" <<EOF
# vault server
server.ssl.cert.pem=${CMA_SERVER_SSL_CERT_PEM}
server.ssl.key.pem=${CMA_SERVER_SSL_KEY_PEM}
server.ssl.key-password=${CMA_SERVER_SSL_KEY_PASSWORD}
EOF

  # secure config file
  chmod 600 ${CMA_VAULT_SERVER_SSL_FILE}
}

backup_cma_vault_server_conf() {
  yes | cp "${CMA_VAULT_SERVER_CONF_FILE}" "${CMA_VAULT_SERVER_CONF_FILE_BAK}"
}

create_cma_pypi_server_conf() {

  mkdir -p ${CMA_PYPI_SERVER_CONF_DIR}
  echo 'Create cma-pypi-server.properties ...' | tee -a "$LOG_FILE"
  cat >"${CMA_PYPI_SERVER_CONF_FILE}" <<EOF
# pypi server
cma.pypi.server.host=${PYPI_WEBSERVER_HOST}
cma.pypi.server.port=${PYPI_WEBSERVER_PORT}
EOF

  # secure config file
  chmod 600 ${CMA_PYPI_SERVER_CONF_FILE}
}

backup_cma_pypi_server_conf() {
  yes | cp "${CMA_PYPI_SERVER_CONF_FILE}" "${CMA_PYPI_SERVER_CONF_FILE_BAK}"
}


remove_from_active_profiles() {
  local remove_profile=$1
  local profile_property="${2:-cma.server.profiles}"

  # get active profiles
  local active_profiles=$(get_cma_conf_value "${profile_property}")
  if ! [[ "${active_profiles}" =~ "${remove_profile}" ]]; then
    echo "Profile $remove_profile was not found in the active profiles '${profile_property}' in ${CMA_SERVER_CONF_FILE}"
    return
  fi

  local profiles=($(echo $active_profiles | tr ',' ' '))

  local new_active_profile=''
  local delim=''
  for profile in ${profiles[@]}; do
    if [ "${profile}" != "${remove_profile}" ]; then
      new_active_profile+="$delim$profile"
      delim=','
    fi
  done

  # save new profile
  if [ "${active_profiles}" != "${new_active_profile}" ]; then
    set_cma_conf_value "${profile_property}" "${new_active_profile}"
    echo "Profile $remove_profile is removed from the active profiles '${profile_property}' in ${CMA_SERVER_CONF_FILE}"
  fi
}

add_to_active_profiles() {
  local add_profile=$1
  local profile_property="${2:-cma.server.profiles}"

  # get active profiles
  local active_profiles=$(get_cma_conf_value "${profile_property}")

  local new_active_profile="${active_profiles}"
  if [[ "${active_profiles}" =~ "${add_profile}" ]]; then
    echo "Profile $add_profile is already in the active profiles '${profile_property}' in ${CMA_SERVER_CONF_FILE}"
  else
    if [ -z "${active_profiles}" ]; then
      new_active_profile="${add_profile}"
    else
      new_active_profile+=",${add_profile}"
    fi
  fi
  if [ "${active_profiles}" != "${new_active_profile}" ]; then
    set_cma_conf_value "${profile_property}" "${new_active_profile}"
    echo "Profile $add_profile is added to the active profiles '${profile_property}' in ${CMA_SERVER_CONF_FILE}"
  fi
}

# deprecated
is_upgrade_needed() {
  if [ -f "${CMA_SERVER_ENV_FILE}" ]; then
    local env_cma_version=$(get_cma_environment_variable_value "CMA_VERSION")
    local cma_version=$(get_cma_version)

    if [ "${env_cma_version}" != "${cma_version}" ]; then
      return 0
    fi
  fi
  return 1
}

backup_cma_server_env_file() {
  yes | cp "${CMA_SERVER_ENV_FILE}" "${CMA_SERVER_ENV_FILE_BAK}"
}

#
# get the previous cma version
# $1 name of the env var, default "CMA_VERSION"
# $1 env file, default $CMA_SERVER_ENV_FILE_BAK
get_previous_cma_version() {
  local env_name=${1:-"CMA_VERSION"}
  local env_file=${2:-${CMA_SERVER_ENV_FILE_BAK}}
  if ! [ -f "${env_file}" ]; then
    exit 1
  fi

  local cma_version=$(get_cma_environment_variable_value "${env_name}" "${env_file}")
  if [ -z ${cma_version} ]; then
    cma_version="${CMA_UPGRADE_INITIAL_VERSION}"
  fi
  echo $cma_version
}

#
# Create db backup
# $1 filename prefix, default "db_backup"
# $2 backup dir, default ${CMA_BACKUP_DIR}
#
create_cma_db_backup() {
  local cma_version=$(get_previous_cma_version)

  local prefix=${1:-"db_backup"}
  local filename="${prefix}_${cma_version}.tar.gz"
  local backup_file="${2:-${CMA_BACKUP_DIR}}/${filename}"

  if [ -f $backup_file ]; then
    mkdir -p "${CMA_BACKUP_HISTORY_DIR}"
    mv $backup_file "${CMA_BACKUP_HISTORY_DIR}/$(date +%s)_${filename}"
  fi

  if [ "${VERBOSE}" = true ]; then
    TAR_OPTS="-czvf "
  else
    TAR_OPTS="-czf "
  fi
  TAR_OPTS+="${backup_file} -C ${CMA_SERVER_DB_DIR}"

  echo "Backup current database.." | tee -a "$LOG_FILE"
  tar ${TAR_OPTS} .
  if [ $? -ne 0 ]; then
    echo "Failed to back up the database." | tee -a "$LOG_FILE"
    exit 1
  else
    echo "The database is backed up in ${backup_file}" | tee -a "$LOG_FILE"
  fi
}

#
# Backup DB
#
backup_cma_db() {
  local cma_version=$(get_previous_cma_version)
  create_cma_db_backup
  CMA_UPGRADE_FROM="${cma_version}"
}


#
# Restore db backup
# $1 the version to be downgraded from
#
restore_cma_db() {
  # backup current DB before restore the backup
  create_cma_db_backup "downgrade_db_backup" "${CMA_BACKUP_HISTORY_DIR}"

  echo "Deleting current db files"
  rm -rf ${CMA_SERVER_DB_DIR}

  local cma_upgrade_from=${1:-$(get_previous_cma_version "CMA_UPGRADE_FROM")}
  local backup_file="${CMA_BACKUP_DIR}/db_backup_${cma_upgrade_from}.tar.gz"
  if ! [ -f "${backup_file}" ]; then
    echo "Missing db backup file: $backup_file";
    return 1
  fi

  if [ "${VERBOSE}" = true ]; then
    TAR_OPTS="-xzvf "
  else
    TAR_OPTS="-xzf "
  fi
  TAR_OPTS+="${backup_file} -C ${CMA_SERVER_DB_DIR}"

  echo "Restore db backup ${backup_file}" | tee -a "$LOG_FILE"
  tar ${TAR_OPTS}
  if [ $? -ne 0 ]; then
    echo "Failed to restore the database." | tee -a "$LOG_FILE"
    exit 1
  else
    echo "The database is restored from ${backup_file}" | tee -a "$LOG_FILE"
  fi
}

#
# Returns:
# 1 if upgrade
# 2 if equal
# 3 if downgrade
#
compare_cma_versions() {
  if ! [ -f "${CMA_SERVER_ENV_FILE}" ]; then
    exit 1
  fi

  local env_cma_version=$(get_previous_cma_version "CMA_VERSION" "${CMA_SERVER_ENV_FILE}")
  local cma_version=$(get_cma_version)
  compare_versions ${env_cma_version} ${cma_version}
  echo $?
}

backup_or_restore_cma_db_if_version_changed() {
  if [ -f "${CMA_SERVER_ENV_FILE}" ]; then
    local res=$(compare_cma_versions)

    if [ $res -eq 2 ]; then
      echo "The version of CMA has not changed, so no need to backup or restore the db!"
    else
      mkdir -p ${CMA_BACKUP_DIR}
      mkdir -p ${CMA_BACKUP_HISTORY_DIR}

      # set current cma_version
      CMA_VERSION=$(get_cma_version)

      echo "Backup cma-server.env"
      backup_cma_server_env_file

      case $res in
        1) backup_cma_db;;
        3) restore_cma_db;;
      esac
    fi
  fi
}

#
# Checks if alias exists in the keystore file
# $1: alias name
# $2: the path to the keystore file, default ${CMA_KEYSTORE_FILE}
# $3: the password of the keystore file, default ${CMA_KEYSTORE_PASSWORD}
#
is_alias_exist_in_keystore() {
  local alias=$1
  local keystore_file=${2:-${CMA_KEYSTORE_FILE#file:}}
  local keystore_pass=${3:-${CMA_KEYSTORE_PASSWORD}}
  if [ -n "${keystore_file}" ]; then
    ${JAVA_HOME}/bin/keytool -list -keystore $keystore_file -storepass $keystore_pass -alias $alias > /dev/null
    echo $?
  fi
}

verify_ssl_config() {
  if [ "${CMA_SERVER_SSL}" = true ]; then
    if [ -n "${CMA_KEYSTORE_FILE}" ]; then

      if [ -z "${CMA_KEYSTORE_PASSWORD}" ]; then
        echo "Keystore password must be specified!"
        exit 1
      fi

      if [ $(is_alias_exist_in_keystore ${CMA_KEY_ALIAS}) -ne 0 ]; then
        echo "The alias $cma_alias does not exist in the keystore ${CMA_KEYSTORE_FILE}!"
        exit 1
      fi

    else
      echo "Since no external keystore is specified, the built-in self-signed keystore is used!"
    fi

  fi
}

get_cma_server_pid() {
  local pid=0
  if [ -f "${CMA_SERVER_PID_FILE}" ]; then
    pid=$(cat "${CMA_SERVER_PID_FILE}")
  fi
  echo $pid
}

get_csd_min_version() {
  if [ -f ${CMA_VERSION_INFO_FILE} ]; then
    echo $(cat ${CMA_VERSION_INFO_FILE} | grep "csd.min.version" | awk -F = {'print $2'})
  fi
}

get_pypi_server_pid() {
  if [ ! -f "${PYPI_SERVER_PID_FILE}" ]; then
    echo ''
  else
    cat "${PYPI_SERVER_PID_FILE}"
  fi
}

is_pypi_server_running() {
  local pid=$(get_pypi_server_pid)
  if [ -z "${pid}" -o "${pid}" = '' ]; then
    return 1
  fi
  kill -0 "$pid" >/dev/null 2>&1
}

is_vault_active() {
  if [ -f "${CMA_SERVER_CONF_FILE}" ]; then
    # get active profiles
    local active_profiles=$(get_cma_conf_value "cma.server.profiles")
    [[ "${active_profiles}" =~ "vault" ]]
  else
    [ "${CMA_VAULT}" = true ]
  fi
}

is_process_from_pidfile_running() {
  local pidfile="$1"
  [ -f "$pidfile" ] || { echo "PID file missing"; return 1; }
  local pid
  pid=$(cat "$pidfile")
  kill -0 "$pid" 2>/dev/null && echo "Process $pid is running" || echo "Process $pid is not running"
}

execute_command() {
  if [ "${VERBOSE}" = true ]; then
    if is_dev_tty_available; then
      printf "\n" > /dev/tty
    fi
    "$@" | tee -a "$LOG_FILE"
    if [ "${PIPESTATUS[0]}" -ne "0" ]; then
      echo "Command execution has failed! Exiting..." | tee -a "$LOG_FILE"
      exit 1
    fi
  else
    local cmd_name="$1"
    # Export if it's a function, to be able to run in separate process
    if declare -F "$cmd_name" 2>&1 > /dev/null; then
      export -f "$cmd_name"
    fi

    "$SCRIPT_PATH"/capture_exit_code.sh "${LOGS_DIR}/command_exit_code.txt" "${@}" >>"$LOG_FILE" 2>&1 &

    local pid=$!
    trap "kill ${pid}; cleanup" SIGINT

    wait_for_pid ${pid} 2> /dev/null
    if [ "$(head -n 1 "${LOGS_DIR}"/command_exit_code.txt)" != "0" ]; then
      echo "Command execution has failed! Exiting..." | tee -a "$LOG_FILE"
      exit 1
    fi
  fi
}

wait_for_cma_agent_to_stop() {
  wait_for_cma_server_to_stop $CMA_AGENT_LOG_FILE $CMA_AGENT_STOP_TIMEOUT "AgentApplication"
  if [ $? -ne 0 ]; then
    echo "CMA Agent failed to stop!" | tee -a "$LOG_FILE"
    exit 1
  fi
}

generate_random_secret() {
  local secret
  secret=$(head -c 16 /dev/urandom | base64 | tr -dc 'a-zA-Z0-9')
  echo "${secret}"
}

# ensure_internal_keystore_password: load or generate CMA_INTERNAL_KEYSTORE_PASSWORD.
# Persists the generated password to ${conf_dir}/internal-keystore.properties so it
# survives restarts and can be shared between cma-server and cma-auth-server.
#
# Expected env vars:
#   CMA_INTERNAL_KEYSTORE_PASSWORD  — optional; used as password when no file exists yet;
#                                     ignored once a stored password is present in the file
#   conf_dir arg (positional $1)    — directory for internal-keystore.properties
#                                     (defaults to ${CMA_CONF_DIR})
ensure_internal_keystore_password() {
  local conf_dir="${1:-${CMA_CONF_DIR}}"
  local conf_file="${conf_dir}/internal-keystore.properties"

  if [ -f "${conf_file}" ]; then
    local file_perms
    file_perms=$(stat -c '%a' "${conf_file}" 2>/dev/null || stat -f '%OLp' "${conf_file}" 2>/dev/null)
    if [ "${file_perms}" != "600" ]; then
      echo "ERROR: ${conf_file} has unsafe permissions (${file_perms}). Expected 600. Aborting." >&2
      exit 1
    fi
    local stored_password
    stored_password=$(grep "^cma.internal-keystore.password=" "${conf_file}" | cut -d'=' -f2-)
    if [ -n "${stored_password}" ]; then
      # Stored password takes precedence over env var to ensure keystore consistency across restarts.
      # If env var differs from stored value, the env var is ignored — the keystore was already created
      # with the stored password and changing it would render the keystore unreadable.
      export CMA_INTERNAL_KEYSTORE_PASSWORD="${stored_password}"
      return
    fi
  fi

  if [ -z "${CMA_INTERNAL_KEYSTORE_PASSWORD}" ]; then
    export CMA_INTERNAL_KEYSTORE_PASSWORD=$(generate_random_secret)
    echo "Generated internal keystore password and saved to ${conf_file}" | tee -a "${LOG_FILE:-/dev/null}"
  fi

  mkdir -p "${conf_dir}"
  cat > "${conf_file}" <<EOF
# CMA internal keystore password
# Generated by deployment tooling — do not edit manually
cma.internal-keystore.password=${CMA_INTERNAL_KEYSTORE_PASSWORD}
EOF
  chmod 600 "${conf_file}"
}

# ensure_grpc_keystore: generate a self-signed PKCS12 keystore for gRPC TLS when
# CMA_KEYSTORE_FILE is not already pointing at an existing file.
#
# Expected env vars:
#   CMA_KEYSTORE_FILE               — if already set and file exists, skips generation
#   CMA_INTERNAL_KEYSTORE_PASSWORD  — used as the keystore/key password
#   CMA_GRPC_ROTATE_CERT            — set to true to force re-generation even if file exists
#   DATA_DIR                        — base data directory (for ssl/ sub-directory)
#   JAVA_HOME                       — must point to a JDK installation
ensure_grpc_keystore() {
  # When SSL is configured the ssl Spring profile overrides grpc.server.security.key-store
  # to the user-provided SSL keystore — no internal gRPC keystore is needed.
  if [ "${CMA_SERVER_SSL}" = true ]; then
    return 0
  fi
  [ -n "${DATA_DIR}" ] || { echo "ERROR: DATA_DIR environment variable is not set" >&2; exit 1; }
  local keystore_file="${CMA_KEYSTORE_FILE#file:}"
  if [ -n "${keystore_file}" ] && [ -f "${keystore_file}" ]; then
    echo "Using existing CMA_KEYSTORE_FILE for gRPC TLS: $(basename ${keystore_file})" | tee -a "${LOG_FILE:-/dev/null}"
    return 0
  fi

  local grpc_p12="${DATA_DIR}/ssl/grpc.p12"
  mkdir -p "${DATA_DIR}/ssl"

  if [ "${CMA_GRPC_ROTATE_CERT:-false}" = "true" ] || [ ! -f "${grpc_p12}" ]; then
    echo "Generating internal gRPC TLS certificate at $(basename ${grpc_p12})..." | tee -a "${LOG_FILE:-/dev/null}"
    CMA_KEYSTORE_FILE="${grpc_p12}" \
    CMA_KEYSTORE_PASSWORD="${CMA_INTERNAL_KEYSTORE_PASSWORD}" \
    CMA_KEYSTORE_TYPE="pkcs12" \
    CMA_KEYSTORE_VALIDITY="3650" \
    CMA_SSL_DIR="${DATA_DIR}/ssl" \
    "${SCRIPT_PATH}/setup-ssl.sh" generate-grpc || { echo "ERROR: Failed to generate gRPC keystore" >&2; exit 1; }
  else
    echo "Reusing existing internal gRPC certificate at $(basename ${grpc_p12})" | tee -a "${LOG_FILE:-/dev/null}"
  fi

  export CMA_KEYSTORE_FILE="file:${grpc_p12}"
  export CMA_KEYSTORE_PASSWORD="${CMA_INTERNAL_KEYSTORE_PASSWORD}"
  export CMA_KEYSTORE_TYPE="PKCS12"
  export CMA_GRPC_KEYSTORE_PATH="file:${grpc_p12}"
  export CMA_GRPC_KEYSTORE_PASSWORD="${CMA_INTERNAL_KEYSTORE_PASSWORD}"
}

# create_cma_agent_auth_conf: write conf/agent-auth.properties with the agent OAuth2
# client credentials.  Must be called on both the master/authz-server side (so the
# authz-server can register the cma-agent OAuth2 client) and on the agent side (so the
# agent can obtain tokens at startup).
#
# Note: cma.master.idp-uri is intentionally omitted here — it belongs in
# cma-agent.properties alongside the master connection settings, not in the credential
# file that the authz-server reads.
#
# Expected env vars (set before calling):
#   CMA_AGENT_AUTH_CONF_FILE  — destination path (defaults to ${CMA_CONF_DIR}/agent-auth.properties)
#   CMA_AGENT_CLIENT_ID       — OAuth2 client ID   (default: cma-agent)
#   CMA_AGENT_CLIENT_SECRET   — OAuth2 client secret (must be non-empty)
create_cma_agent_auth_conf() {
  if [ "${SKIP_CREATING_CMA_AGENT_AUTH_CONF_FILE}" = true ]; then
    echo "Skipping creating cma agent auth conf file as SKIP_CREATING_CMA_AGENT_AUTH_CONF_FILE is set to true."
    return
  fi

  local conf_file="${CMA_AGENT_AUTH_CONF_FILE:-${CMA_CONF_DIR}/agent-auth.properties}"
  if [ -f "${conf_file}" ]; then
    echo "Skipping creating agent-auth.properties: ${conf_file} already exists." | tee -a "${LOG_FILE}"
    return
  fi

  local client_id="${CMA_AGENT_CLIENT_ID:-cma-agent}"
  local client_secret="${CMA_AGENT_CLIENT_SECRET}"

  mkdir -p "$(dirname "${conf_file}")"
  echo "Create agent-auth.properties ..." | tee -a "${LOG_FILE}"

  cat > "${conf_file}" <<EOF
# CMA Agent OAuth2 client credentials
# Generated by deployment tooling — do not edit manually
cma.agent.client-id=${client_id}
cma.agent.client-secret=${client_secret}
EOF

  chmod 600 "${conf_file}"
}

# create_agent_db_conf: write conf/agent-db.properties with the cma-agent.db.*
# database connection credentials.  Skips silently if the file already exists so
# that saved credentials are preserved across restarts.
#
# Expected env vars (set before calling):
#   CMA_AGENT_DB_CONF_FILE  — destination path (defaults to ${CMA_CONF_DIR}/agent-db.properties)
#   CMA_AGENT_DB_HOST       — database hostname        (default: localhost)
#   CMA_AGENT_DB_PORT       — database port            (default: 5432)
#   CMA_AGENT_DB_NAME       — database name            (default: cma-agent)
#   CMA_AGENT_DBA_USER      — database user            (default: admin)
#   CMA_AGENT_DBA_PASS      — database password        (must be non-empty)
create_agent_db_conf() {
  local db_conf_file="${CMA_AGENT_DB_CONF_FILE:-${CMA_CONF_DIR}/agent-db.properties}"
  if [ -f "${db_conf_file}" ]; then
    echo "Skipping creating agent db conf: ${db_conf_file} already exists." | tee -a "${LOG_FILE}"
    return
  fi

  mkdir -p "$(dirname "${db_conf_file}")"
  echo "Create agent-db.properties ..." | tee -a "${LOG_FILE}"

  cat > "${db_conf_file}" <<EOF
# CMA Agent database credentials
# Generated by deployment tooling — do not edit manually
cma-agent.db.host=${CMA_AGENT_DB_HOST:-localhost}
cma-agent.db.port=${CMA_AGENT_DB_PORT:-5432}
cma-agent.db.name=${CMA_AGENT_DB_NAME:-cma-agent}
cma-agent.db.user=${CMA_AGENT_DBA_USER:-admin}
cma-agent.db.pass=${CMA_AGENT_DBA_PASS}
EOF

  chmod 600 "${db_conf_file}"
}

# Loads cma.agent.client-* credentials from an existing agent-auth.properties into env vars.
# Called before prompting so a persisted secret is reused on restart.
load_cma_agent_auth_conf() {
  local conf_file="${CMA_AGENT_AUTH_CONF_FILE:-${CMA_CONF_DIR}/agent-auth.properties}"
  if [ ! -f "${conf_file}" ]; then
    return
  fi
  # Pre-populate env vars from the existing file (only if not already set by the caller)
  local saved_client_id=$(get_cma_conf_value "cma.agent.client-id" "${conf_file}")
  local saved_client_secret=$(get_cma_conf_value "cma.agent.client-secret" "${conf_file}")
  CMA_AGENT_CLIENT_ID="${CMA_AGENT_CLIENT_ID:-${saved_client_id}}"
  CMA_AGENT_CLIENT_SECRET="${CMA_AGENT_CLIENT_SECRET:-${saved_client_secret}}"
}


# load_agent_db_conf: read an existing agent-db.properties and populate
# CMA_AGENT_DBA_USER / CMA_AGENT_DBA_PASS env vars (only if not already set),
# so saved credentials are reused on restart without re-prompting.
#
# Expected env vars (optional):
#   CMA_AGENT_DB_CONF_FILE  — source path (defaults to ${CMA_CONF_DIR}/agent-db.properties)
load_agent_db_conf() {
  local db_conf_file="${CMA_AGENT_DB_CONF_FILE:-${CMA_CONF_DIR}/agent-db.properties}"
  if [ ! -f "${db_conf_file}" ]; then
    return
  fi

  CMA_AGENT_DBA_USER="${CMA_AGENT_DBA_USER:-$(get_cma_conf_value "cma-agent.db.user" "${db_conf_file}")}"
  CMA_AGENT_DBA_PASS="${CMA_AGENT_DBA_PASS:-$(get_cma_conf_value "cma-agent.db.pass" "${db_conf_file}")}"
}

prompt_for_agent_client_secret() {
  # prompt for agent OAuth2 client secret if not provided
  if [ -z "${CMA_AGENT_CLIENT_SECRET}" ] && [ -t 0 ]; then
    while [ -z "${CMA_AGENT_CLIENT_SECRET}" ]; do
      read -r -s -p "Enter Agent OAuth2 client secret: " CMA_AGENT_CLIENT_SECRET
      echo
      if [ -z "${CMA_AGENT_CLIENT_SECRET}" ]; then
        echo "Error: CMA Agent OAuth2 client secret cannot be empty." >&2
      fi
    done
    export CMA_AGENT_CLIENT_SECRET
  elif [ -z "${CMA_AGENT_CLIENT_SECRET}" ]; then
    echo "Error: CMA Agent OAuth2 client secret (CMA_AGENT_CLIENT_SECRET) is required." >&2
    exit 1
  fi
}

prompt_for_agent_secrets() {
  # prompt for database password if not provided
  if [ -z "${CMA_AGENT_DBA_PASS}" ] && [ -t 0 ]; then
    while [ -z "${CMA_AGENT_DBA_PASS}" ]; do
      read -r -s -p "Enter database password: " CMA_AGENT_DBA_PASS
      echo
      if [ -z "${CMA_AGENT_DBA_PASS}" ]; then
        echo "Error: CMA Agent database password cannot be empty." >&2
      fi
    done
    export CMA_AGENT_DBA_PASS
  elif [ -z "${CMA_AGENT_DBA_PASS}" ]; then
    echo "Error: CMA Agent database password (CMA_AGENT_DBA_PASS) is required." >&2
    exit 1
  fi

  prompt_for_agent_client_secret
}

run_ssl_setup() {
  local service_type="${1}"
  local deployment_type="${2}"
  shift 2
  local ssl_args=("$@")
  export CMA_CLI_COMMAND+=" ssl"
  ${SCRIPT_PATH}/setup-ssl.sh --service-type "${service_type}" --deployment-type "${deployment_type}" "${ssl_args[@]}"
}
