#!/bin/bash

export PATH="${PATH}:/bin:/sbin:/usr/bin:/usr/sbin"

readonly AEMBIT_GROUP_NAME="aembit"
readonly AEMBIT_MCP_GATEWAY_USER_NAME="aembit_mcp_gateway"
readonly AEMBIT_MCP_GATEWAY_SYSTEMD_UNIT="aembit_mcp_gateway.service"
readonly AEMBIT_MCP_GATEWAY_VERSION="1.26.3770"
readonly AEMBIT_BASE_INSTALL_DIR="/opt/aembit"
readonly AEMBIT_MCP_GATEWAY_INSTALL_DIR="${AEMBIT_BASE_INSTALL_DIR}/edge/mcp_gateway"
readonly AEMBIT_MCP_GATEWAY_INSTALL_DIR_BIN="${AEMBIT_MCP_GATEWAY_INSTALL_DIR}"/"${AEMBIT_MCP_GATEWAY_VERSION}"/bin
readonly AEMBIT_MCP_GATEWAY_INSTALL_DIR_SCRIPTS="${AEMBIT_MCP_GATEWAY_INSTALL_DIR}"/"${AEMBIT_MCP_GATEWAY_VERSION}"/scripts
readonly AEMBIT_MCP_GATEWAY_JOURNALD_CONFIG_FILE="/etc/systemd/journald@aembit_mcp_gateway.conf"
readonly AEMBIT_MCP_GATEWAY_VERSION_PATTERN="[[:digit:]]*.[[:digit:]]*.[[:digit:]]*"
readonly AEMBIT_MCP_GATEWAY_BIN="aembit_mcp_gateway"

INSTALLER_DIR=$(dirname "${0}")
readonly LOG_FILE="${INSTALLER_DIR}"/installer.log

readonly SYSTEMD_UNIT_FILES_FOLDER="/etc/systemd/system"
readonly AEMBIT_MCP_GATEWAY_SYSTEMD_UNIT_PATH="${SYSTEMD_UNIT_FILES_FOLDER}"/"${AEMBIT_MCP_GATEWAY_SYSTEMD_UNIT}"
readonly MIN_SYSTEMD_VERSION_WITH_NAMESPACE_JOURNALS=245
readonly DEFAULT_FD_LIMIT=65535

# OPTIONS
TO_UPGRADE=false

parse_options() {
    for _ in $(getopt --options "" --longoptions "upgrade" -- "${@}"); do
        case $1 in
            --upgrade)
                TO_UPGRADE=true ;;
        esac
        shift
    done

}

log() {
    local level="${1}"
    shift

    local log_fmt="%s %s\n"
    if [ "${level}" ]; then
        log_fmt="%s ${level} %s\n"
    fi

    for line in "$@"; do
        # shellcheck disable=SC2059
        printf "${log_fmt}" "$(date +"%H:%M:%S")" "${line}" | tee -a "${LOG_FILE}"
    done
}

log_info() {
    log "Info:" "$@"
}

log_warn() {
    log "Warning:" "$@"
}

log_err() {
    log "Error:" "$@"
}

check_running_as_root() {
    if [ "$(id --user)" -ne 0 ]; then
        log_err "Installer must be run as root."
        exit 1
    fi
}

check_user_file_access() {
    user=$1
    file_path=$2

    if sudo -u "$user" test -r "$file_path" && sudo -u "$user" test -x "$file_path"; then
        return 0
    else
        log_err "User '${user}' does not have access to ${file_path}"
        return 1
    fi
}

check_for_required_env_var() {
    env_var_name=$1
    env_var_value="${!env_var_name}"

    if [ -z "${env_var_value}" ]; then
        log_err "Required environment variable \"${env_var_name}\" is not defined."
        exit 1
    fi
}

check_for_optional_env_vars() {
    until [ -z "$1" ]; do
        env_var_name=$1
        # `env_var_value` contents will be `unset` if the var is unset and `null` if it's set but null/empty string
        env_var_value="${!env_var_name-unset}"
        shift

        if [ -z "${env_var_value}" ]; then
            log_err "Optional environment variable \"${env_var_name}\" is set to null but a value is required."
            exit 1
        fi
    done
}

check_if_binary_missing() {
    binary=$1

    if ! which "${binary}" > /dev/null 2>&1; then
        if [ -z "${missing_binaries}" ]; then
            missing_binaries="${binary}"
        else
            missing_binaries="${missing_binaries}, ${binary}"
        fi
    fi
}

check_required_dependencies() {
    log_info "Checking for required package dependencies."

    check_if_binary_missing pgrep
    check_if_binary_missing grep
    check_if_binary_missing id
    check_if_binary_missing getent
    check_if_binary_missing useradd
    check_if_binary_missing groupadd
    check_if_binary_missing dmidecode

    if [ -n "${missing_binaries}" ]; then
        log_err "One or more necessary binaries were missing. Please install the following from the appropriate packages and then rerun the installer: ${missing_binaries}"
        exit 1
    fi
}


mcp_gateway_is_already_installed() {
    if ! [ -d "${AEMBIT_MCP_GATEWAY_INSTALL_DIR}" ]; then
        return 1
    fi

    for file in ${AEMBIT_MCP_GATEWAY_INSTALL_DIR}/${AEMBIT_MCP_GATEWAY_VERSION_PATTERN}; do
        # If there is no match, $file just refers to the pattern, instead of a
        # file name. No file exists with the name of the pattern literal.
        if [ -e "${file}" ]; then
            return 0
        else
            return 1
        fi
    done
}

mcp_gateway_is_already_running() {
    if pgrep --euid "${AEMBIT_MCP_GATEWAY_USER_NAME}" &>/dev/null; then
        return 0
    else
        return 1
    fi
}

# Whether the installer is being run in upgrade mode.
is_upgrade() {
    if [ "${TO_UPGRADE}" == "true" ]; then
        return 0
    else
        return 1
    fi
}

# Exits 0 if the current platform is $1, else exits with 1.
#
# Examples:
# * is_platform rhel
# * is_platform ubuntu
# * is_platform "Red Hat Enterprise Linux"
# * is_platform Jammy
is_platform() {
    platform=$1
    val=$(cat /etc/*-release | grep -c "$platform")
    if [[ $val -gt 0 ]]; then
        return 0
    else
        return 1
    fi
}

handle_existing_mcp_gateway() {
    if (mcp_gateway_is_already_running || mcp_gateway_is_already_installed) && ! is_upgrade; then
        log_err "Aembit MCP Gateway is already installed. Please uninstall it first or use the \"--upgrade\" option."
        exit 1
    fi
}

create_user_and_group() {
    if getent group "${AEMBIT_GROUP_NAME}" > /dev/null 2>&1; then
        log_info "Group \"${AEMBIT_GROUP_NAME}\" exists"
    else
        log_info "Group \"${AEMBIT_GROUP_NAME}\" does not exist. Creating."
        if groupadd "${AEMBIT_GROUP_NAME}"; then
            log_info "Group created"
        else
            log_err "Error creating the \"${AEMBIT_GROUP_NAME}\" group."
            exit 1
        fi
    fi

    if getent passwd "${AEMBIT_MCP_GATEWAY_USER_NAME}" > /dev/null 2>&1; then
        log_info "User \"${AEMBIT_MCP_GATEWAY_USER_NAME}\" exists"
    else
        log_info "User \"${AEMBIT_MCP_GATEWAY_USER_NAME}\" does not exist. Creating."
        if useradd --system --shell /bin/bash  --gid "${AEMBIT_GROUP_NAME}" "${AEMBIT_MCP_GATEWAY_USER_NAME}"; then
            log_info "User created"
        else
            log_err "There was an error creating the \"${AEMBIT_MCP_GATEWAY_USER_NAME}\" user."
            exit 1
        fi
    fi
}

set_file_permissions() {
    file_path=$1
    permissions=$2

    if ! chmod "${permissions}" "${file_path}"; then
        log_err "Unable to set the permissions of ${file_path}."
        exit 1
    fi
}

set_file_user_and_group_owner() {
    file_path=$1
    user=$2
    group=$3

    if ! chown "${user}:${group}" "${file_path}"; then
        log_err "Unable to set the ownership of ${file_path}."
        exit 1
    fi
}

set_recursive_file_permissions() {
    file_path=$1
    permissions=$2

    if ! chmod --recursive "${permissions}" "${file_path}"; then
        log_err "Unable to set the permissions of ${file_path}."
        exit 1
    fi
}

set_recursive_file_user_and_group_owner() {
    file_path=$1
    user=$2
    group=$3

    if ! chown --recursive "${user}:${group}" "${file_path}"; then
        log_err "Unable to set the ownership of ${file_path}."
        exit 1
    fi
}

# Sets the group owner and permissions for all directories from $base_dir to $leaf_dir, inclusive.
set_group_owner_and_permissions_in_range() {
    base_dir=$1
    leaf_dir=$2
    perms=$3
    group=$4

    current_dir="${leaf_dir}"
    while true; do
        chgrp "${group}" "${current_dir}"
        chmod "${perms}" "${current_dir}"
        if [ "${current_dir}" = "${base_dir}" ]; then
            break
        fi
        current_dir="$(dirname "${current_dir}")"
    done
}

# get systemd version using systemctl
get_systemd_version() {
    systemd_version_output=$(systemctl --version)
    echo "$systemd_version_output" | grep -oP 'systemd \K\d+' | tr -dc '0-9'
}

set_up_logs() {

    # From "man systemd-journald.service"
    # "On systems where /var/log/journal/ does not exist yet but where persistent logging is
    #  desired (and the default journald.conf is used), it is sufficient to create the
    #  directory, and ensure it has the correct access modes and ownership:"
    if [ ! -d "/var/log/journal" ]; then
        mkdir -p /var/log/journal
        chgrp systemd-journal /var/log/journal
        if ! systemd-tmpfiles --create --prefix /var/log/journal; then
            exit 1
        fi
    fi

    if [ "$(get_systemd_version)" -ge "${MIN_SYSTEMD_VERSION_WITH_NAMESPACE_JOURNALS}" ]; then
        if ! cp "${INSTALLER_DIR}"/installer_components/journald@aembit_mcp_gateway.conf "${AEMBIT_MCP_GATEWAY_JOURNALD_CONFIG_FILE}"; then
            log_err "Unable to copy the journald configuration file to host."
            exit 1
        fi

        set_file_permissions "${AEMBIT_MCP_GATEWAY_JOURNALD_CONFIG_FILE}" 400 || exit 1
        set_file_user_and_group_owner "${AEMBIT_MCP_GATEWAY_JOURNALD_CONFIG_FILE}" root root || exit 1
    fi
}

copy_static_components_to_host() {
    if ! mkdir --parents "${AEMBIT_MCP_GATEWAY_INSTALL_DIR_BIN}"; then
        log_err "Unable to make installation directory for MCP Gateway binary."
        exit 1
    fi

    if ! cp "${INSTALLER_DIR}"/aembit_mcp_gateway "${AEMBIT_MCP_GATEWAY_INSTALL_DIR_BIN}"/"${AEMBIT_MCP_GATEWAY_BIN}"; then
        log_err "Unable to copy the MCP Gateway binary to the installation directory."
        exit 1
    fi

    if ! mkdir --parents "${AEMBIT_MCP_GATEWAY_INSTALL_DIR_SCRIPTS}"; then
        log_err "Unable to make installation directory for MCP Gateway scripts."
        exit 1
    fi

    if ! cp "${INSTALLER_DIR}"/uninstall "${AEMBIT_MCP_GATEWAY_INSTALL_DIR_SCRIPTS}"/uninstall; then
        log_err "Unable to copy the MCP Gateway uninstall script to the installation directory."
        exit 1
    fi

    # Ensure the installed files are accessible throughout the installation dir structure.
    # Needed when installing into environments with very restrictive umasks.
    set_group_owner_and_permissions_in_range "${AEMBIT_BASE_INSTALL_DIR}" "${AEMBIT_MCP_GATEWAY_INSTALL_DIR}/.." 550 "${AEMBIT_GROUP_NAME}"
    set_recursive_file_permissions "${AEMBIT_MCP_GATEWAY_INSTALL_DIR}" 500
    set_recursive_file_user_and_group_owner "${AEMBIT_MCP_GATEWAY_INSTALL_DIR}" "${AEMBIT_MCP_GATEWAY_USER_NAME}" "${AEMBIT_GROUP_NAME}"
}

replace_in_service_unit() {
  template_text="${1?:replace_in_service_unit requires a template pattern arg}"
  replacement_text="${2?:replace_in_service_unit requires replacement text arg}"
  sed --in-place "s#${template_text}#${replacement_text}#" "${AEMBIT_MCP_GATEWAY_SYSTEMD_UNIT_PATH}"
}

install_systemd_service() {
    if ! cp "${INSTALLER_DIR}"/installer_components/aembit_mcp_gateway.service "${AEMBIT_MCP_GATEWAY_SYSTEMD_UNIT_PATH}"; then
        log_err "Unable to copy the systemd service file to the destination."
        exit 1
    fi

    set_file_permissions "${SYSTEMD_UNIT_FILES_FOLDER}/${AEMBIT_MCP_GATEWAY_SYSTEMD_UNIT}" 400 || exit 1
    set_file_user_and_group_owner "${SYSTEMD_UNIT_FILES_FOLDER}/${AEMBIT_MCP_GATEWAY_SYSTEMD_UNIT}" root root || exit 1   

    # Sets the file descriptor limit for the MCP Gateway service.
    fd_limit=$DEFAULT_FD_LIMIT
    if [ -n "${AEMBIT_FD_LIMIT}" ]; then
        fd_limit=$AEMBIT_FD_LIMIT
    fi

    if ! replace_in_service_unit "{{ FD_LIMIT }}" "${fd_limit}"; then
        log_err "Unable to update file descriptor limit in the systemd service file."
        exit 1
    fi

    if ! replace_in_service_unit "{{ AUTHORIZATION_SERVER }}" "${AEMBIT_AUTHORIZATION_SERVER}"; then
        log_err "Unable to update the Authorization Server location in the systemd service file."
        exit 1
    fi

    if ! replace_in_service_unit "{{ MCP_GATEWAY_URL }}" "${AEMBIT_MCP_GATEWAY_URL}"; then
        log_err "Unable to update the MCP Gateway URL in the systemd service file."
        exit 1
    fi

    if ! replace_in_service_unit "{{ UPSTREAM_MCP_SERVER_URL }}" "${AEMBIT_UPSTREAM_MCP_SERVER_URL}"; then
        log_err "Unable to update the Upstream MCP Server URL in the systemd service file."
        exit 1
    fi

    if [ -n "${AEMBIT_TLS_CERT_CHAIN_PATH}" ] && [ -n "${AEMBIT_TLS_PRIVATE_KEY_PATH}" ]; then
        tls_args="--tls-cert-chain-path ${AEMBIT_MCP_GATEWAY_INSTALL_DIR}/tls.crt --tls-private-key-path ${AEMBIT_MCP_GATEWAY_INSTALL_DIR}/tls.key"
    else
        tls_args=""
    fi

    if ! replace_in_service_unit "{{ OPTIONAL_TLS_ARGS }}" "${tls_args}"; then
        log_err "Unable to update optional TLS arguments in the systemd service file."
        exit 1
    fi

    # Sets MCP Gateway's log level if the user has provided one.
    aembit_log_level_replacement_value='info'
    if [ -n "${AEMBIT_LOG_LEVEL}" ]; then
        aembit_log_level_replacement_value="$AEMBIT_LOG_LEVEL"
    fi

    if ! replace_in_service_unit "{{ LOG_LEVEL }}" "${aembit_log_level_replacement_value}"; then
        log_err "Unable to update the Aembit log level in the systemd service file."
        exit 1
    fi

    if ! replace_in_service_unit "{{ AGENT_CONTROLLER_URL }}" "${AEMBIT_AGENT_CONTROLLER_URL}"; then
        log_err "Unable to update the Aembit Agent Controller URL in the systemd service file."
        exit 1
    fi

    # Add LogNamespace only if systemd version is 245 or greater (when LogNamespace was introduced).
    # Added in version 245.
    # See LogNamespace section in https://www.man7.org/linux/man-pages/man5/systemd.exec.5.html
    aembit_log_namespace_value=''
    if [ "$(get_systemd_version)" -ge "${MIN_SYSTEMD_VERSION_WITH_NAMESPACE_JOURNALS}" ]; then
        aembit_log_namespace_value="LogNamespace=$AEMBIT_MCP_GATEWAY_USER_NAME"
    fi

    # If systemd version is prior to 245, a blank line will be present in the unit file.
    if ! replace_in_service_unit "{{ OPTIONAL_AEMBIT_LOG_NAMESPACE }}" "${aembit_log_namespace_value}"; then
        log_err "Unable to update optional log namespace in the systemd service file."
        exit 1
    fi

    if ! systemctl enable "${AEMBIT_MCP_GATEWAY_SYSTEMD_UNIT}"; then
        log_err "Unable to enable the MCP Gateway systemd service."
        exit 1
    fi
}

start_systemd_service() {
    if ! systemctl start "${AEMBIT_MCP_GATEWAY_SYSTEMD_UNIT}"; then
        log_err "Unable to start the MCP Gateway systemd service."
        exit 1
    fi
}

upgrade_systemd_service() {
    # The MCP Gateway needs to be stopped and started, instead of just
    # restarted, because it needs to remove its existing iptables rules
    # prior to referencing the updated ones.

    # If the MCP Gateway service is not running or not loaded, continue
    # with the upgrade process.
    systemctl stop "${AEMBIT_MCP_GATEWAY_SYSTEMD_UNIT}" 2> /dev/null

    install_systemd_service || exit 1

    if ! systemctl daemon-reload; then
        log_err "Unable to reload the MCP Gateway systemd service."
        exit 1
    fi

    if ! systemctl start "${AEMBIT_MCP_GATEWAY_SYSTEMD_UNIT}"; then
        log_err "Unable to start the updated MCP Gateway systemd service."
        exit 1
    fi

    if ! systemctl restart systemd-journald; then
        log_err "Unable to restart journald with the updated log configuration."
        exit 1
    fi
}

remove_all_previous_installations() {
    log_info "Deleting old MCP Gateway versions."
    for dir in ${AEMBIT_MCP_GATEWAY_INSTALL_DIR}/${AEMBIT_MCP_GATEWAY_VERSION_PATTERN}; do
        case "${dir}" in
            # Don't delete the upgraded MCP Gateway version.
            *"${AEMBIT_MCP_GATEWAY_VERSION}"*)
                # Do nothing
                ;;
            *)
                rm -rf "${dir}"
                ;;
        esac
    done
}

copy_tls_cert() {
    if [[ -z "${AEMBIT_TLS_CERT_CHAIN_PATH}" && -z "${AEMBIT_TLS_PRIVATE_KEY_PATH}" ]]; then
        log_info "AEMBIT_TLS_CERT_CHAIN_PATH and AEMBIT_TLS_PRIVATE_KEY_PATH are not set. Skipping TLS config."
        return 0
    fi

    if [[ ( -z "${AEMBIT_TLS_CERT_CHAIN_PATH}" && -n "${AEMBIT_TLS_PRIVATE_KEY_PATH}" ) || ( -n "${AEMBIT_TLS_CERT_CHAIN_PATH}" && -z "${AEMBIT_TLS_PRIVATE_KEY_PATH}" ) ]]; then
        log_err "Both AEMBIT_TLS_CERT_CHAIN_PATH and AEMBIT_TLS_PRIVATE_KEY_PATH must be set together."
        return 1
    fi

    if [[ ! -r "${AEMBIT_TLS_CERT_CHAIN_PATH}" || ! -r "${AEMBIT_TLS_PRIVATE_KEY_PATH}" ]]; then
        log_err "TLS certificate or private key file is missing or not accessible: '${AEMBIT_TLS_CERT_CHAIN_PATH}', '${AEMBIT_TLS_PRIVATE_KEY_PATH}'."
        return 1
    fi

    # Copy files and set permissions
    cp "${AEMBIT_TLS_CERT_CHAIN_PATH}" "${AEMBIT_MCP_GATEWAY_INSTALL_DIR}/tls.crt"
    cp "${AEMBIT_TLS_PRIVATE_KEY_PATH}" "${AEMBIT_MCP_GATEWAY_INSTALL_DIR}/tls.key"
    set_file_user_and_group_owner "${AEMBIT_MCP_GATEWAY_INSTALL_DIR}/tls.crt" "${AEMBIT_MCP_GATEWAY_USER_NAME}" "${AEMBIT_GROUP_NAME}"
    set_file_user_and_group_owner "${AEMBIT_MCP_GATEWAY_INSTALL_DIR}/tls.key" "${AEMBIT_MCP_GATEWAY_USER_NAME}" "${AEMBIT_GROUP_NAME}"
    set_file_permissions "${AEMBIT_MCP_GATEWAY_INSTALL_DIR}/tls.crt" 400
    set_file_permissions "${AEMBIT_MCP_GATEWAY_INSTALL_DIR}/tls.key" 400
    log_info "Copied TLS pem and key."
}

# Read and set environment variables based upon the environment variables of the
# installed MCP gateway proxy service.
# These environment variables can be overwritten by the ones passed as part of the
# installation script.
read_env_vars_from_mcpgateway_service() {
    env_vars=$(systemctl show -p Environment ${AEMBIT_MCP_GATEWAY_SYSTEMD_UNIT} | cut -d '=' -f 2-)

    # Split the string into an array
    IFS=' ' read -r -a env_array <<< "$env_vars"

    # Iterate over the array and set all the environment values which are not
    # set.
    for var in "${env_array[@]}"; do
        # Split each element into variable name and value
        IFS='=' read -r key value <<< "$var"
        if [[ ! -v "${key}" ]] && [ -n "$value" ]; then
            export "$key"="$value"
        fi
    done
}

run_installer() {
    check_running_as_root || exit 1
    parse_options "${@}"

    if is_upgrade; then
        read_env_vars_from_mcpgateway_service
    fi

    check_for_required_env_var AEMBIT_AUTHORIZATION_SERVER || exit 1
    check_for_required_env_var AEMBIT_MCP_GATEWAY_URL || exit 1
    check_for_required_env_var AEMBIT_UPSTREAM_MCP_SERVER_URL || exit 1
    check_for_required_env_var AEMBIT_AGENT_CONTROLLER_URL || exit 1

    check_required_dependencies || exit 1
    handle_existing_mcp_gateway || exit 1
    create_user_and_group || exit 1
    set_up_logs || exit 1
    copy_static_components_to_host || exit 1
    copy_tls_cert || exit 1
    check_user_file_access "${AEMBIT_MCP_GATEWAY_USER_NAME}" "${AEMBIT_MCP_GATEWAY_INSTALL_DIR_BIN}/${AEMBIT_MCP_GATEWAY_BIN}"  || exit 1

    if is_upgrade; then
        upgrade_systemd_service || exit 1
        remove_all_previous_installations || exit 1
    else
        install_systemd_service || exit 1
        start_systemd_service || exit 1
    fi
}

main() {
    set -o pipefail
    log_info "Installing Aembit MCP Gateway"
    run_installer "${@}" 2>&1
}

main "${@}"|| exit 1
