#!/usr/bin/bash
#shellcheck shell=bash

# By default, executing basename $0 directly will update the SM2 root certificate
# to the system keystore, if you want to remove the SM2 root certificate from the
# keystore, you can execute basename $0 --remove|r, for more information, you can
# execute basename $0 --help|h to view the help information.

if [[ $EUID -ne 0 ]]; then
    echo "This script must be run as root" 1>&2
    exit 1
fi

# Function to print the usage information for the script.
# Usage: print_usage
# Options:
#   -u, --update    Update the SM2 root certificate to the system keystore, default.
#   -r, --remove    Remove the SM2 root certificate from the keystore.
#       --no-update Skip the update operation, valid with the --remove option.
#   -h, --help      Print this help message.
function print_usage() {
    printf 'usage:\n'
    printf '\t%s [OPTION]\n' "$(basename "$0")"
    printf '\n'
    printf 'OPTIONS:\n'
    printf '  -u, --update\t\tUpdate the SM2 root certificate to the system keystore, default.\n'
    printf '  -r, --remove\t\tRemove the SM2 root certificate from the keystore.\n'
    printf '      --no-update\tSkip the update operation, valid with the --remove option.\n'
    printf '  -h, --help\t\tPrint this help message.\n'
}

# By default, when no parameters are added, the update operation is performed
declare -i remove_keystone=0
declare -i skip_update=0

# Function: process_parameters
# Description: This function processes the command line parameters passed to the script.
#              It uses the getopt command to parse the options and arguments.
#              The supported options are:
#                   -u, --update: Sets the remove_keystone variable to 0.
#                   -r, --remove: Sets the remove_keystone variable to 1.
#                   -h, --help: Prints the usage information and exits.
# Parameters:
#   - $@: The command line parameters passed to the script.
# Returns:
#   - None
function process_parameters() {
    local SCRIPT_OPTIONS
    SCRIPT_OPTIONS="$(
        getopt \
            --longoptions update,remove,no-update,help \
            --name "$(basename "$0")" \
            --options "urh" \
            -- "$@"
    )"
    local -i getopt_ret=$?
    if [ "${getopt_ret}" -ne 0 ]; then
        print_usage
        exit 1
    fi

    eval set -- "${SCRIPT_OPTIONS}"

    while [[ $# -gt 0 ]]; do
        case "$1" in
        -u | --update)
            remove_keystone=0
            shift 1
            ;;
        -r | --remove)
            remove_keystone=1
            shift 1
            ;;
        --no-update)
            skip_update=1
            shift 1
            ;;
        -h | --help)
            print_usage
            exit 0
            ;;
        --)
            break
            ;;
        *)
            break
            ;;
        esac
    done
}

declare -r certificates_list="/usr/share/ca-certificates-kylinsec/certificates.list"
declare -r certificates_store="/usr/share/pki/ca-trust-source/anchors/kylinsec"
declare -r keystore_path="/etc/pki/ca-trust/source/anchors"

# Function: check_file_permission
# Description: Checks if a file exists and is readable.
# Parameters:
#   - filePath: The path to the file to be checked.
# Returns:
#   - True (0) if the file exists and is readable, False (1) otherwise.
function check_file_permission() {
    local -r filePath="$1"
    [ -f "$filePath" ] && [ -r "$filePath" ]
}

# Function to validate parameters before updating CA trust
function validate_parameters() {
    if ! check_file_permission "${certificates_list}"; then
        echo "The file ${certificates_list} does not exist or is not readable, please check it."
        exit 1
    fi

    if [ ! -d "${keystore_path}" ]; then
        echo "The keystore path does not exist, please check it."
        exit 1
    fi

    if [ ! -d "${certificates_store}" ]; then
        echo "The certificates store path does not exist, please check it."
        exit 1
    fi
}

# Function: process_update
# Description: This function processes the update of CA certificates by creating
#              symbolic links from the source certificate paths to the destination
#              paths specified in the cert_map array. It then updates the CA trust
#              store using the update-ca-trust command.
# Parameters:
#   - cert_map: An associative array that maps source certificate paths to
#               destination paths.
# Returns:
#   - The exit code of the update-ca-trust command.
# shellcheck disable=SC2317
function process_update() {
    local -n cert_map=$1
    local dest_path
    for cert_path in "${!cert_map[@]}"; do
        dest_path="${cert_map[${cert_path}]}"
        ln -s -f -T "${cert_path}" "${dest_path}"
    done

    update-ca-trust
    return $?
}

# Function: process_remove
# Description: This function removes certificates specified in the cert_map array
#              and updates the CA trust.
# Parameters:
#   - cert_map: An associative array containing the paths of certificates to be
#               removed and their corresponding destination paths.
# Return Value:
#   - Returns the exit status of the update-ca-trust command.
# shellcheck disable=SC2317
function process_remove() {
    local -n cert_map=$1
    local dest_path
    for cert_path in "${!cert_map[@]}"; do
        dest_path="${cert_map[${cert_path}]}"
        rm -f "${dest_path}"
    done

    if [[ ${skip_update} -eq 1 ]]; then
        return 0
    fi

    update-ca-trust
    return $?
}

# Function: process_update_keystone
# Description: This function processes the update or removal of keystone certificates.
# Parameters:
#   - None
# Returns:
#   - 0 if the operation is successful
#   - 1 if the operation fails
function process_update_keystone() {
    local -a certificates_array
    mapfile -t certificates_array <<<"$(cat 2>/dev/null <"${certificates_list}")"

    declare -gA certificates_map
    local cert_path dest_path
    for cert_name in "${certificates_array[@]}"; do
        cert_path="${certificates_store}/${cert_name}"
        if [ ! -f "${cert_path}" ]; then
            echo "The certificate ${cert_path} does not exist, please check it."
            return 1
        fi

        dest_path="${keystore_path}/${cert_name}"
        if [ -e "${dest_path}" ]; then
            if [ -d "${dest_path}" ]; then
                echo "The path ${dest_path} is a directory, please check it."
                return 1
            fi
            if ! diff -q "${cert_path}" "$(readlink -f "${dest_path}")" >/dev/null; then
                echo >&2 "A file with the same name as ${cert_name} is" \
                    "found and a forced overwrite/remove will be performed."
            fi
        fi
        # shellcheck disable=SC2034
        certificates_map["${cert_path}"]="${dest_path}"
    done

    local operation="update"
    if [[ ${remove_keystone} -eq 1 ]]; then
        operation="remove"
    fi

    if [[ -n $operation ]]; then
        if "process_${operation}" certificates_map; then
            printf '%s keystone success.\n' "$operation"
            return 0
        else
            printf '%s keystone failed!\n' "$operation"
            return 1
        fi
    fi
}

function main {
    process_parameters "$@"
    validate_parameters
    process_update_keystone
}

main "$@"
exit $?
