#!/bin/bash

####################################################################################################
#
# Example:
#    Deploy and run cli against master
#         ./cli-setup.sh
#    Deploy and run cli against master
#         ./cli-setup.sh
#    Local deployment using the current branch debugging enabled (DEVELOPMENT ONLY!)
#         ./cli-setup.sh -l -d -b=$(git branch 2> /dev/null | sed -e '/^[^*]/d' -e 's/* \(.*\)/\1/')
#
####################################################################################################

set -o pipefail

start_time=$(date +"%s.%N")

assert() {
    if [ $# -gt 0 ]; then stdout_log "ASSERT: ${1}"; fi
    if [[ -f ${log_file} ]]; then
        echo -e "\n\n"
	echo ""
	echo "Installation failed, Here are the last 10 lines from the log"
	echo "The full installation log is available at ${log_file}"
	echo "If more information is needed re-run the install with --debug"
	echo "$(tail ${log_file})"
    else
	echo "Installation failed prior to log file being created"
	echo "Try re-running with --debug"
	echo "Installation instructions: https://docs.platform9.com/kubernetes/PMK-CLI/#installation"
    fi
    exit 1
}

debugging() {
    # This function handles formatting all debugging text.
    # debugging is always sent to the logfile.
    # If no debug flag, this function will silently log debugging messages.
    # If debug flag is present then debug output will be formatted then echo'd to stdout and sent to logfile.
    # If debug flag is present messages sent to stdout_log will be forwarded to debugging for consistancy.

    # Avoid error if bc is not installed yet
    if (which bc > /dev/null 2>&1); then
	output="DEBUGGING: $(date +"%T") : $(bc <<<$(date +"%s.%N")-${start_time}) :$(basename $0) : ${1}"
    else
	output="DEBUGGING: $(date +"%T") : $(basename $0) : ${1}"
    fi

    if [ -f "${log_file}" ]; then
	echo "${output}" 2>&1 >> ${log_file}
    fi
    if [[ ${debug_flag} ]]; then
	echo "${output}"
    fi
}

stdout_log(){
    # If debug flag is present messages sent to stdout_log will be forwarded to debugging for consistancy.
    if [[ ${debug_flag} ]]; then
	debugging "$1"
    else
        echo "$1"
	debugging "$1"
    fi
}

parse_args() {
    while [ $# -gt 0 ]; do
        case ${1} in
            -h|--help)
                echo "Usage: $(basename $0)"
                #echo "	  [--branch=] Specify a different branch to pull Platform9 CLI source"
                #echo "	  [--dev] Installs from local source code for each project in editable mode."
                #echo "                This assumes you have provided all source code in the correct locations"
                #echo "	  [--local] Installs local source code in the same directory"
                echo "	  [-d|--debug] Uses debug verbosity during install"
                echo ""
                exit 0
                ;;
            --branch=*)
                if [[ -n ${1#*=} ]]; then
                branch="${1#*=}"
                else
                assert "'--branch=' Requires a Branch name"
                fi
                shift
                ;;
            -d|--debug)
                debug_flag="${1#*=}"
                shift
                ;;
            --install_only)
                install_only=TRUE
                shift
                ;;
            --dev)
                dev_build="--dev"
                shift
                ;;
            --dev-key)
                dev_key="--dev-key"
                shift
                ;;
            --disable-analytics)
                disable_analytics="--disable-analytics"
                shift
                ;;
            --local)
                run_local="--local"
                shift
                ;;
            --pf9_account_url)
                PF9_MGMTURL=${2}
                shift 2
                ;;
            --pf9_email)
                PF9_USER=${2}
                shift 2
                ;;
            --pf9_password)
                PF9_PASS=${2}
                shift 2
                ;;
            --pf9_region)
                PF9_REGION=${2}
                shift 2
                ;;
            --pf9_project)
                PF9_TENANT=${2}
                shift 2
                ;;
            --skip-checks)
                skip_checks="true"
                shift
                ;;
            *)
                echo "${1} is not a valid command line option."
                echo ""
                echo "For help, please use $0 -h"
                echo ""
                exit 1
                ;;
        esac
    done
}

init_venv_python() {
    debugging "Virtualenv: ${venv} doesn't not exist, Configuring."
    if [[ ${platform} == 'centos' ]]; then
        python_version=2
    else
        for ver in {3,2,''}; do #ensure python3 is first
        debugging "Checking Python${ver}: $(which python${ver})"
            if (which python${ver} > /dev/null 2>&1); then
            python_version="$(python${ver} <<< 'import sys; print(sys.version_info[0])')"
            stdout_log "Python Version Selected: python${python_version}"
            break
            fi
        done
    fi

    if [[ ${python_version} == 2 ]]; then
        pyver="";
    else
        pyver="3";
    fi
    stdout_log "Initializing Virtual Environment using Python ${python_version}"
    #Validate and initialize virtualenv
    if ! (${local_virtualenv} --version > /dev/null 2>&1); then
        debugging "Validating pip"
	if ! which ${local_pip} > /dev/null 2>&1; then
            debugging "ERROR: missing package: pip (attempting to install using get-pip.py)"
            curl -s -o ${pip_path} ${pip_url}
            if [ ! -r ${pip_path} ]; then assert "failed to download get-pip.py (from ${pip_url})"; fi

            if ! (python${pyver} "${pip_path}" --user); then
                debugging "ERROR: failed to install package: pip (attempting to install via 'sudo get-pip.py')"
                if (sudo python${pyver} "${pip_path}" --user > /dev/null 2>&1); then
                    assert "Please install package: pip"
                fi
            fi
        fi
	debugging "ERROR: missing python package: virtualenv (attempting to install via 'pip install virtualenv')"
        # Attemping to Install virtualenv
        if ! (${local_pip}${pyver} install virtualenv --user --ignore-installed > /dev/null 2>&1); then
            debugging "ERROR: failed to install python package (attempting to install via 'sudo pip install virtualenv')"
            if ! (sudo pip${pyver} install virtualenv --user --ignore-installed > /dev/null 2>&1); then
                assert "Please install the 'virtualenv' module using 'pip install virtualenv'"
            else
                # Fix setuptools to be below a certain version to workaround this issue
                # https://github.com/pypa/setuptools/issues/2352
                debugging "INFO: sudo pip${pyver} install setuptools<50.0 --force-reinstall > /dev/null 2>&1"
                sudo pip${pyver} install "setuptools<50.0" --force-reinstall > /dev/null 2>&1
            fi
        else
            # Fix setuptools to be below a certain version to workaround this issue
            # https://github.com/pypa/setuptools/issues/2352
            debugging "INFO: ${local_pip}${pyver} install setuptools<50.0 --force-reinstall > /dev/null 2>&1"
            sudo ${local_pip}${pyver} install "setuptools<50.0" --force-reinstall > /dev/null 2>&1
        fi
    fi
    debugging "INFO: ${local_virtualenv} -p python${pyver} --system-site-packages ${venv} > /dev/null 2>&1"
    if ! (${local_virtualenv} -p python${pyver} --system-site-packages ${venv} > /dev/null 2>&1); then
        assert "Creation of virtual environment failed"
    fi
    debugging "venv_python: ${venv_python}"
    if [ ! -r ${venv_python} ]; then assert "failed to initialize virtual environment"; fi
}

initialize_basedir() {
    debugging "Initializing: ${pf9_basedir}"
    if [[ -n "${init_flag}" ]]; then
	debugging "DELETEING Existing State Directories: ${pf9_state_dirs}"
        for dir in ${pf9_state_dirs}; do
	    if [ -d "${dir}" ]; then
		debugging "DELETING ${dir}"
	        rm -rf "${dir}"
	        if [ -d "${dir}" ]; then assert "failed to remove ${dir}"; fi
	    fi
        done
    fi
    for dir in ${pf9_state_dirs}; do
	debugging "Ensuring ${dir} Exist"
        if ! mkdir -p "${dir}" > /dev/null 2>&1; then assert "Failed to create directory: ${dir}"; fi
    done
    debugging "Ensuring ${log_file} Exist"
    if ! mkdir -p "${dir}" > /dev/null 2>&1; then assert "Failed to create directory: ${dir}"; fi
    if ! touch "${log_file}" > /dev/null 2>&1; then assert "failed to create log file: ${log_file}"; fi
}

setup_pf9_bash_profile() {
    debugging "Setting up pf9_bash_profile"
    if [ -f ~/.bashrc ]; then
	bash_config=$(realpath ~/.bashrc)
    elif [ -f ~/.bash_profile ]; then
	bash_config=$(realpath ~/.bash_profile)
    elif [ -f ~/.profile ]; then
	bash_config=$(realpath ~/.profile)
    else
	bash_config=$(realpath ~/.bashrc)
	if ! touch "${bash_config}" >> ${log_file} 2>&1; then
	    debugging "Failed to create ${bash_config}, BASH profile not setup"; fi
    fi
    debugging "Using $bash_config to source ${pf9_bash_profile}"

    debugging "Writing pf9_bash_profile to: ${pf9_bash_profile}"
    # Write pf9_bin=${pf9_bin} to the bash_profile as this needs variable expansion
    echo "pf9_bin=${pf9_bin}" > ${pf9_bash_profile}
    debugging "pf9_bin=$(dirname $(realpath .))"
    # Write the rest of the bash_profile.
    # This is done in 2 steps as only the first line needs variable expansion.
    # The rest of the file must be written with variable test intact.
    cat <<'EOT' >> ${pf9_bash_profile}
if [[ -d "${pf9_bin}" ]]; then
    if ! echo "$PATH" | grep -q "${pf9_bin}"; then
	export PATH="${pf9_bin}:$PATH"
    fi
fi

EOT

    if [ -s ${pf9_bash_profile} ]; then
        debugging "pf9_bash_profile successfully written to: ${pf9_bash_profile}"
	if ! (grep -q "source ${pf9_bash_profile}" ${bash_config}); then
	    debugging "Adding 'source ${pf9_bash_profile}' to: ${bash_config}"
	    echo "source ${pf9_bash_profile}" >> ${bash_config}
	else
	    debugging "${bash_config} already configured to source ${pf9_bash_profile}"
	fi
    else
	assert "Failed to write pf9_bash_profile to: ${pf9_bash_profile}"
    fi
}

create_cli_config(){
    debugging "Creating Platform9 CLI Configuration"
    auth_retry=0
    max_auth_retry=3
    while (( ${auth_retry} < ${max_auth_retry} )); do
	(( auth_retry++ ))
	if [ $auth_retry -gt 1 ]; then
	    stdout_log "Attempt $auth_retry of ${max_auth_retry}:"; fi

    if [ ! -z "${PF9_MGMTURL}" ] && [ ! -z "${PF9_USER}" ] && [ ! -z "${PF9_PASS}" ] && [ ! -z "${PF9_REGION}" ] && [ ! -z "${PF9_TENANT}" ]; then
        eval "${cli_exec}" config create --du_url ${PF9_MGMTURL} --os_username ${PF9_USER} --os_password ${PF9_PASS} --os_region ${PF9_REGION} --os_tenant ${PF9_TENANT} ${dev_key} ${disable_analytics}
    else
        echo ""
        stdout_log "Please provide your Platform9 Credentials"
        eval "${cli_exec}" config create ${dev_key} ${disable_analytics}
    fi

	if (${cli_exec} config validate); then
        stdout_log "Successfully validated the Platform9 account details"
	    break
	else
	    debugging "Config creation failed"
	    if (( $auth_retry == $max_auth_retry )); then
                echo ""
                stdout_log "Max Authentication Attempts Reached."
		stdout_log "You can retry entering your credentials with:"
		echo "    ${cli_exec} config create --help"
		eval "${cli_exec}" config create --help
                echo ""
                echo ""
                echo ""
	    fi
	fi
    done
}

validate_platform() {
  # check if running CentOS 7, Ubuntu 16.04, or Ubuntu 18.04
  source /etc/os-release
  if [[ "$ID" == "ubuntu" ]]; then
     platform="ubuntu"
     if [[ "$VERSION_ID" != "20.04" ]] && [[ "$VERSION_ID" != "18.04" ]] && [[ "$VERSION_ID" != "16.04" ]]; then
         stdout_log "Unsupported Ubuntu version"
         exit 99
     fi
  elif [[ "$ID" == "centos" ]]; then
     platform="centos"
     if [[ "$VERSION_ID" != "7" ]]; then
         stdout_log "Unsupported CentOS version"
         exit 99
     fi
  else
     stdout_log "Unsupported Linux distribution"
     exit 99
  fi

  # check for locale
  if [[ "${LANG}" != *"UTF-8"* ]]; then
    stdout_log "Warning: Expected unicode supported locale like en_US.UTF-8 but found ${LANG}. CLI Installation may fail"
  fi
  echo ${platform}
}

install_prereqs() {
    stdout_log "Validating and installing package dependencies"
    source /etc/os-release
    if [[ "$ID" == "ubuntu" ]]; then
        # add ansible repository
        sudo apt-get update -y > /dev/null 2>&1
        if [ $? -ne 0 ]; then
                echo -e "\nERROR: failed to update apt repository - here's the last 10 lines of the log:\n"
                tail -10 ${log_file}; exit 1
        fi

        for pkg in haveged sshpass git; do
            dpkg-query -f '${binary:Package}\n' -W | grep ^${pkg}$ > /dev/null 2>&1
            if [ $? -ne 0 ]; then
                sudo apt-get -y install ${pkg} >> ${log_file} 2>&1
                if [ $? -ne 0 ]; then
                    echo -e "\nERROR: failed to install ${pkg} - here's the last 10 lines of the log:\n"
                    tail -10 ${log_file}; exit 1
                fi
            fi
        done
        # Install python3-distutils only for 18.04
        if [[ "$VERSION_ID" == "20.04" ]] || [[ "$VERSION_ID" == "18.04" ]]; then
            sudo apt-get -y install python3-distutils >> ${log_file} 2>&1
            if [ $? -ne 0 ]; then
                echo -e "\nERROR: failed to install ${pkg} - here's the last 10 lines of the log:\n"
                tail -10 ${log_file}; exit 1
            fi
        fi
        # Install python3-dev only for WSL
        if cat /proc/version | grep -q 'microsoft'; then
            sudo apt-get -y install python3-dev >> ${log_file} 2>&1
            if [ $? -ne 0 ]; then
                echo -e "\nERROR: failed to install ${pkg} - here's the last 10 lines of the log:\n"
                tail -10 ${log_file}; exit 1
            fi
        fi	
    elif [[ "$ID" == "centos" ]]; then
        sudo rpm -Uvh https://download.fedoraproject.org/pub/epel/epel-release-latest-7.noarch.rpm
        for pkg in git haveged sshpass; do
            sudo yum install -y ${pkg} >> ${log_file} 2>&1
            if [ $? -ne 0 ]; then
                echo -e "\nERROR: failed to install ${pkg} - here's the last 10 lines of the log:\n"
                tail -10 ${log_file}; exit 1
            fi
        done
        sudo haveged
    else
        echo -e "\nERROR: Unsupported platform ${ID}-${VERSION_ID}."; exit 1
    fi
}

show_sudo_notice() {

    # If tty is present, then prompt for confirmation
    if [[ "${TTY_AVAILABLE}" == "true" && "${skip_checks}" == "false" ]]; then
        read -p "SUDO Requirements: The CLI requires SUDO privileges to operate correctly. Confirm your user has SUDO privileges (yes/no): " yn
        case $yn in
            [Yy]* )
                return 0
                ;;
            [Nn]* )
                echo "Setup your user to have SUDO privileges and rerun this step"
                exit 0
                ;;
            *) echo "Please answer yes or no."; exit 1;;
        esac
    else
        stdout_log "** NOTE ** - SUDO Requirements: The CLI requires SUDO privileges to operate correctly. Please ensure your account has SUDO privileges."
    fi
}

## main

# Set the path so double quotes don't use the litteral '~'
pf9_basedir=$(dirname ~/pf9/.)
log_file=${pf9_basedir}/log/cli_install.log
pf9_bin=${pf9_basedir}/bin
venv="${pf9_basedir}/pf9-venv"
pf9_state_dirs="${pf9_bin} ${venv} ${pf9_basedir}/db ${pf9_basedir}/log"
skip_checks="false"

parse_args "$@"

TTY_AVAILABLE="true"
tty -s
if [[ $? != "0" ]]; then
    debugging "No TTY available. Skipping all prompts..."
    TTY_AVAILABLE="false"
fi

# initialize installation directory
initialize_basedir

# Validate & install system packages
platform=$(validate_platform)
show_sudo_notice
install_prereqs

debugging "CLFs: $*"

# Set global variables

if [ -z ${branch} ]; then
    branch=$(git branch 2> /dev/null | sed -e '/^[^*]/d' -e 's/* \(.*\)/\1/')
    if [ $? -ne 0 ]; then
	branch=master
    fi
fi
debugging "Setting environment variables to be passed to installers"

if [[ -z ${run_local} && -z ${dev_build} ]]; then
    cli_url="git+https://github.com/platform9/express-cli.git@${branch}#egg=express-cli"
elif [[ -n ${dev_build} ]]; then
    cli_url="-e .[test]"
elif [[ -n ${run_local} ]]; then
    cli_url="."
fi
debugging "branch: ${branch}"
debugging "cli_url: ${cli_url}"

pip_path=${pf9_basedir}/get_pip.py
venv_python="${venv}/bin/python"
venv_activate="${venv}/bin/activate"
pip_url="https://bootstrap.pypa.io/pip/2.7/get-pip.py"
cli_entrypoint=$(dirname ${venv_python})/express
cli_exec=${pf9_bin}/pf9ctl
pf9_bash_profile=${pf9_bin}/pf9-bash-profile.sh
local_pip=$(dirname ~/.)/.local/bin/pip
local_virtualenv=$(dirname ~/.)/.local/bin/virtualenv

# configure python virtual environment
debugging "Configuring virtualenv"
if [ ! -f "${venv_activate}" ]; then
    init_venv_python
else
    stdout_log "INFO: using exising virtual environment"
fi

stdout_log "Upgrading pip"
# Fix setuptools to be below a certain version to workaround this issue
# https://github.com/pypa/setuptools/issues/2352
if ! (${venv_python} -m pip install --upgrade pip "setuptools<50.0" wheel >> ${log_file} 2>&1); then
    assert "Pip upgrade failed"; fi
debugging "pip install express-cli completed"

stdout_log "Installing Platform9 CLI"
if ! (${venv_python} -m pip install --upgrade --ignore-installed ${cli_url} >> ${log_file} 2>&1); then
    assert "Installation of Platform9 CLI Failed"; fi

if ! (${cli_entrypoint} --help >> ${log_file} 2>&1); then
    assert "Base Installation of Platform9 CLI Failed"; fi

if [ ! -f ${cli_exec} ]; then
    debugging "Creating Platform9 CLI symlink"
    if [ -L ${cli_exec} ]; then
        if ! (rm ${cli_exec} >> ${log_file} 2>&1); then
	        assert "Failed to remove existing symlink: ${cli_exec}"; fi
    fi
    if ! (ln -s ${cli_entrypoint} ${cli_exec} >> ${log_file} 2>&1); then
	    assert "Failed to create Platform9 CLI symlink: ${cli_exec}"; fi
else
    debugging "Platform9 CLI symlink already exist"
fi

# Create symlink in /usr/bin
if [ -L /usr/bin/pf9ctl ]; then
    if ! (sudo rm /usr/bin/pf9ctl >> ${log_file} 2>&1); then
        assert "Failed to remove existing symlink: ${cli_exec}"; fi
fi
if ! (sudo ln -s ${cli_entrypoint} /usr/bin/pf9ctl >> ${log_file} 2>&1); then
    assert "Failed to create Platform9 CLI symlink in /usr/bin"; fi

if ! (${cli_exec} --help >> ${log_file} 2>&1); then
    assert "Installation of Platform9 CLI Failed"; fi

# Setup pf9_bash_profile which creates a bash file that adds ~/pf9/bin to the users path and enables bash-completion
# An entry to source ~/pf9/bin/pf9_bash_profile is created in one of the following ~/.bashrc, ~/.bash_profile, ~/.profile
setup_pf9_bash_profile
if [[ ${bash_config} ]]; then
    eval source "${bash_config}"
fi

if ! [[ -n ${install_only} ]]; then
    # call cli config create which will prompt user to provide PF9 credentials.
    create_cli_config
fi
echo ""
stdout_log "Platform9 CLI installation completed successfully"
echo ""
stdout_log "** SUDO REQUIRED FOR ALL COMMANDS **"
stdout_log "Why is SUDO required?: The CLI performs operations like installing packages and configuring services. These require SUDO privileges to run successfully."
echo ""
echo "To start building a Kubernetes cluster you can execute:"
echo "        ${cli_exec} cluster --help"
echo ""
eval "${cli_exec}" cluster --help