#!/usr/bin/env bash

CLN_REGISTER_SERVER="https://cln.cloudlinux.com/cln/api/els/server/register"
CLN_UNREGISTER_SERVER="https://cln.cloudlinux.com/cln/api/els/server/unregister"
LICENSE=""
HOSTNAME=$(hostname)
AUTH_CONF_PATH="/etc/apt/auth.conf.d/postgresql-els.conf"

POSTGRESQL_ELS_PACKAGE_URI="https://repo.tuxcare.com/postgresql-els/els-postgresql-release-install.amd64.deb"

POSTGRESQL_ELS_TEMP_DEB=""

exec 3>&1

cleanup() {
    if [[ -f "$POSTGRESQL_ELS_TEMP_DEB" ]]; then
        rm -f "$POSTGRESQL_ELS_TEMP_DEB"
    fi

    exec 3>&-
}

trap cleanup EXIT

log() {
    printf "%s\\n" "$1" 1>&3
}

show_usage() {
    echo 'Usage: install-postgresql-els-deb-repo.sh [OPTION]...'
    echo ''
    echo '  -l, --license-key   User license key'
    echo '  -f, --force         Force re-register if ELS is already installed'
    echo '  -d, --delete        Delete ELS from server'
    echo '  -v, --validate      Check if ELS is installed'
    echo '  -h, --help          Show this message and exit'
}

do_opts() {
    if [ $# -eq 0 ]; then
        log "$(show_usage)"
        exit 1
    fi
    while [ $# -gt 0 ]; do
        key="$1"
        case $key in
            -h|--help)
                log "$(show_usage)"
                exit 0
                ;;
            -l|--license-key)
                if [[ -z "$2" || "$2" == -* ]]; then
                    log "Error: --license-key requires an argument"
                    log "$(show_usage)"
                    exit 1
                fi
                LICENSE="$2"
                shift
                ;;
            -f|--force)
                FORCE=true
                ;;
            -d|--delete)
                DELETE=true
                ;;
            -v|--validate)
                VALIDATE=true
                ;;
            -*)
                log "Unknown option: $key"
                log "$(show_usage)"
                exit 1
                ;;
        esac
        shift
    done
}

# Three questions, three predicates -- conflating any two of them is how the
# `apt-get remove` path goes wrong. This one is "is the package configuring apt
# right now": `dpkg -l <pkg>` also succeeds in the `deinstall ok config-files`
# state a plain remove leaves, where the postrm has already dropped the
# repository file and the keyring, so ask dpkg for the status field instead.
postgresql_els_installed() {
    log "Checking if els-postgresql-release is already installed... "
    if dpkg-query -W -f='${Status}' els-postgresql-release 2>/dev/null | grep -q '^install ok installed$'; then
        log "els-postgresql-release package is already installed."
        return 0
    fi
    log "els-postgresql-release package is not installed"
    return 1
}

# True while this server still holds a CLN registration. $AUTH_CONF_PATH is
# written only after CLN hands a token out and removed only by --delete, which
# unregisters first -- and no package owns it, so dpkg's state cannot answer
# this. A plain `apt-get remove` drops the package and leaves the token.
postgresql_els_registered() {
    [ -f "$AUTH_CONF_PATH" ]
}

# Everything --delete still has to clean up, whether or not the package is
# currently configuring anything: `dpkg -l` unlike the status check above also
# succeeds in the `deinstall ok config-files` state a plain remove leaves
# behind, and the token outlives the package either way.
postgresql_els_residue() {
    if dpkg -l els-postgresql-release > /dev/null 2>&1; then
        return 0
    fi
    postgresql_els_registered
}

extract_token() {
    if [ ! -f "$AUTH_CONF_PATH" ]; then
        log "ERROR: Auth config file not found at $AUTH_CONF_PATH"
        return 1
    fi

    token=$(sed -n 's/^login \([^[:space:]]*\).*/\1/p' "$AUTH_CONF_PATH")

    if [[ -z "$token" || "$token" != *SERVER* ]]; then
        log "ERROR: Invalid token in $AUTH_CONF_PATH (empty or missing SERVER marker)"
        return 1
    fi

    echo "$token"
    return 0
}

unregister_token() {
    if ! token=$(extract_token); then
        return 1
    fi

    log "Unregistering server token..."

    response=$(curl -i -s -X POST "${CLN_UNREGISTER_SERVER}?token=${token}")
    curl_exit_code=$?

    if [ $curl_exit_code -ne 0 ]; then
        log "ERROR: Failed to connect to unregister server (curl exited with status: $curl_exit_code)"
        return 1
    fi

    if ! echo "$response" | grep -qi '^HTTP/[0-9.]*[[:space:]]200'; then
        if [[ -z "$response" ]]; then
            log "elstoken wasn't found. Server is not registered"
        else
            log "Got incorrect status from CLN: $response"
        fi
        return 1
    fi

    log "Unregistered successfully"
    return 0
}

remove_postgresql_els() {
    # Purge, not remove: the postrm drops the repository file and the keyring on
    # either, but only a purge clears the `deinstall ok config-files` state a
    # plain remove leaves -- and this runs on that state too, so an unpurged
    # leftover ends the same way as a live installation. `apt-get purge` errors
    # out on a name dpkg has never heard of, which is the token-only case below.
    if dpkg -l els-postgresql-release > /dev/null 2>&1; then
        log "Purging els-postgresql-release package... "
        if apt-get purge -yq els-postgresql-release 1>&3; then
            log "Removed els-postgresql-release package successfully"
        else
            log "Error: Could not purge els-postgresql-release package"
            return 1
        fi
    else
        log "els-postgresql-release package is not on this system, nothing to purge"
    fi

    log "Removing authentication configuration file... "
    if [[ -f "$AUTH_CONF_PATH" ]]; then
        if rm -f "$AUTH_CONF_PATH" 1>&3; then
            log "Removed authentication configuration file successfully"
        else
            log "Error: Could not remove auth configuration file: $AUTH_CONF_PATH"
            return 1
        fi
    else
        log "Auth configuration file not found"
    fi

    log "PostgreSQL ELS deleted successfully"
    return 0
}

delete_postgresql_els() {
    if postgresql_els_residue; then
        if [ "$FORCE" = "true" ]; then
            unregister_token || log "Warning: Failed to unregister, but continuing with force delete"
            remove_postgresql_els
        else
            if ! unregister_token; then
                log "Couldn't deactivate account"
                return 12
            fi
            remove_postgresql_els
        fi
    else
        log "ELS is not installed"
    fi
    log "ELS PostgreSQL removal completed"
    return 0
}

get_auth_token() {
    log "Request repository token for this server... "
    local data="{\"key\": \"$3\", \"host_name\": \"$2\"}"
    local ret
    if ! ret=$(curl -s -X POST -H "Content-Type: application/json" -H "Accept: application/json" -d "$data" "$1"); then
        log "Error (Curl command failed)"
        return 1
    fi
    local token
    token=$(echo "$ret" | grep -oP '"token":"\K[^"]*')
    if [ -n "$token" ]; then
        log "Ok"
        echo "$token"
        return 0
    fi
    log "Error (No token was returned from CLN)"
    return 1
}

install_els_postgresql_release() {
    log "Installing els-postgresql-release..."

    POSTGRESQL_ELS_TEMP_DEB=$(mktemp /tmp/els-postgresql-release-XXXXXX.deb)

    if ! curl -fsSL -o "$POSTGRESQL_ELS_TEMP_DEB" "$POSTGRESQL_ELS_PACKAGE_URI"; then
        log "Error: Couldn't download els-postgresql-release.deb"
        return 1
    fi

    # mktemp creates the file 0600, and apt fetches a local .deb as the _apt
    # user, which then cannot read it.
    if ! chmod 644 "$POSTGRESQL_ELS_TEMP_DEB"; then
        log "Error: Couldn't set permissions for $POSTGRESQL_ELS_TEMP_DEB"
        return 2
    fi

    if ! apt-get -yq install "$POSTGRESQL_ELS_TEMP_DEB"; then
        log "Error: Couldn't install els-postgresql-release"
        return 3
    fi

    log "els-postgresql-release installed successfully"
    return 0
}

check_system_compatibility() {
    log "Checking system compatibility..."

    local os_release_path="/etc/os-release"
    if [ ! -f "$os_release_path" ]; then
        log "Error (Could not determine OS: $os_release_path does not exist)"
        return 1
    fi
    # shellcheck disable=SC1090
    . "$os_release_path"

    if [[ "$ID" != "ubuntu" ]]; then
        log "Error: PostgreSQL ELS deb repositories are published for Ubuntu only (detected: ${ID:-unknown})"
        return 1
    fi

    # PostgreSQL ELS publishes an Ubuntu 22.04 apt repo only.
    case "$VERSION_ID" in
        22.04) ;;
        *)
            log "Error: PostgreSQL ELS supports Ubuntu 22.04 only (detected: ${VERSION_ID:-unknown})"
            return 1
            ;;
    esac

    # The repo and the release package are published for amd64 only. Without
    # this the failure surfaces as a generic "Couldn't install
    # els-postgresql-release" after the server has already been registered in CLN.
    if ! command -v dpkg > /dev/null 2>&1; then
        log "Error (dpkg not found; cannot determine architecture)"
        return 1
    fi

    arch=$(dpkg --print-architecture 2>/dev/null)
    if [[ "$arch" != "amd64" ]]; then
        log "Error: PostgreSQL ELS is published for amd64 only (detected: ${arch:-unknown})"
        return 1
    fi

    if ! command -v curl > /dev/null 2>&1; then
        log "Error: curl is required but not installed"
        return 1
    fi

    log "System compatibility check passed"
}

check_superuser_privileges() {
    log "Checking for superuser privileges..."
    if [ "$(id -u)" -ne 0 ]; then
        log "Error: This script must be run with superuser privileges"
        return 1
    fi
    log "Superuser privileges confirmed"
    return 0
}

main() {
    do_opts "$@"

    if ! check_superuser_privileges; then
        return 14
    fi

    if ! check_system_compatibility; then
        return 1
    fi

    if [ "$DELETE" = "true" ];  then
        delete_postgresql_els
        return $?
    fi

    if [[ "$VALIDATE" = "true" ]]; then
        if postgresql_els_installed; then
            if token=$(extract_token); then
                log "Server is registered with token $token"
                exit 0
            else
                log "els-postgresql-release is installed but token is missing or invalid"
                exit 1
            fi
        else
            if postgresql_els_registered; then
                log "els-postgresql-release is not installed, but $AUTH_CONF_PATH is still"
                log "on this server, so its CLN registration is still held."
                log "Run script with --delete to release it."
            else
                log "Server is not registered"
            fi
            exit 1
        fi
    fi

    if [[ -z $LICENSE ]]; then
        log "License key should be specified with -l, --license-key."
        log "See -h, --help."
        return 1
    fi

    # A held registration blocks a fresh install just as a present package does:
    # registering again would hand out a second token and leave the first one
    # consuming a seat with nothing on the server able to release it.
    if postgresql_els_installed || postgresql_els_registered; then
        if [ "$FORCE" = "true" ]; then
            unregister_token
            remove_postgresql_els
        else
            if ! postgresql_els_installed; then
                log "els-postgresql-release is not installed, but this server still holds a"
                log "CLN registration ($AUTH_CONF_PATH)"
                log "Run script with --delete to release it, or --force to re-register"
            elif ! extract_token > /dev/null 2>&1; then
                log "els-postgresql-release is installed but token is missing or invalid"
                log "Run script with --force to repair the installation"
            else
                log "This server has installed ELS repo and token"
                log "For re-registration license run script with --force"
            fi
            return 2
        fi
    fi

    log "Registering in CLN..."
    local token
    if ! token=$(get_auth_token "$CLN_REGISTER_SERVER" "$HOSTNAME" "$LICENSE"); then
        return 3
    fi

    # The token is not in the package: the repository URL els-postgresql-release
    # writes carries no credentials, apt picks them up from auth.conf.d. Write
    # it before the package, so the apt-get update below is already authorized.
    if ! printf 'machine repo.tuxcare.com/postgresql-els/\nlogin %s\npassword\n' "$token" > "$AUTH_CONF_PATH"; then
        log "Error (Could not write to $AUTH_CONF_PATH)"
        return 10
    fi

    log "Applying repository token for this server..."
    if ! install_els_postgresql_release; then
        return 9
    fi

    log "Updating cache..."
    if ! apt-get -yq update 1>&3; then
        log "Error (Could not update repository cache)"
        return 6
    fi

    log "ELS for PostgreSQL installed successfully"
}

main "$@"
