#!/bin/bash

set -Ee

HARD_RESET=false
CHECK_KEYS=false
INSERT_KEYS=false
UNIT_FILE=false
SKIP_BUILD=false
SET_ENVIRONMENT=false
VALIDATE=false
EXECUTABLE_PATH="/usr/bin/"
SPECIFICATION_PATH="/etc/ghost/"
BASE_PATH="/var/lib/ghost"
SPECIFICATION_NAME="casper"
TARGET="release"

CURRENT_PATH=$(pwd)
CURRENT_SCRIPT=$(realpath "$0")
SCRIPT_FOLDER=$(dirname "$CURRENT_SCRIPT")
PROJECT_FOLDER=("$SCRIPT_FOLDER/..")

final() {
    cd $CURRENT_PATH
    if [[ $1 -eq 1 ]]; then
        echo "[-] error occured during execution"
    else
        echo "[+] execution finished"
    fi
    exit $1
}

prompt() {
    while true; do
        printf "$1 [y/N]: "
        read yn
        case $yn in
            [Yy]* ) return 0;;
            * ) return 1;;
        esac
    done
}

sanity_check() {
    secret_seed=$(ghost key inspect --scheme="$1" "$2" | grep "Secret seed" | awk '{ print $3 }')
    account_id=$(ghost key inspect --scheme="$1" "$2" | grep "Account ID" | awk '{ print $3 }')

    echo "[+] inspected account id for $3: $account_id"
    num_keys=$(grep $account_id "$PROJECT_FOLDER/service/ghosties" | wc -l)
    num_types=$(grep $account_id "$PROJECT_FOLDER/service/ghosties" | grep $3 | wc -l)
    if [ $num_keys = 1 ] && [ $num_types = 1 ]; then
        echo "[+] local $3 key found in 'ghosties' with correct key type"
    else
        echo "[-] inspected account id not found on 'ghosties' file or wrong key type" 
        exit 1
    fi
}

genesis_check() {
    secret_seed=$(ghost key inspect --scheme="$1" "$2" | grep "Secret seed" | awk '{ print $3 }')
    account_id=$(ghost key inspect --scheme="$1" "$2" | grep "Account ID" | awk '{ print $3 }')

    num_keys=$(grep ${account_id:2} "$PROJECT_FOLDER/service/chain-specs/casper.json" | wc -l)
    if [ ! $num_keys = 0 ]; then
        echo "[+] found in genesis block"
    else
        echo "[-] not yet in genesis block" 
    fi
    echo
}

extract_seed() {
    name_with_spaces=$(echo $1 | tr '-' ' ')
    read -p "[?] path to the file with $name_with_spaces: (default: /etc/ghost/$1) " seed_path
    seed_path="${seed_path:-/etc/ghost/$1}"
    if [ ! -f $seed_path ]; then
        echo "[-] path to $name_with_spaces is not valid"
    fi
    seed=$(cat $seed_path)
    echo $seed
}

upgrade_compiler_if_needed() {
    UPDATE_STATUS=$(rustup check 2>&1)
    "$SCRIPT_FOLDER"/patch.sh || PATCH_STATUS=$?

    if echo "$UPDATE_STATUS" | grep -q "Update available" || [ $PATCH_STATUS -eq 2 ]; then
        echo "[+] clean all cache on the current system"
        cargo install cargo-cache
        cargo clean
        cargo cache --remove-dir all

        echo "[+] upgrading rustc compiler version to latest"
        rustup update
        toolchain_name=$(rustup show | grep default | head -n 1 | cut -d' ' -f1)

        if ! rustup target list --installed | grep -q "wasm32-unknown-unknown"; then
            echo "[+] installing wasm32-unknown-unknown target"
            rustup target add wasm32-unknown-unknown
        fi

        if ! rustup component list --installed | grep -q "rust-src"; then
            echo "[+] installing rust-src component"
            rustup component add rust-src
        fi

        echo "[+] fetching all dependencies"
        cargo fetch

        echo "[+] trying to apply all patches"
        "$SCRIPT_FOLDER"/patch.sh apply
    fi
}

help() {
    echo -e "Ghost Node Build automation tool. Helper for Ghost Node environment preparation.\n"
    echo -e "With no OPTION nothing will happen, possible OPTION:\n"
    echo -e "-i, --set-environment\n\tSet up rust environment."
    echo -e "-u, --unit-file\n\tCreation of systemd unit file."
    echo -e "-m, --make-global\n\tStore compiled ghost executable and chain specification globally."
    echo -e "-a, --set-arguments\n\tPrepare CLI arguments for running ghost node."
    echo -e "-k, --check-keys\n\tCheck if your keys are already included in 'ghosties' file."
    echo -e "-y, --insert-keys\n\tInsert session keys to the keystore via JSON RPC."
    echo -e "-r, --release\n\tCompile node with '--release' flag."
    echo -e "-v, --validate\n\tManipulate '--validate' flag in CLI_ARGS."
    echo -e "-p, --profile\n\tCompile node with '--profile [PROFILE]' flag."
    echo -e "-f, --features\n\tCompilation features '--features=\"FEATURE1,FEATURE2\"'"
    echo -e "-e, --executable-path\n\tPath to the executable ('/usr/lib/' is default)."
    echo -e "-s, --base-path\n\tPath to the folder with chain database ('/var/lib/ghost' is default)."
    echo -e "-c, --specification-path\n\tPath to specification ('/etc/ghost' is default)."
    echo -e "-n, --specification-name\n\tSpecification name to be used ('casper' is default)."
    echo -e "--hard-reset-i-know-what-im-doing\n\tWill completely remove ledger along with associated session keys."
    echo -e "-h, --help\n\tPrints help information."
}

trap 'final "$?"' EXIT

clear
echo "  ____ _               _     _   _           _"
echo " / ___| |__   ___  ___| |_  | \ | | ___   __| | ___"
echo "| |  _| '_ \ / _ \/ __| __| |  \| |/ _ \ / _' |/ _ \\"
echo "| |_| | | | | (_) \__ \ |_  | |\  | (_) | (_| |  __/"
echo " \____|_| |_|\___/|___/\__| |_| \_|\___/ \__,_|\___|"
echo -e "\nCreated by st1nky (stinky@ghostchain.io)"
echo -e "Repository: https://git.ghostchain.io/ghostchain/ghost-node"
echo -e "Usage: starter.sh [OPTION]\n"

while [ $# -gt 0 ]; do
    case "$1" in
        --set-environment|-i)
            SET_ENVIRONMENT=true
            ;;
        --unit-file|-u)
            UNIT_FILE=true
            ;;
        --make-global|-m)
            MAKE_GLOBAL=true
            ;;
        --set-arguments|-a)
            ARGUMENTS=true
            ;;
        --check-keys|-k)
            CHECK_KEYS=true
            ;;
        --insert-keys|-k)
            INSERT_KEYS=true
            ;;
        --release|-r)
            RELEASE="--release"
            TARGET="release"
            ;;
        --validate|-v)
            VALIDATE=true
            ;;
        --profile*|-p*)
            if [[ "$1" != *=* ]]; then shift; fi
            RELEASE="--profile=${1#*=}"
            TARGET="${1#*=}"
            ;;
        --feature*|-f*)
            if [[ "$1" != *=* ]]; then shift; fi
            FEATURES="--features=${1#*=}"
            ;;
        --executable-path*|-e*)
            if [[ "$1" != *=* ]]; then shift; fi
            EXECUTABLE_PATH=$(echo ${1#*=}/ | tr -s /)
            ;;
        --specification-path*|-c*)
            if [[ "$1" != *=* ]]; then shift; fi
            SPECIFICATION_PATH=$(echo ${1#*=}/ | tr -s /)
            ;;
        --base-path*|-?)
            if [[ "$1" != *=* ]]; then shift; fi
            BASE_PATH=$(echo ${1#*=}/ | tr -s /)
            ;;
        --specification-name*|-n*) 
            if [[ "$1" != *=* ]]; then shift; fi
            SPECIFICATION_NAME="${1#*=}"
            ;;
        --hard-reset-i-know-what-im-doing)
            HARD_RESET=true
            ;;
        --help|-h)
            help
            exit 0
            ;;
        *)
            help
            exit 1
            ;;
    esac
    shift
done

if [[ $HARD_RESET = true ]]; then
    echo -e "\n"
    echo "WARNING!!! THIS ACTION WILL COMPLETELY PURGE THE LEDGER AND REBUILD THE NODE USING THE LATEST"
    echo "REPOSITORY VERSION. NOTE THAT THE VALIDATION PROCESS WILL BE LOST BECAUSE SESSION OF KEYS."
    echo "THERE ARE TWO SCENARIOS IN WHICH YOU MIGHT NEED TO PROCEED:"
    echo -e "\t- A new version of the network has been released, and a restart is neccessary"
    echo -e "\t- There is a critical issue, and you require a hard reset of the node in a single command"
    echo -e "\n"
    echo "IMPORTANT!!! Your validator's session keys will be also removed. Consequently, your validator node"
    echo "will be slashed indefinitely and will become inactive. After hard reset, you'll need to manage the"
    echo "keys manually to reactivate the validator."
    echo -e "\n"
    if prompt "[?] do you understand all risks?"; then
        echo "[+] you were warned, I hope you know what you're doing"
    else
        echo "[-] aborting hard reset"
        exit 1
    fi

    upgrade_compiler_if_needed

    echo "[+] trying to stop current ghost-node"
    sudo systemctl stop ghost-node
    echo "[+] trying to remove locally stored ledger"
    sudo rm -rf "$BASE_PATH/chains/casper_staging_testnet"

    cd $PROJECT_FOLDER
    git switch main
    git pull origin main
    echo "[+] starting build in 3 seconds..."
    sleep 3
    cargo build --release

    printf "[?] are you ready to continue?"
    read anything

    cd $PROJECT_FOLDER
    echo "[+] trying to copy executable to '$EXECUTABLE_PATH'"
    sudo cp target/$TARGET/ghost $EXECUTABLE_PATH
    cp service/chain-specs/$SPECIFICATION_NAME.json $SPECIFICATION_PATH

    echo "[+] ghost executable copied in '$EXECUTABLE_PATH' from '$TARGET'"
    echo "[+] specification '$SPECIFICATION_NAME.json' copied to '$SPECIFICATION_PATH'"

    echo "[+] starting ghost-node"
    sudo systemctl start ghost-node

    exit 0
fi

if [[ $SET_ENVIRONMENT = true ]]; then
    echo -e "\n"
    echo "WARNING!!! THIS IS HIGHLY EXPERIMENTAL FLAG, USE IT ONLY ON YOUR"
    echo "OWN RISK! NOT EVERY SYSTEM CHECKED AND PROVED TO BE WORKING AS"
    echo "EXPECTED! FOR THOSE WHO FOUND BUG OR SOME INCONSISTENCY FEEL"
    echo "FREE TO FILL ISSUE ON https://git.ghostchain.io/ghostchain/ghost-node/issues"
    echo -e "\n"
    if prompt "[?] are you brave enough?"; then 
        echo "[+] you were warned, I hope you know what you're doing" 
    else
        echo "[-] aborting environment setup"
        exit 1
    fi

    if command -v rustc >/dev/null 2>&1; then
        echo "[+] rust already installed"
    else
        os_name=$(uname -s)
        cd $HOME
        if [ "$os_name" = "Darwin" ]; then
            echo "[+] detected MacOS. installing dependencies via homebrew"

            if command -v brew >/dev/null 2>&1; then
                echo "[+] already installed: $(brew --version)"
            else
                if prompt "[?] do you want to install homebrew?"; then 
                    /bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/master/install.sh)"
                else
                    echo "[-] cannot continue without homebrew"
                    exit 1
                fi
            fi

            brew update

            if prompt "[?] install git, cmake, openssl and protobuf?"; then
                brew install git cmake openssl protobuf
            else
                echo "[!] assuming git, cmake, openssl and protobuf are present"
            fi
        elif [ "$os_name" == "Linux" ]; then
            distro=$(cat /etc/*-release | tr '[:upper:]' '[:lower:]' | grep -Poi '(debian|ubuntu|arch|fedora|opensuse)' | uniq | head -n 1)
            echo $distro

            if [ "$distro" = "ubuntu" ]; then
                echo "[+] ubuntu detected, using apt to install dependencies"
                sudo apt update
                sudo apt install --assume-yes git make clang curl libssl-dev protobuf-compiler
            elif [ "$distro" = "debian" ]; then
                echo "[+] debian detected, using apt to install dependencies"
                sudo apt update
                sudo apt install --assume-yes git make clang curl libssl-dev llvm libudev-dev protobuf-compiler
            elif [ "$distro" = "arch" ]; then
                echo "[+] arch linux detected, using pacman to install dependencies"
                sudo pacman -Syu --needed --no-confirm curl git clang make protobuf
            elif [ "$distro" = "fedora" ]; then
                echo "[+] fedora detected, using dnf to install dependencies"
                sudo dnf update --assumeyes
                sudo dnf install --assumeyes clang curl git openssl-devel make protobuf-compiler perl
            elif [ "$distro" = "opensuse" ]; then
                echo "[+] openSUSE detected, using zypper to install dependencies"
                sudo zypper install --no-confirm clang gcc gcc-c++ curl git openssl-devel llvm-devel libudev-devel make awk protobuf-devel
            else
                if prompt "[?] unknown linux distribution, unable to install dependencies. continue anyway?"; then
                    echo "[+] proceeding with unknown linux distribution"
                else
                    echo "[-] aborting with uknown distribution"
                    exit 1
                fi
            fi
        else
            echo "[-] unknown operating system"
            exit 1
        fi
    fi

    if prompt "[?] setup the rust environment (e.g. WASM support)?"; then
        rustup default stable
        upgrade_compiler_if_needed
    fi
fi

if [[ ! -z $RELEASE ]]; then
    upgrade_compiler_if_needed
    if prompt "[?] 'cargo build $RELEASE $FEATURES' is what you want?"; then 
        cd $PROJECT_FOLDER
        echo "[+] starting build in 3 seconds..."
        sleep 3
        cargo build $RELEASE $FEATURES
    fi
fi

if [[ $VALIDATE = true ]]; then
    cli_args=$(cat /etc/default/ghost)
    if prompt "[?] turn on validator mode?"; then
        if [[ $cli_args == *"--validator"* ]]; then
            echo "[+] '--validator' flag already exists in CLI_ARGS, check /etc/default/ghost"
        else
            cli_args="${cli_args%?} --validator\""
            echo "[+] '--validator' flag inserted to CLI_ARGS, check /etc/default/ghost"
        fi
    else
        cli_args=${cli_args// --validator/}
        echo "[+] '--validator' flag removed"
    fi

    echo "[+] trying to store CLI updated arguments to '/etc/default/ghost'"
    echo "$cli_args" | sudo tee /etc/default/ghost > /dev/null
    echo "[+] updated CLI arguments stored in '/etc/default/ghost'"

    if prompt "[?] do you want to restart 'ghost-node.service' now?"; then
        sudo systemctl status ghost-node.service
        echo "[+] 'ghost-node.service' succefully restarted, new CLI_ARGS applied"
    else
        echo "[+] don't forget to restart ghost node service manually"
    fi
fi

if [[ $MAKE_GLOBAL = true ]]; then
    cd $PROJECT_FOLDER
    echo "[+] trying to copy executable to '$EXECUTABLE_PATH'"
    sudo cp target/$TARGET/ghost $EXECUTABLE_PATH
    cp service/chain-specs/$SPECIFICATION_NAME.json $SPECIFICATION_PATH

    echo "[+] ghost executable copied in '$EXECUTABLE_PATH' from '$TARGET'"
    echo "[+] specification '$SPECIFICATION_NAME.json' copied to '$SPECIFICATION_PATH'"
fi

if [[ $ARGUMENTS = true ]]; then
    echo "[+] setting-up default CLI arguments"
    CLI_ARGS=()

    read -p "[?] specify p2p protocol TCP port (default: 30333): " port
    CLI_ARGS+=("--port=${port:-30333}")

    read -p "[?] specify JSON-RPC server TCP port: (default: 9945): " rpc_port
    CLI_ARGS+=("--rpc-port=${rpc_port:-9945}")

    read -p "[?] specify the chain specification (default: /etc/ghost/casper.json): " chain
    CLI_ARGS+=("--chain=${chain:-/etc/ghost/casper.json}")

    read -p "[?] specify node's secret key file for p2p networking (default: /etc/ghost/node-key): " node_key
    CLI_ARGS+=("--node-key-file=${node_key:-/etc/ghost/node-key}")

    read -p "[?] specify name for the node (default: RANDOM_NAME): " node_name
    if [[ ! -z $node_name ]]; then 
        CLI_ARGS+=("--name='$node_name'")
    fi

    CLI_ARGS+=("--validator")
    if prompt "[?] disable validator mode?"; then 
        unset CLI_ARGS[-1]
    fi

    if prompt "[?] enable prometheus?"; then 
        read -p "[?] specify prometheus exporter TCP port: (default: 9615)" prometheus_port
        CLI_ARGS+=("--prometheus-port=${prometheus_port:-9615}")
    else
        CLI_ARGS+=("--no-prometheus")
    fi

    read -p "[?] bootnode if any: " bootnodes
    if [ ! -z $bootnodes ]; then
        CLI_ARGS+=("--bootnodes=$bootnodes")
    fi

    read -p "[?] address that other nodes will use to connect: (default: current ip)" public_addr
    if [ ! -z $public_addr ]; then
        CLI_ARGS+=("--public-addr=$public_addr")
    fi

    # default for now
    CLI_ARGS+=("--telemetry-url='wss://telemetry.ghostchain.io/submit/ 9'")
    CLI_ARGS+=("--base-path=$BASE_PATH")
    CLI_ARGS+=("--state-pruning=archive")
    CLI_ARGS+=("--blocks-pruning=archive")
    CLI_ARGS+=("--rpc-methods=auto")
    CLI_ARGS+=("--no-private-ip")
    CLI_ARGS+=("--no-mdns")

    echo "[+] trying to save new node arguments"
    echo "GHOST_CLI_ARGS=\"$(IFS=' '; echo "${CLI_ARGS[*]}")\"" > /tmp/ghost
    sudo cp /tmp/ghost /etc/default/ghost
    rm /tmp/ghost
    echo "[+] new CLI arguments stored in '/etc/default/ghost'"
    cat /etc/default/ghost
fi

if [ $CHECK_KEYS = true ]; then
    seed=$(extract_seed "wallet-key")
    sanity_check "sr25519" $seed "wallet"
    genesis_check "sr25519" $seed "wallet"

    seed=$(extract_seed "stash-key")
    sanity_check "sr25519" $seed "stash"
    genesis_check "sr25519" $seed "stash"

    seed=$(extract_seed "session-key")
    if [ $INSERT_KEYS = true ]; then
        read -p "[?] JSON RPC endpoint to the node: (default: localhost:9945) " rpc_endpoint
        rpc_endpoint="${rpc_endpoint:-localhost:9945}"
    fi

    for type in $(echo audi babe gran slow); do
        echo "[+] parsing session key for [$type]"
        scheme="sr25519"
        if [ $type = "gran" ]; then
            scheme="ed25519"
        fi
        
        sanity_check $scheme "$seed//$type" $type
        genesis_check $scheme "$seed//$type" $type
        if [ $INSERT_KEYS = true ]; then
            echo "[+] trying to make an 'author_insertKey' RPC call to $rpc_endpoint..."
            curl --location $rpc_endpoint \
                --header "Content-Type: application/json" \
                --data '{ "id":1, "jsonrpc":"2.0", "method":"author_insertKey", "params": ["'"$type"'", "'"$secret_seed"'", "'"$account_id"'"] }'
        fi
        echo
    done
fi

if [ $UNIT_FILE = true ]; then
    cd $SCRIPT_FOLDER
    read -p "[?] name for the unit file (default: ghost-node.service) " unit_name
    if [ -z $unit_name ]; then
        unit_name="ghost-node"
    fi
    unit_name=$(echo "$unit_name" | sed -e "s/.service//g")
    unit_name="$unit_name.service"

    user_name="ghost"
    if id $user_name > /dev/null 2>&1; then
        echo "[+] user ghost found, continue for the user ghost"
    else
        echo "[!] user ghost not found"
        if prompt "[?] do you want to create ghost user? (NOT RECOMMENDED: current $(whoami))"; then 
            sudo useradd --system --create-home $user_name
        else
            user_name=$(whoami)
        fi
    fi

    if [ ! -d $BASE_PATH ]; then
        echo "[+] create folder for the node at '$BASE_PATH'"
        sudo mkdir $BASE_PATH
    fi

    if [ -z "$(stat -c "%U %G" $BASE_PATH | grep $user_name)" ]; then
        echo "[+] make $user_name owner of $BASE_PATH"
        sudo chown -R "$user_name:" $BASE_PATH
    fi
    
    cp packaging/template.service /tmp/$unit_name
    sed -i -e "s/User=ghost/User=$user_name/g" /tmp/$unit_name
    sed -i -e "s#/ReadWritePaths=/var/lib/ghost#/ReadWritePaths=$BASE_PATH#g" /tmp/$unit_name

    echo "[+] prepare unit file for the $unit_name"
    sudo cp packaging/template.service /etc/systemd/system/$unit_name
    echo "[+] reloading systemd because of updated unit file"
    sudo systemctl daemon-reload

    if prompt "[?] do you want to start the $unit_name?"; then 
        sudo systemctl restart $unit_name
    fi

    if prompt "[?] do you want to enable the $unit_name?"; then 
        sudo systemctl enable $unit_name
    fi
fi
