#!/bin/bash

set -Ee -o pipefail

silent=0
no_restart=0
force=0
default_install_path="/opt/openfog"
install_path="${default_install_path}"
install_path_provided=0
config_file="${default_install_path}/etc/pear/pear_monitor/config.json"
install_only=1
tmp_dir=""
log_file=""
package_file=""

username=""
bandwidth=""
use_storage_size=""
limited_storage=""
limited_area=""
storage_values=()
nic_values=()

config_json="{}"
user_marked_json="{}"
nic_json="[]"
storage_json="[]"

info() {
    if [ "${silent}" -eq 0 ]; then
        printf '\033[32m%s\033[0m\n' "${1}"
    fi
}

error() {
    printf '\033[31m%s\033[0m\n' "${1}" >&2
}

die() {
    error "${1}"
    if [ -n "${log_file}" ] && [ -f "${log_file}" ]; then
        error "Command log: ${log_file}"
    fi
    exit 1
}

cleanup() {
    local status=$?
    if [ "${status}" -eq 0 ] && [ -n "${tmp_dir}" ] && [ -d "${tmp_dir}" ]; then
        rm -rf "${tmp_dir}"
    fi
}
trap cleanup EXIT

run_cmd() {
    local message="${1}"
    shift

    if [ -n "${message}" ]; then
        info "${message}"
    fi

    if [ -n "${log_file}" ]; then
        printf '\n[%s] %s\n' "$(date '+%Y-%m-%d %H:%M:%S')" "$*" >> "${log_file}"
    fi

    if [ "${silent}" -eq 1 ]; then
        "$@" >> "${log_file}" 2>&1
        return $?
    fi

    if [ -n "${log_file}" ] && command -v tee >/dev/null 2>&1; then
        "$@" 2>&1 | tee -a "${log_file}"
        local status=${PIPESTATUS[0]}
        return "${status}"
    fi

    "$@"
}

show_usage() {
    printf 'Usage: %s [options]\n' "$0"
    printf 'Options:\n'
    printf '  -f, --force                         Force download and reinstall the latest package\n'
    printf '  -u, --username <username>           Set the username\n'
    printf '  -b, --bandwidth <bandwidth>         Set the bandwidth, just valid on the first time\n'
    printf '  -s, --storage <storage>             Add a storage, can be used multiple times\n'
    printf '  -n, --nic <nic>                     Add a nic, can be used multiple times\n'
    printf '  -S, --silent                        Suppress normal output and only print errors\n'
    printf '      --install_path <path>           Set the install path, defaults to /opt/openfog\n'
    printf '      --use_storage_size <size>       Set the storage size\n'
    printf '      --limited_storage <percentage>  Set limited storage percentage, min 20\n'
    printf '      --limited_area <area_code>      Set limited area code, 0: nationwide, 1: province, 2: region\n'
    printf '  -h, --help                          Display this help message\n'
}

set_config_file() {
    if [ -n "${install_path}" ]; then
        config_file="${install_path}/etc/pear/pear_monitor/config.json"
    else
        config_file="/etc/pear/pear_monitor/config.json"
    fi
}

normalize_install_path() {
    if [ -z "${install_path}" ] || [ "${install_path}" = "/" ]; then
        install_path=""
        set_config_file
        return 0
    fi

    case "${install_path}" in
        /*)
            ;;
        *)
            die "--install_path must be an absolute path"
            ;;
    esac

    while [ "${install_path}" != "/" ] && [ "${install_path%/}" != "${install_path}" ]; do
        install_path="${install_path%/}"
    done

    if [ "${install_path}" = "/" ]; then
        install_path=""
    fi

    case "${install_path}" in
        *[[:space:]]*)
            die "--install_path cannot contain whitespace"
            ;;
    esac

    set_config_file
}

load_existing_install_path() {
    local line=""

    if [ ! -f "/etc/pear/pear_installation_path" ]; then
        return 0
    fi

    IFS= read -r line < "/etc/pear/pear_installation_path" || true
    case "${line}" in
        INSTALLATION_PATH=*)
            install_path="${line#INSTALLATION_PATH=}"
            normalize_install_path
            ;;
    esac
}

parse_args() {
    local opts=""

    if ! command -v getopt >/dev/null 2>&1; then
        error "getopt is required"
        exit 1
    fi

    if ! opts=$(getopt -o fhSu:b:s:n: -l force,help,silent,username:,bandwidth:,storage:,nic:,install_path:,use_storage_size:,limited_storage:,limited_area:,no_restart -- "$@"); then
        exit 1
    fi

    eval set -- "${opts}"
    while true; do
        case "$1" in
            -f|--force)
                force=1
                shift
                ;;
            -u|--username)
                if [ -n "${2}" ]; then
                    username="${2}"
                    install_only=0
                fi
                shift 2
                ;;
            -b|--bandwidth)
                if [ -n "${2}" ]; then
                    bandwidth="${2}"
                    install_only=0
                fi
                shift 2
                ;;
            -s|--storage)
                if [ -n "${2}" ]; then
                    storage_values+=("${2}")
                    install_only=0
                fi
                shift 2
                ;;
            -n|--nic)
                if [ -n "${2}" ]; then
                    nic_values+=("${2}")
                    install_only=0
                fi
                shift 2
                ;;
            -S|--silent)
                silent=1
                shift
                ;;
            --install_path)
                install_path="${2}"
                install_path_provided=1
                normalize_install_path
                shift 2
                ;;
            --use_storage_size)
                if [ -n "${2}" ]; then
                    use_storage_size="${2}"
                    install_only=0
                fi
                shift 2
                ;;
            --limited_storage)
                if [ -n "${2}" ]; then
                    limited_storage="${2}"
                    install_only=0
                fi
                shift 2
                ;;
            --limited_area)
                if [ -n "${2}" ]; then
                    limited_area="${2}"
                    install_only=0
                fi
                shift 2
                ;;
            --no_restart)
                no_restart=1
                shift
                ;;
            -h|--help)
                show_usage
                exit 0
                ;;
            --)
                shift
                break
                ;;
            *)
                break
                ;;
        esac
    done
}

create_tmp_dir() {
    tmp_dir=$(mktemp -d "${TMPDIR:-/tmp}/pear_deploy.XXXXXX") || die "Failed to create temporary directory"
    log_file="${tmp_dir}/pear_deploy.log"
    : > "${log_file}" || die "Failed to create command log"
}

install_jq() {
    local manager="${1}"

    info "Using ${manager} to install jq..."
    case "${manager}" in
        apt-get)
            run_cmd "" apt-get update && run_cmd "" apt-get install -y jq
            ;;
        yum)
            run_cmd "" yum install -y jq
            ;;
        opkg)
            run_cmd "" opkg update && run_cmd "" opkg install jq
            ;;
        apk)
            run_cmd "" apk add jq
            ;;
        pacman)
            run_cmd "" pacman -Sy --noconfirm jq
            ;;
        zypper)
            run_cmd "" zypper --non-interactive install jq
            ;;
        *)
            return 1
            ;;
    esac
}

ensure_jq() {
    local manager=""
    local attempted=0

    if command -v jq >/dev/null 2>&1; then
        return 0
    fi

    for manager in apt-get yum opkg apk pacman zypper; do
        if command -v "${manager}" >/dev/null 2>&1; then
            attempted=1
            if install_jq "${manager}" && command -v jq >/dev/null 2>&1; then
                return 0
            fi
        fi
    done

    if [ "${attempted}" -eq 0 ]; then
        die "jq is not installed and no supported package manager was found"
    fi
    die "jq is not installed and automatic installation failed"
}

detect_architecture() {
    local uname_m=""

    uname_m="$(uname -m)"
    case "${uname_m}" in
        x86_64)
            printf 'X64\n'
            ;;
        aarch64)
            printf 'ARM64\n'
            ;;
        armv7*)
            printf 'ARM\n'
            ;;
        *)
            die "Unsupported architecture: ${uname_m}"
            ;;
    esac
}

download_file() {
    local url="${1}"
    local output="${2}"

    if command -v curl >/dev/null 2>&1; then
        run_cmd "" curl -fsSLk --retry 3 --connect-timeout 15 --max-time 600 -o "${output}" "${url}"
        return $?
    fi

    if command -v wget >/dev/null 2>&1; then
        run_cmd "" wget --no-verbose --no-check-certificate --timeout=30 --tries=3 -O "${output}" "${url}"
        return $?
    fi

    die "curl or wget is required to download openfog"
}

archive_is_valid() {
    local archive="${1}"
    local output=""

    if output=$(tar -tzf "${archive}" 2>&1 >/dev/null); then
        return 0
    fi

    if [ -n "${log_file}" ]; then
        printf '%s\n' "${output}" >> "${log_file}"
    fi
    if [ "${silent}" -eq 0 ] && [ -n "${output}" ]; then
        printf '%s\n' "${output}" >&2
    fi
    return 1
}

download_openfog() {
    local architecture="${1}"
    local download_url=""
    local download_backup_url=""

    download_url="https://ppkg-1300587032.cos.ap-shanghai.myqcloud.com/data/openfog_install_pkg/fogvdn_PEAR_${architecture}_LINUX_latest.tar.gz"
    download_backup_url="https://download.openfogos.com/release/fogvdn_PEAR_${architecture}_LINUX_latest.tar.gz"
    package_file="${tmp_dir}/openfog.tar.gz"

    info "Downloading openfog..."
    if ! download_file "${download_url}" "${package_file}"; then
        info "Primary download failed, trying backup URL..."
        rm -f "${package_file}"
        if ! download_file "${download_backup_url}" "${package_file}"; then
            die "Download failed"
        fi
    fi

    if [ ! -s "${package_file}" ]; then
        die "Downloaded package is empty"
    fi

    if ! archive_is_valid "${package_file}"; then
        die "Downloaded package is not a valid tar.gz archive"
    fi

    info "Downloaded openfog"
}

is_pear_installed() {
    [ -f "${install_path}/usr/sbin/pear_restart" ] &&
        [ -f "${install_path}/usr/sbin/pear_updater" ] &&
        [ -f "${install_path}/usr/sbin/pear_monitor" ]
}

run_post_command() {
    local post_command="${install_path}/etc/pear/pear_update/post_command.sh"

    if [ ! -f "${post_command}" ]; then
        die "Deploy failed: ${post_command} not found"
    fi

    if [ -x "${post_command}" ]; then
        run_cmd "Running post install command..." "${post_command}" || die "Deploy failed"
    else
        run_cmd "Running post install command..." bash "${post_command}" || die "Deploy failed"
    fi
}

install_openfog() {
    local architecture=""
    local target_dir="/"

    ensure_jq

    if [ "${install_path_provided}" -eq 0 ]; then
        load_existing_install_path
    else
        normalize_install_path
    fi

    if is_pear_installed; then
        if [ "${force}" -eq 0 ]; then
            info "pear is already installed"
            return 0
        fi
        info "pear is already installed, but --force was specified; downloading the latest package and reinstalling"
    fi

    architecture="$(detect_architecture)"
    download_openfog "${architecture}"

    if [ -n "${install_path}" ]; then
        run_cmd "Preparing install path..." mkdir -p "${install_path}" || die "Failed to create install path: ${install_path}"
        target_dir="${install_path}"
    fi

    run_cmd "Extracting openfog..." tar -xzf "${package_file}" -C "${target_dir}" || die "Deploy failed while extracting package"
    run_cmd "" mkdir -p /etc/pear || die "Failed to create /etc/pear"
    printf 'INSTALLATION_PATH=%s' "${install_path}" > /etc/pear/pear_installation_path || die "Failed to write /etc/pear/pear_installation_path"
    run_post_command
}

build_config_json() {
    local item=""
    local mtime=""

    config_json="{}"
    user_marked_json="{}"
    nic_json="[]"
    storage_json="[]"

    if [ -n "${username}" ]; then
        user_marked_json=$(printf '%s\n' "${user_marked_json}" | jq --arg username "${username}" '. + {"username": $username}') || die "Failed to set username config"
    fi

    if [ -n "${bandwidth}" ]; then
        user_marked_json=$(printf '%s\n' "${user_marked_json}" | jq --argjson bandwidth "${bandwidth}" '. + {"per_line_up_bw": $bandwidth}') || die "Invalid bandwidth: ${bandwidth}"
    fi

    for item in "${storage_values[@]}"; do
        storage_json=$(printf '%s\n' "${storage_json}" | jq --arg storage "${item}" '. += [$storage]') || die "Failed to set storage config"
    done

    for item in "${nic_values[@]}"; do
        nic_json=$(printf '%s\n' "${nic_json}" | jq --arg nic "${item}" '. += [$nic]') || die "Failed to set nic config"
    done

    if [ -n "${use_storage_size}" ]; then
        config_json=$(printf '%s\n' "${config_json}" | jq --argjson storage_size "${use_storage_size}" '. + {"storage_total": $storage_size}') || die "Invalid use_storage_size: ${use_storage_size}"
    fi

    if [ -n "${limited_storage}" ]; then
        user_marked_json=$(printf '%s\n' "${user_marked_json}" | jq --argjson limited_storage "${limited_storage}" '. + {"limited_storage": $limited_storage}') || die "Invalid limited_storage: ${limited_storage}"
    fi

    if [ -n "${limited_area}" ]; then
        user_marked_json=$(printf '%s\n' "${user_marked_json}" | jq --argjson limited_area "${limited_area}" '. + {"limited_area": $limited_area}') || die "Invalid limited_area: ${limited_area}"
    fi

    mtime=$(date +%s)
    user_marked_json=$(printf '%s\n' "${user_marked_json}" | jq --argjson mtime "${mtime}" '. + {"mtime": $mtime}') || die "Failed to set config mtime"
    config_json=$(printf '%s\n' "${config_json}" | jq --argjson user_marked "${user_marked_json}" '. + {"user_marked": $user_marked}') || die "Failed to set user_marked config"
    config_json=$(printf '%s\n' "${config_json}" | jq --argjson nics "${nic_json}" '. + {"nics": $nics}') || die "Failed to set nic config"
    config_json=$(printf '%s\n' "${config_json}" | jq --argjson storage "${storage_json}" '. + {"storage": $storage}') || die "Failed to set storage config"
}

write_config() {
    local config_dir="${config_file%/*}"

    build_config_json
    run_cmd "" mkdir -p "${config_dir}" || die "Failed to create config directory: ${config_dir}"
    printf '%s\n' "${config_json}" > "${config_file}" || die "Failed to write config file: ${config_file}"
    info "Config generated"
}

restart_openfog() {
    local init_script="${install_path}/etc/init.d/openfog.sh"

    if command -v systemctl >/dev/null 2>&1; then
        if run_cmd "Restarting openfog with systemctl..." systemctl restart openfog.service; then
            return 0
        fi
        info "systemctl restart failed, trying init script..."
    fi

    if [ -x "${init_script}" ]; then
        run_cmd "Restarting openfog with init script..." "${init_script}" restart && return 0
    elif [ -f "${init_script}" ]; then
        run_cmd "Restarting openfog with init script..." bash "${init_script}" restart && return 0
    fi

    return 1
}

main() {
    parse_args "$@"
    normalize_install_path
    create_tmp_dir

    install_openfog

    if [ "${install_only}" -eq 0 ]; then
        write_config
    else
        info "Installation completed, no configuration changes to apply"
    fi

    if [ "${no_restart}" -eq 0 ]; then
        restart_openfog || die "Failed to restart openfog"
    fi
    info "Deployed openfog"
}

main "$@"
