#!/bin/bash

# Copyright 2020 MongoDB Inc
#
# 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.

set -o nounset
set -o errexit

DIR=
DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )"
readonly DIR
SCRIPT=
SCRIPT=$(basename "${BASH_SOURCE[0]}")
readonly SCRIPT

declare -a ssh_opts
ssh_opts[0]="-o"
ssh_opts[1]="UserKnownHostsFile=/dev/null"
ssh_opts[2]="-o"
ssh_opts[3]="StrictHostKeyChecking=no"
ssh_opts[4]="-q"
ssh_opts[5]="-o"
ssh_opts[6]="ConnectTimeout=30"
ssh_opts[7]="-o"
ssh_opts[8]="TCPKeepAlive=yes"
ssh_opts[9]="-o"
ssh_opts[10]="ServerAliveInterval=15"
ssh_opts[11]="-o"
ssh_opts[12]="ServerAliveCountMax=20"
#ssh_opts[13]="-vvv" # DEBUG mode
readonly ssh_opts
readonly timeout_duration="25m"

# Commands
LSB_RELEASE="$(command -v lsb_release || echo "lsb_release")"
SCP="$(command -v scp || echo "scp")"
SSH="$(command -v ssh || echo "ssh")"
UNAME="$(command -v uname || echo "uname")"


_fix_term() {
    # Set a TERM env var
    if [[ -z "${TERM+x}" ]]; then
      export TERM=xterm
    fi

    # Do not allow Debian distros to have an interactive term
    if [[ "${PLATFORM}" = "debian" || "${PLATFORM}" = "ubuntu" ]]; then
      export DEBIAN_FRONTEND="noninteractive"
    fi
}

# #######################################################
#                   SYSTEM DEPENDENCIES
# #######################################################
readonly E_UNSUPPORTED_DISTRO=145
_unsupported_distro() {
    echo "Unsupported distro..."
    if "$UNAME" > /dev/null 2>&1; then
        echo
        echo "uname:"
        uname -a
    fi
    if "$LSB_RELEASE" > /dev/null 2>&1; then
        echo
        echo "lsb_release:"
        "$LSB_RELEASE" -a
    fi
    if [[ -f /etc/SuSe-release ]]; then
        echo
        echo "/etc/SuSe-release:"
        cat /etc/SuSE-release
    fi
    if [[ -f /etc/os-release ]]; then
        echo
        echo "/etc/os-release:"
        cat /etc/os-release
    fi
    exit ${E_UNSUPPORTED_DISTRO}
}
system_info() {
    echo
    echo "System information"
    echo "---------------------"
    echo "Platform: ${PLATFORM}"
    if [[ "${OSVER}" != -1 ]]; then
        echo "OS version: ${OSVER}"
    fi
    echo "---------------------"
    echo
}
# Determine architecture and version
_determine_architecture() {
    PLATFORM=""
    OSVER=-1
    if [[ -n ${OS+x} && "Windows_NT" = "${OS}" ]]; then
        PLATFORM="windows"

    elif uname -a | grep -q Debian > /dev/null 2>&1; then # Debian
        PLATFORM="debian"
        if grep -qE '^10' /etc/debian_version; then
            OSVER=10

        elif grep -qE '^9' /etc/debian_version; then
            OSVER=9

        elif grep -qE '^8' /etc/debian_version; then
            OSVER=8

        else
            _unsupported_distro
        fi

    elif uname -a | grep -q Ubuntu > /dev/null 2>&1; then # Ubuntu
        PLATFORM="ubuntu"

        if "$LSB_RELEASE" -a 2>&1 | grep 20.04 > /dev/null 2>&1; then
            OSVER=20

        elif "$LSB_RELEASE" -a 2>&1 | grep 19.04 > /dev/null 2>&1; then
            OSVER=19

        elif "$LSB_RELEASE" -a 2>&1 | grep 18.04 > /dev/null 2>&1; then
            OSVER=18

        else
            _unsupported_distro
        fi

    elif uname -a | grep -q Darwin; then # Darwin/MacOS
        PLATFORM="macos"

    elif "$LSB_RELEASE" -a 2>&1 | grep -qE "^Description:\\s+Red Hat.+ 8" > /dev/null; then # Red Hat 8
        PLATFORM="rhel"
        OSVER="8"

    elif "$LSB_RELEASE" -a 2>&1 | grep -qE "^Description:\\s+Red Hat.+ 7" > /dev/null; then # Red Hat 7
        PLATFORM="rhel"
        OSVER="7"

    elif [[ -f /etc/os-release ]] && grep PRETTY /etc/os-release | grep -qE 'Red Hat.+ 8' > /dev/null; then # Red Hat 8
        PLATFORM="rhel"
        OSVER="8"

    elif [[ -f /etc/os-release ]] && grep PRETTY /etc/os-release | grep -qE 'Red Hat.+ 7' > /dev/null; then # Red Hat 7
        PLATFORM="rhel"
        OSVER="7"

    elif [[ -f /etc/system-release ]] && grep -qE 'Red Hat.+ 8' /etc/system-release > /dev/null; then # Red Hat 8
        PLATFORM="rhel"
        OSVER="8"

    elif [[ -f /etc/system-release ]] && grep -qE 'Red Hat.+ 7' /etc/system-release > /dev/null; then # Red Hat 7
        PLATFORM="rhel"
        OSVER="7"

    elif "$LSB_RELEASE" -a 2>&1 | grep -qE "^Description:\\s+Red Hat.+ 6" > /dev/null; then
        PLATFORM="rhel"
        OSVER="6"

    elif [[ -f /etc/os-release ]] && grep PRETTY /etc/os-release | grep -qE 'Red Hat.+ 6' > /dev/null; then # Red Hat 6
        PLATFORM="rhel"
        OSVER="6"

    elif [[ -f /etc/os-release ]] && grep PRETTY /etc/os-release | grep -qE 'SUSE.+15' > /dev/null; then # SUSE 15
        PLATFORM="suse"
        OSVER=15

    elif [[ -f /etc/system-release ]] && grep -qE 'Red Hat.+ 6' /etc/system-release > /dev/null; then # Red Hat 6
        PLATFORM="rhel"
        OSVER="6"

    elif [[ -f /etc/SuSE-release ]] && grep -q SUSE /etc/SuSE-release > /dev/null; then # SUSE
        PLATFORM="suse"
        if grep VERSION /etc/SuSE-release | grep -q 12 > /dev/null; then # SUSE 12
            OSVER=12
        elif grep VERSION /etc/SuSE-release | grep -q 15 > /dev/null; then # SUSE 15
            OSVER=15
        else
            _unsupported_distro
        fi

    elif [[ -f /etc/issue ]] && grep -qE 'SUSE.+ 12' /etc/issue > /dev/null; then # SUSE 12
        PLATFORM="suse"
        OSVER=12

    elif [[ -f /etc/issue ]] && grep -qE 'SUSE.+ 15' /etc/issue > /dev/null; then # SUSE 15
        PLATFORM="suse"
        OSVER=15

    elif [[ -f /etc/os-release ]] && grep PRETTY /etc/os-release | grep -q 'Amazon Linux 2' > /dev/null; then # Amazon Linux 2
        PLATFORM="amazon"
        OSVER=2

    else
        _unsupported_distro
    fi

    # Declare vars are readonly
    readonly PLATFORM
    readonly OSVER

    # Print platform details
    system_info
}


# #######################################################
#                        HELPERS
# #######################################################


replace_property_in_file() {
    # Parameter check
    if [[ "$#" -lt 3 ]]; then
        echo "Invalid call: 'replace_property_in_file $*'"
        echo "Usage: replace_property_in_file FILENAME PROPERTY VALUE"
        echo
        exit 1
    fi

    # Set the new property
    temp_file=$(mktemp)
    grep -vE "^\\s*${2}\\s*=" "${1}" > "${temp_file}" # Export contents minus any lines containing the specified property
    echo "${2}=${3}" >> "${temp_file}"                # Set the new property value
    cat "${temp_file}" > "${1}"                       # Replace the contents of the original file, while preserving any permissions
    rm "${temp_file}"
}

# #######################################################
#                SYSTEM/TEST DEPENDENCIES
# #######################################################

_wait_for_process_to_finish() {
    echo "Waiting for ${timeout_duration} for $1 to complete any running jobs..."

    timeout ${timeout_duration} bash -c 'while pgrep '"$1"' > /dev/null 2>&1; do printf .; sleep 5; done'
}

resolve_system_dependencies() {
    # Update host system
    if [[ "${PLATFORM}" = "debian" ]] || [[ "${PLATFORM}" = "ubuntu" ]]; then # Debian/Ubuntu
        _wait_for_process_to_finish apt
        _wait_for_process_to_finish dpkg
        apt-get update
        apt-get install -y -q --no-install-recommends openssl

    elif [[ "${PLATFORM}" = "rhel" ]] || [[ "${PLATFORM}" = "amazon" ]]; then # Red Hat / Amazon Linux
        _wait_for_process_to_finish yum

    elif [[ "${PLATFORM}" = "suse" ]]; then # SuSe
        _wait_for_process_to_finish zypper

    elif [[ "${PLATFORM}" = "windows" ]]; then # Windows
        echo "Cannot update System Dependencies in Cygwin..."
        return
    fi
}

install_agent() {
    # Parameter check
    if [[ "$#" -eq 0 ]]; then
        echo "An baseUrl was not specified!"
        echo "Usage: install_agent BASE_URL"
        echo
        exit 1
    fi

    echo "Downloading and extracting the automation agent"

    if [[ "${PLATFORM}" = "debian" ]] || [[ ${PLATFORM} = "ubuntu" ]]; then # Debian/Ubuntu
        curl -OL "$1download/agent/automation/mongodb-mms-automation-agent-manager_latest_amd64.ubuntu1604.deb"
        dpkg -i mongodb-mms-automation-agent-manager_latest_amd64.ubuntu1604.deb

    elif [[ "${PLATFORM}" = "rhel" ]] || [[ ${PLATFORM} = "amazon" ]] || [[ "${PLATFORM}" = "suse" ]]; then # Red Hat / Amazon Linux / SUSE
        curl -OL "$1download/agent/automation/mongodb-mms-automation-agent-manager-latest.x86_64.rhel7.rpm"
        rpm -U mongodb-mms-automation-agent-manager-latest.x86_64.rhel7.rpm

    else
        echo "Could not install $1. Unsupported package manager or distribution!"
        echo
        _unsupported_distro
    fi
}

_user_dir() {
    if [[ -n ${SUDO_USER+x} ]]; then
         # If executing through sudo, retrieve the calling user's dir
        eval echo "~${SUDO_USER}"
    else
        # Otherwise use the home dir
        echo "${HOME}"
    fi
}

# #######################################################
#                       SCENARIOS
# #######################################################

scenario_install_agent() {
    local baseUrl="https://cloud.mongodb.com"
    local ops_manager=0

    while [[ "$#" -gt 0 ]]; do
        case "$1" in
            --baseUrl) baseUrl=$2; shift 2 ;;
            --ops-manager) ops_manager=1; shift;;
            * ) echo "Invalid option for installing mongodb $1"; return 1 ;;
        esac
    done
    if [[ ! "${LC_AGENT_KEY:-}"  ||  ! "${LC_GROUP_ID:-}" ]]; then
        {
          echo 'error: LC_AGENT_KEY or LC_GROUP_ID was not specified'
          echo
        } >&2
        exit 1
    fi

    resolve_system_dependencies
    install_agent "${baseUrl}"

    echo "Replacing mmsGroupId and mmsApiKey"
    replace_property_in_file /etc/mongodb-mms/automation-agent.config mmsGroupId "${LC_GROUP_ID}"
    replace_property_in_file /etc/mongodb-mms/automation-agent.config mmsApiKey "${LC_AGENT_KEY}"

    if [[ "${ops_manager}" -eq 1 ]]; then
      echo "Replacing mmsBaseUrl with ${baseUrl::-1}"
      replace_property_in_file /etc/mongodb-mms/automation-agent.config mmsBaseUrl "${baseUrl::-1}"
    fi

    echo "Preparing the /data directory to store your MongoDB data"
    mkdir -p /data
    chown -R mongodb:mongodb /data

    echo "Starting the agent"
    systemctl start mongodb-mms-automation-agent.service
}

readonly NON_ROOT_FUNCTIONS="run seed ssh system_info"

# #######################################################
#             SEED THE TARGET ENVIRONMENT
# #######################################################
run() {
    # Print usage info
    if [[ "$#" -lt 2 ]]; then
        echo "Usage: run user@hostname [args...]"
        echo
        exit 1
    fi

    # Determine args
    host=$1
    shift
    args=$*

    echo "Running remote command '${args}' on ${host}..."

    # shellcheck disable=SC2086
    "$SSH" -tt -A "${ssh_opts[@]}" ${SSH_OPTS-} "${host}" ${args}
}

scp() {
    # Print usage info
    if [[ "$#" -lt 2 ]]; then
        echo "Usage: scp <source> <destination>"
        echo
        exit 1
    fi

    # shellcheck disable=SC2016,SC2086
    if ! "$SCP" -r "${ssh_opts[@]}" ${SSH_OPTS-} "$1" "$2"; then
        # Retry once, with increased verbosity
        # shellcheck disable=SC2016,SC2086
        "$SCP" -r -vvv "${ssh_opts[@]}" ${SSH_OPTS-} "$1" "$2"
    fi
}

update_bin_path() {
    if [[ "$#" -lt 1 ]]; then
        echo "An output filename was not specified!"
        echo
        exit 1
    fi

    for f in "$@"; do
        PATHFILE="$(_user_dir)/$f"

        # Check that the output file is writeable
        if [[ -f "${PATHFILE}" && ! -w "${PATHFILE}" ]]; then
            echo "Could not write to ${PATHFILE}!"
            echo
            return 1
        fi

        # Do not append bin/ to PATH, if already set
        CONF_HEADER="# EGO env settings"
        if grep -q "${CONF_HEADER}" "${PATHFILE}"; then
            return 0
        fi

        echo ""                             | tee -a "${PATHFILE}"
        echo "${CONF_HEADER}"               | tee -a "${PATHFILE}"
        # shellcheck disable=SC2016
        echo 'export PATH=$HOME/bin:$PATH'  | tee -a "${PATHFILE}"
        echo "Updated system path to include ${HOME}/bin (via ${PATHFILE})"
    done
}

seed() {
    # Print usage info
    if [[ "$#" -lt 1 ]]; then
        echo "Usage: seed user@hostname"
        echo
        exit 1
    fi

    echo "Deploying ${SCRIPT} to $1..."
    scp "${DIR}/${SCRIPT}" "$1:"

    # shellcheck disable=SC2016
    exec_cmd='mkdir -p $HOME/bin; mv '${SCRIPT}' $HOME/bin/; $HOME/bin/'${SCRIPT}' update_bin_path .bashrc .bash_profile'

    # shellcheck disable=SC2016,SC2086
    if ! "$SSH" -tt "${ssh_opts[@]}" ${SSH_OPTS-} "$1" ${exec_cmd}; then
        # Retry once, with increased verbosity

        # shellcheck disable=SC2016,SC2086
        "$SSH" -vvv -tt "${ssh_opts[@]}" ${SSH_OPTS-} "$1" ${exec_cmd}
    fi
    echo "Deployed ${SCRIPT} to $1..."
}

ssh() {
    # Parameter check
    if [[ "$#" -lt 1 ]]; then
        echo "Invalid call 'ssh $*'"
        echo "Usage: ssh user@hostname"
        echo
        exit 1
    fi

    # Connects to remote host
    echo "Connecting to $1..."
    # shellcheck disable=SC2086
    if ! "$SSH" -tt -A "${ssh_opts[@]}" ${SSH_OPTS-} "$1" /bin/bash; then
        # Retry once, with increased verbosity
        "$SSH" -vvv -tt -A "${ssh_opts[@]}" ${SSH_OPTS-} "$1" /bin/bash
    fi
}


# #######################################################
#                           MAIN
# #######################################################
_available_functions() {
    declare -F | awk '{print $3}' | grep -vE '^_'
}
_usage() {
    echo "Usage: ${0} FUNCTION"
    echo
    echo "Available functions:"
    echo "------------------"
    for func in $(_available_functions); do echo "$func"; done
    echo "------------------"
    echo
    exit 1
}

main() {
    # If no parameters were specified, print the usage help
    if [[ "$#" -eq 0 ]]; then
        _usage
    fi

    _determine_architecture

    # Run as super user, unless the functions do not require it, or the script is run without any parameters
    if [[ ${NON_ROOT_FUNCTIONS} != *"$1"* ]]; then
        [[ "$UID" -eq 0 || "${PLATFORM}" = "windows" ]] || exec sudo -SE "$0" "$@"
    fi

    # Set a TERM var if not already in the env
    _fix_term

    "$@"
}

main "$@"

