#!/bin/bash
set -euo pipefail

# shellcheck disable=SC1091
source "/etc/default/ngcp-roles"

if [ "${NGCP_TYPE}" = "spce" ]; then
    echo "Not supported for CE. Use 'adduser' instead." >&2
    exit 1
fi

if [ -z "${NGCP_HOSTS}" ]; then
    echo "No other cluster hosts configured. Is this CE? Use 'adduser' instead." >&2
    exit 1
fi

# shellcheck disable=SC1091
source "/etc/adduser.conf"

usage() {
    echo "Usage: $0 [<option>...] <username>

Options:
  -h, --help                       Print this output.
  -s, --shell <shell>              Set a non-default shell.
  --ingroup, --gid, -g <group>     Don't create a usergroup and instead put
                                     the new user into this group.
  --no-create-home                 Skip creation of the user's home directory.
"
}

############################################
# Parse command line arguments
############################################

USERNAME=''
INGROUP=''
COMMENT='created by ngcp-adduser'
NO_CREATE_HOME=false
DSHELL=${DSHELL:-/bin/bash}
DHOME=${DHOME:-/home}
FIRST_UID=${FIRST_UID:-1000}
LAST_UID=${LAST_UID:-59999}
FIRST_GID=${FIRST_GID:-1000}
LAST_GID=${LAST_GID:-59999}

while [ "$#" -ge 1 ]; do
    case "$1" in
        --help|-h)
            usage
            exit 0
            ;;
        --shell|-s)
            shift
            if [ -z "$1" ]; then
                echo "Option '--shell' requires an argument" >&2
                usage
                exit 1
            fi
            DSHELL=$1
            ;;
        --ingroup|--gid|-g)
            shift
            if [ -z "$1" ]; then
                echo "Option '--ingroup' requires an argument" >&2
                usage
                exit 1
            fi
            INGROUP=$1
            ;;
        --no-create-home)
            NO_CREATE_HOME=true
            ;;
        -*)
            echo "Unsupported command line switch '$1'" >&2
            usage
            exit 1
            ;;
        *)
            if [ -n "${USERNAME}" ]; then
                echo "Unknown extra command line argument '$1'" >&2
                usage
                exit 1
            fi
            USERNAME=$1
            ;;
    esac

    shift
done

############################################
# Validate inputs and environment
############################################

if [ -z "${USERNAME}" ]; then
    echo "No username specified." >&2
    usage
    exit 1
fi

if ! [[ "${USERNAME}" =~ ^[a-z_][a-z0-9_-]*[\$]?$ ]]; then
    echo "Invalid username '${USERNAME}'." >&2
    usage
    exit 1
fi

if [ "$(id -u)" -ne 0 ]; then
    echo "Only root may add users." >&2
    exit 1
fi

read -r -a HOSTS <<<"${NGCP_HOSTS}"

echo "Hosts: ${HOSTS[*]}"

############################################
# Check if user or group already exists
############################################

echo "Checking availability of user & group..."

DGID=-1

for host in "${HOSTS[@]}"; do
    if ngcp-ssh "${host}" "getent passwd ${USERNAME}" >/dev/null; then
        echo "ERROR: User ${USERNAME} already exists on ${host}" >&2
        exit 1
    fi

    if [ -z "${INGROUP}" ]; then
        if ngcp-ssh "${host}" "getent group ${USERNAME}" >/dev/null; then
            echo "ERROR: Group ${USERNAME} already exists on ${host}" >&2
            exit 1
        fi
    else
        if ! ENT="$(ngcp-ssh "${host}" "getent group ${INGROUP}")"; then
            echo "ERROR: Group ${INGROUP} doesn't exist on ${host}" >&2
            exit 1
        fi

        # check for consistent GID
        EGID=$(echo "${ENT}" | cut -d: -f3)
        if [ -z "${EGID}" ]; then
            echo "ERROR: Could not parse group entry from ${host}" >&2
            exit 1
        fi
        if [ "${DGID}" -eq -1 ]; then
            DGID=${EGID}
        elif [ "${DGID}" -ne "${EGID}" ]; then
            echo "ERROR: Group ${INGROUP} does not have consistent IDs across the cluster" >&2
            exit 1
        fi
    fi
done

echo "User and group name are free cluster-wide."

############################################
# Find free UID/GID across cluster
############################################

echo "Determining UID & GID..."

find_unused() {
    DB=$1
    ID_CANDIDATE=$2
    LAST=$3

    while true; do
        ID_IN_USE=false

        for host in "${HOSTS[@]}"; do
            if ! ngcp-ssh "$host" "getent ${DB} ${ID_CANDIDATE}" > /dev/null; then
                continue
            fi

            ID_IN_USE=true

            # find next larger one
            if ! LIST=$(ngcp-ssh "$host" "getent ${DB} | cut -d: -f3" | sort -n); then
                echo "ERROR: Failed to read ${DB} list from ${host}" >&2
                exit 1
            fi

            ID_CANDIDATE=$((ID_CANDIDATE+1))
            while true; do
                if [ "${ID_CANDIDATE}" -gt "${LAST}" ]; then
                    echo "ERROR: No free ID for ${DB} found!" >&2
                    exit 1
                fi
                if ! grep -xq "${ID_CANDIDATE}" <<<"${LIST}"; then
                    break
                fi
                ID_CANDIDATE=$((ID_CANDIDATE+1))
            done
        done

        if ! "${ID_IN_USE}"; then
            echo "${ID_CANDIDATE}"
            return 0
        fi
    done
}

DUID=$(find_unused "passwd" "${FIRST_UID}" "${LAST_UID}")

if [ -z "${INGROUP}" ]; then
    DGID=$(find_unused "group" "${FIRST_GID}" "${LAST_GID}")
fi

echo "Selected UID/GID: ${DUID}/${DGID}"

############################################
# Create group and user everywhere
############################################

CMDLINE="useradd -u ${DUID} -g ${DGID} -s ${DSHELL} -c \"${COMMENT}\" ${USERNAME}"

if ! "${NO_CREATE_HOME}"; then
    CMDLINE="${CMDLINE} -m"
else
    CMDLINE="${CMDLINE} -M"
fi

for host in "${HOSTS[@]}"; do
    if [ -z "${INGROUP}" ]; then
        echo "Creating group on ${host}"
        ngcp-ssh "${host}" "groupadd -g ${DGID} ${USERNAME}"
    fi

    echo "Creating user on ${host}"
    ngcp-ssh "${host}" "${CMDLINE}"
done

echo "SUCCESS: User ${USERNAME} created cluster-wide with UID/GID ${DUID}/${DGID}"
