#!/usr/bin/env bash

### Constants
readonly CLIENT_TOOL="MXB/bin/ClientTool"
readonly REDEEM_ENCRYPTION_KEY_CONFIGURED="encryptionKeyConfigured"
readonly REEDEM_MANAGED_ENCRYPTION_KEY="managedEncryptionKey"
readonly ENCRYPTION_KEY_UPLOADED_TO_CLOUD="encryptionKeyHashUploadedToCloud"
readonly SET_ENCRYPTION_KEY_INVALID_KEY="invalidKey"
readonly SET_ENCRYPTION_KEY_ERR_CODE_INVALID_KEY=200
readonly KEY_IS_NOT_UPLOADED_TO_CLOUD=0
readonly KEY_IS_UPLOADED_TO_CLOUD=1
readonly CONFIG_FILE_PATH="/opt/MXB/etc/config.ini"
readonly PREVIOUS_CONFIG_FILE_PATH="${CONFIG_FILE_PATH}~"

. "MXB/sbin/proxy_settings_utils-fp.sh"
. "MXB/sbin/ui_utils-fp.sh"
. "MXB/sbin/ct_utils-fp.sh"

PROXY_PARAMETERS=()

# Prints message when redeem is successful
print_successful_redeem_message()
{
    echo "Managed encryption key has been accepted."
    echo ""
    echo "Encryption key has been configured."
    echo ""
    print_dash_line
}

# Print message before reading encryption key from user during installation
print_enter_encryption_key_install()
{
    echo "ENTER AN ENCRYPTION KEY"
    echo ""

    echo -n "The encryption key will encrypt the data on this device, ensuring backups are safe. "
    echo "It ensures files are sent and stored encrypted, so only you can read and use them."
    echo ""

    client_tool_print_encryption_key_requirements
    print_delimiter
}

# Print message before reading encryption key from user during reinstallation
print_enter_encryption_key_reinstall()
{
    echo "ENTER THE ENCRYPTION KEY FOR THIS DEVICE"
    echo ""
    echo "The encryption key encrypts the data on this device, ensuring backups are safe."
    echo ""
    echo "The encryption key is set by the user during the initial installation process."
    echo "We do not store this key on our system."
    echo ""
}

# Prints message that encryption key passed by argument is used
print_using_argument_enryption_key()
{
    echo "Using encryption key from installation command."
}

# Prints message that encryption key passed by argument is accepted
print_argument_enryption_key_accepted()
{
    echo ""
    echo "Encryption key has been successfully accepted."
    echo ""
    print_dash_line
}

# Print message before reading passphrase from user
print_enter_passphrase()
{
    echo "ENTER THE PASSPHRASE FOR THIS DEVICE"
    echo ""
}

# Print message that passphrase passed by argument is used
print_using_argument_passphrase()
{
    echo "Using passphrase from installation command."
}

# Print message that passphrase passed by argument is accepted
print_argument_passphrase_accepted()
{
    echo ""
    echo "Passphrase has been successfully accepted."
    echo ""
    print_dash_line
}

### ClientTool utility functions
client_tool_print_error()
{
    print_error "$@ - See ClientTool log for more information."
}

# Execute ClientTool install.encryption-key.request command
# Arguments:
# $1: Passphrase
client_tool_request_encryption_key()
{
    local passphrase="$1"

    local output
    output="$("${CLIENT_TOOL}" -machine-readable install.encryption-key.request -passphrase "${passphrase}" -config-path="${CONFIG_FILE_PATH}" 2>/dev/null)" || return $?
    echo "${output}"
}

# Execute ClientTool installation.redeem command
# Arguments:
# $1: Installation token
client_tool_redeem()
{
    local installationToken="$1"

    local output
    output="$("${CLIENT_TOOL}" -machine-readable installation.redeem -installation-token="${installationToken}" -config-path="${CONFIG_FILE_PATH}" "${PROXY_PARAMETERS[@]}")"
    local errorCode=$?
    if [ ${errorCode} -ne 0 ]; then
        client_tool_print_error "Redeem device failed."
        restore_config
        return 1
    fi

    echo "${output}"
}

# Execute ClientTool password.requirements.get command
client_tool_print_encryption_key_requirements()
{
    echo "The encryption key must contain:"
    echo ""
    "${CLIENT_TOOL}" password.requirements.get
}

# Execute ClientTool password.requirements.check command
# Arguments:
# $1: Encryption key
client_tool_validate_encryption_key()
{
    local encryptionKey="$1"
    "${CLIENT_TOOL}" -machine-readable password.requirements.check -password="${encryptionKey}" >/dev/null 2>&1
}

# Execute ClientTool install.encryption-key.is-configured command
client_tool_check_encryption_key_configuration()
{
    local output

    output="$("${CLIENT_TOOL}" -machine-readable install.encryption-key.is-configured -config-path="${CONFIG_FILE_PATH}")"
    local errorCode=$?
    if [ ${errorCode} -ne 0 ]; then
        client_tool_print_error "Error checking if the encryption key is uploaded."
        return 1
    fi

    echo "${output}"
}

# Execute ClientTool install.encryption-key.set command
# Arguments:
# $1: Encryption key
# $2: Force key flag (0 or 1)
client_tool_set_encryption_key()
{
    local encryptionKey="$1"
    local forceKey=$2
    local parameters=(-machine-readable install.encryption-key.set -encryption-key="${encryptionKey}" -config-path="${CONFIG_FILE_PATH}")

    if [ "${forceKey}" -eq 1 ]; then
        parameters+=("-force-key")
    fi

    local output
    output="$("${CLIENT_TOOL}" "${parameters[@]}")"

    local errorCode=$?
    if [ ${errorCode} -ne 0 ]; then
        client_tool_print_error "Error setting the encryption key."
        return 1
    fi

    echo "${output}"
}

# Validate package name against the expected format: cove#v1#<installation-token>.
# Installation token must be a GUID.
# Arguments:
# $1: Package name
validate_package_name()
{
    local packageName="$1"
    if [ -z "$packageName" ]; then
        print_error "Package name not specified. Use --package-name=\"<name>\""
        return 1
    fi

    local installationToken="$(echo "${packageName}" | awk -F '#' '{print $3}')"
    local guidPattern="^[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}$"
    if ! [[ "${installationToken}" =~ ${guidPattern} ]]; then
        print_error "Package contains a malformed installation token."
        return 1
    fi
}

# Handles the retrieval of encryption key from the user, performing confirmation and validation if required.
# Argument:
# $1: Confirm encryption key flag (0 or 1)
# $2: Validate encryption key flag (0 or 1)
# $3: Encryption key variable to be filled
read_encryption_key_from_user()
{
    local requireConfirmation="$1"
    local requireValidation="$2"
    local encryptionKeyRef="$3"
    local isValidKey=0

    while [ ${isValidKey} -eq 0 ]; do
        echo -n "Please enter the encryption key: "
        read -s firstEncryptionKey
        echo ""

        if [ ${requireValidation} -eq 1 ]; then
            client_tool_validate_encryption_key "${firstEncryptionKey}"
            local validationStatus=$?
            if [ ${validationStatus} -ne 0 ]; then
                print_message_with_dash "The encryption key does not meet the requirements. Please try again."
                continue
            fi
        fi

        if [ ${requireConfirmation} -eq 1 ]; then
            echo ""
            echo -n "Please confirm the encryption key: "
            read -s secondEncryptionKey
            echo ""

            if [ "${firstEncryptionKey}" != "${secondEncryptionKey}" ]; then
                print_message_with_dash "The encryption keys do not match. Please try again."
                continue
            fi
        fi

        printf -v "${encryptionKeyRef}" "%s" "${firstEncryptionKey}"
        isValidKey=1
    done
}

# Handles the retrieval of passphrase from the user
# Argument:
# $1: Passphrase variable to be filled
read_passphrase_key_from_user()
{
    local passphraseRef="$1"

    echo -n "Please enter the passphrase: "
    read -s passphrase
    echo ""

    printf -v "${passphraseRef}" "%s" "${passphrase}"
}

# Set the given encryption key through ClientTool application
# Arguments:
# $1: Encryption key
# $2: HasKeyBeenUploadedToCloud flag (0 or 1)
set_encryption_key()
{
    local encryptionKey="$1"
    local forceKey="$2"
    local setEncryptionKeyOutput

    setEncryptionKeyOutput="$(client_tool_set_encryption_key "${encryptionKey}" ${forceKey})" || return $?
    if [ "${setEncryptionKeyOutput}" = "${SET_ENCRYPTION_KEY_INVALID_KEY}" ]; then
        return ${SET_ENCRYPTION_KEY_ERR_CODE_INVALID_KEY}
    fi
}

# Read and tries to set encryption key
# Arguments:
# $1: Encryption key
# $2: HasKeyBeenUploadedToCloud flag (0 or 1)
read_and_set_encryption_key_interactively()
{
    local encryptionKey="$1"
    local hasKeyBeenUploadedToCloud="$2"
    local requireConfirmation=0
    local requireValidation=0

    local isValidKey=0
    while [ ${isValidKey} -eq 0 ]; do
        read_encryption_key_from_user ${requireConfirmation} ${requireValidation} encryptionKey
        set_encryption_key "${encryptionKey}" ${hasKeyBeenUploadedToCloud}
        local setEncryptionKeyStatus=$?
        if [ ${setEncryptionKeyStatus} -eq ${SET_ENCRYPTION_KEY_ERR_CODE_INVALID_KEY} ]; then
            print_message_with_dash "The encryption key is incorrect. Please try again."
            continue
        elif [ ${setEncryptionKeyStatus} -ne 0 ]; then
            return 1
        fi
        isValidKey=1
    done
}

# Choose appropriate flow during install or reinstall to retrieve the encryption key
# Arguments:
# $1: Encryption key
configure_encryption_key()
{
    local encryptionKey="$1"
    local keyConfigOutput
    keyConfigOutput="$(client_tool_check_encryption_key_configuration)" || return $?
    if [ "${keyConfigOutput}" = "${ENCRYPTION_KEY_UPLOADED_TO_CLOUD}" ]; then
        configure_encryption_key_reinstall "${encryptionKey}" || return 1
    else
        configure_encryption_key_install "${encryptionKey}" || return 1
    fi
}

# Retrieve the encryption key, if not passed by parameter, and configure it
# through ClientTool.
# Arguments:
# $1: Encryption key
configure_encryption_key_install()
{
    local encryptionKey="$1"
    local requireConfirmation=1
    local requireValidation=1
    local interactiveInputUsed=0

    if [ -z "${encryptionKey}" ]; then
        # Encryption key was not given, ask the user for it
        interactiveInputUsed=1
        print_enter_encryption_key_install
        read_encryption_key_from_user ${requireConfirmation} ${requireValidation} encryptionKey
    else
        print_using_argument_enryption_key
        client_tool_validate_encryption_key "${encryptionKey}"
        local validationStatus=$?
        if [ ${validationStatus} -ne 0 ]; then
            print_error "The encryption key does not meet the requirements."
            return 1
        fi
    fi

    set_encryption_key "${encryptionKey}" ${KEY_IS_NOT_UPLOADED_TO_CLOUD} || return $?

    if [ ${interactiveInputUsed} -eq 1 ]; then
        echo ""
        print_block_message "Encryption key has been successfully created.\n\nPLEASE NOTE: We do not store this key, so please ensure you keep it safe, stored somewhere other than on this device."
    else
        print_argument_enryption_key_accepted
    fi
}

# Retrieve the encryption key, if not passed by parameter, and configure it
# through ClientTool during the reinstallation process
# Arguments:
# $1: Encryption key
configure_encryption_key_reinstall()
{
    local encryptionKey="$1"

    if [ -z "${encryptionKey}" ]; then
        # Encryption key was not given, ask the user for it
        print_enter_encryption_key_reinstall
        read_and_set_encryption_key_interactively "${encryptionKey}" ${KEY_IS_UPLOADED_TO_CLOUD} || return 1
        echo ""
        print_block_message "Encryption key has been successfully entered."

    else
        print_using_argument_enryption_key
        set_encryption_key "${encryptionKey}" ${KEY_IS_UPLOADED_TO_CLOUD}
        local setEncryptionKeyStatus=$?
        if [ ${setEncryptionKeyStatus} -ne 0 ]; then
            print_error "Setting encryption key failed"
            return 1
        fi
        print_argument_enryption_key_accepted
    fi
}

# Retrieve the passphrase, if not passed by parameter and
# request encryption key through ClientTool
# Arguments:
# $1: Passphrase
configure_encryption_key_by_passphrase()
{
    local passphrase="$1"
    local encryptionKey

    if [ -z "${passphrase}" ]; then
        # Passphrase key was not given, ask the user for it
        print_enter_passphrase

        local isValidPassphrase=0
        while [ ${isValidPassphrase} -eq 0 ]; do
            read_passphrase_key_from_user passphrase

            encryptionKey="$(client_tool_request_encryption_key "${passphrase}")"
            local requestEncryptionKeyStatus=$?
            if [ ${requestEncryptionKeyStatus} -ne 0 ]; then
                print_message_with_dash "Passphrase is invalid. Please try again."
                continue
            fi

            isValidPassphrase=1
        done

        echo ""
        print_block_message "Passphrase has been successfully accepted."
    else
        print_using_argument_passphrase

        encryptionKey=("$(client_tool_request_encryption_key "${passphrase}")")
        local requestEncryptionKeyStatus=$?
        if [ ${requestEncryptionKeyStatus} -ne 0 ]; then
            print_error "Invalid passphrase"
            return 1
        fi

        print_argument_passphrase_accepted
    fi

    set_encryption_key "${encryptionKey}" ${KEY_IS_UPLOADED_TO_CLOUD}
    local setEncryptionKeyStatus=$?
    if [ ${setEncryptionKeyStatus} -ne 0 ]; then
        print_error "Setting encryption key failed"
        return 1
    fi
}

# Arguments:
# $1: Package name string to be filled
# $2: Encryption key string to be filled
# $3: Passphrase string to be filled
# $@:4: arguments to be parsed
parse_arguments()
{
    local packageNameRef="$1"
    local encryptionKeyRef="$2"
    local passphraseRef="$3"
    shift 3

    local parsedArguments=()
    while [ $# -gt 0 ]; do
        local arg="$1"
        shift

        local argName="${arg%%=*}"
        local argValue="${arg#*=}"
        [ -z "${argName}" ] && argName="${arg}"

        case "${argName}" in
        --package-name)
            printf -v "${packageNameRef}" "%s" "${argValue}"
            ;;
        --encryption-key)
            printf -v "${encryptionKeyRef}" "%s" "${argValue}"
            ;;
        --passphrase)
            printf -v "${passphraseRef}" "%s" "${argValue}"
            ;;

        *)
            local cleanArgName="${argName:2}"
            if [[ " ${PROXY_PARAMS[@]} " =~ " ${cleanArgName} " ]]; then
                PROXY_PARAMETERS+=("${argName}=${argValue}")
            else
                print_error "Unknown option: ${argName}"
                return 1
            fi
            ;;
        esac

        if [[ " ${parsedArguments[@]} " =~ " ${argName} " ]]; then
            print_error "Parameter ${argName} specified more than once."
            return 1
        fi

        parsedArguments+=("${argName}")
    done
}

remove_backed_up_config()
{
    if [ -e "${PREVIOUS_CONFIG_FILE_PATH}" ]; then
        rm -rf "${PREVIOUS_CONFIG_FILE_PATH}" >/dev/null 2>&1
    fi
}

backup_existing_config()
{
    remove_backed_up_config

    if [ -f "${CONFIG_FILE_PATH}" ]; then
        cp -f "${CONFIG_FILE_PATH}" "${PREVIOUS_CONFIG_FILE_PATH}" >/dev/null 2>&1
    fi
}

restore_config()
{
    if [ -e "${CONFIG_FILE_PATH}" ]; then
       rm -rf "${CONFIG_FILE_PATH}" >/dev/null 2>&1
    fi

    if [ -f "${PREVIOUS_CONFIG_FILE_PATH}" ]; then
        mv -f "${PREVIOUS_CONFIG_FILE_PATH}" "${CONFIG_FILE_PATH}" >/dev/null 2>&1
    fi
}

sigint_handler()
{
    restore_config
    exit 1
}

main()
{
    if [ "$(id -u)" != "0" ]; then
        print_error "This script should be run by root (which you are not)"
        return 1
    fi

    trap sigint_handler SIGINT

    print_header_message "INSTALL BACKUP MANAGER"

    local packageName=""
    local encryptionKey=""
    local passphrase=""
    parse_arguments packageName encryptionKey passphrase "$@" || return $?
    packageName="${packageName%.*}"

    validate_package_name "${packageName}" || return $?
    validate_proxy_parameters "${PROXY_PARAMETERS[@]}" || return $?

    backup_existing_config

    local installationToken="$(echo "${packageName}" | awk -F '#' '{print $3}')"
    local redeemOutput
    redeemOutput=("$(client_tool_redeem "${installationToken}")") || return $?
    local redeemOutputFiltered=" $(echo "${redeemOutput[@]}" | tr '\n' ' ') " 

    if [[ ${redeemOutputFiltered} =~ " ${REDEEM_ENCRYPTION_KEY_CONFIGURED} " ]]; then
        print_successful_redeem_message
    else
        local configureEncryptionKeyStatus
        if [[ ${redeemOutputFiltered} =~ " ${REEDEM_MANAGED_ENCRYPTION_KEY} " ]]; then
            configure_encryption_key_by_passphrase "${passphrase}"
            configureEncryptionKeyStatus=$?
        else
            configure_encryption_key "${encryptionKey}"
            configureEncryptionKeyStatus=$?
        fi

        if [ ${configureEncryptionKeyStatus} -ne 0 ]; then
            restore_config
            return 1
        fi
    fi

    client_tool_install_wizard_disable "${CONFIG_FILE_PATH}" >/dev/null
    local errorCode=$?
    if [ ${errorCode} -eq 0 ]; then
        remove_backed_up_config
    else
        restore_config
        return ${errorCode}
    fi

    echo ""
}

main "$@"
