#!/bin/bash
#
# dirctl — global directory management command for the OpenLDAP test container.
#
#   dirctl seed                     seed groups + users, save credentials
#   dirctl create [opts]            create a user (all fields optional / auto-generated)
#   dirctl list                     list users
#   dirctl show <username>          show a user
#   dirctl passwd <username> [pw]   set/reset a user password (random if omitted)
#   dirctl update <username> [opts] update user attributes
#
# create/update opts (long or short):
#   -u|--username  -p|--password  -e|--email  -f|--first  -l|--last  -g|--group  -c|--company
# create with no options generates a random username, email and password.
#
set -euo pipefail

BASE_DN="${LDAP_BASE_DN:-dc=ldap,dc=example,dc=test}"
ADMIN_DN="cn=admin,${BASE_DN}"
ADMIN_PW="${LDAP_ADMIN_PASSWORD:-admin}"
URI="${LDAP_URI:-ldap://localhost}"
PEOPLE="ou=people,${BASE_DN}"
GROUP_BASE="ou=groups,${BASE_DN}"
DOMAIN="${LDAP_DOMAIN:-ldap.example.test}"
CRED_FILE="${CRED_FILE:-/credentials/openldap-users.txt}"

L() { ldapsearch -x -LLL -H "$URI" -D "$ADMIN_DN" -w "$ADMIN_PW" "$@"; }
M() { ldapmodify -x -H "$URI" -D "$ADMIN_DN" -w "$ADMIN_PW" "$@"; }
A() { ldapadd    -x -H "$URI" -D "$ADMIN_DN" -w "$ADMIN_PW" "$@"; }

# head reads /dev/urandom first (no mid-pipe SIGPIPE), downstream commands consume to EOF -> pipefail-safe
random_string() { head -c 24 /dev/urandom | base64 | tr -dc 'A-Za-z0-9' | cut -c "1-${1:-16}"; }
random_password() { random_string 16; }

user_exists() { [ -n "$(L -b "$PEOPLE" "(uid=$1)" dn 2>/dev/null)" ]; }
group_gid() { L -b "$GROUP_BASE" "(cn=$1)" gidNumber | awk '/^gidNumber:/{print $2}'; }

base_username() {
    local b
    b="$(printf '%s%s' "$(printf '%s' "$1" | cut -c1)" "$2" | tr '[:upper:]' '[:lower:]' | tr -dc 'a-z0-9')"
    [ -n "$b" ] || b="user$(random_string 6 | tr 'A-Z' 'a-z')"
    printf '%s' "$b"
}

derive_username() {
    local base candidate n
    base="$(base_username "$1" "$2")"; candidate="$base"; n=1
    while user_exists "$candidate"; do n=$((n + 1)); candidate="${base}${n}"; done
    printf '%s' "$candidate"
}

next_uid_number() {
    local max
    max=$(L -b "$PEOPLE" "(objectClass=posixAccount)" uidNumber | awk '/^uidNumber:/{print $2}' | sort -n | tail -1)
    echo $(( ${max:-9999} + 1 ))
}

ensure_ou() {
    L -b "$1" -s base dn >/dev/null 2>&1 && return 0
    A >/dev/null <<EOF
dn: $1
objectClass: organizationalUnit
ou: $(echo "$1" | sed 's/^ou=\([^,]*\),.*/\1/')
EOF
}

ensure_group() {
    L -b "$GROUP_BASE" "(cn=$1)" dn >/dev/null 2>&1 && [ -n "$(group_gid "$1")" ] && return 0
    A >/dev/null <<EOF
dn: cn=$1,$GROUP_BASE
objectClass: posixGroup
cn: $1
gidNumber: $2
EOF
}

record_credentials() {
    mkdir -p "$(dirname "$CRED_FILE")"
    [ -f "$CRED_FILE" ] || printf '# OpenLDAP test users (%s)\n# username | password | email | group | dn\n' "$DOMAIN" > "$CRED_FILE"
    printf '%s | %s | %s | %s | %s\n' "$1" "$2" "$3" "$4" "$5" >> "$CRED_FILE"
}

print_summary() {
    printf '\n  user created\n    username : %s\n    password : %s\n    email    : %s\n    group    : %s\n    dn       : %s\n\n' \
        "$1" "$2" "$3" "$4" "$5"
}

cmd_create() {
    local username="" password="" email="" first="" last="" group="" company="EasyDCIM"
    while [ $# -gt 0 ]; do
        case "$1" in
            -u|--username) username="$2"; shift 2;;
            -p|--password) password="$2"; shift 2;;
            -e|--email)    email="$2"; shift 2;;
            -f|--first)    first="$2"; shift 2;;
            -l|--last)     last="$2"; shift 2;;
            -g|--group)    group="$2"; shift 2;;
            -c|--company)  company="$2"; shift 2;;
            -*)            echo "unknown option: $1" >&2; exit 2;;
            *) [ -z "$username" ] && { username="$1"; shift; } || { echo "unexpected argument: $1" >&2; exit 2; };;
        esac
    done
    [ -n "$username" ] || username="$(derive_username "$first" "$last")"
    [ -n "$first" ] || first="$username"
    [ -n "$last" ] || last="User"
    [ -n "$email" ] || email="${username}@${DOMAIN}"
    [ -n "$password" ] || password="$(random_password)"
    local gid=""
    [ -n "$group" ] && gid="$(group_gid "$group")"
    [ -n "$gid" ] || gid="5000"
    local uidnum hash dn
    uidnum="$(next_uid_number)"
    hash="$(slappasswd -s "$password")"
    dn="uid=$username,$PEOPLE"
    A >/dev/null <<EOF
dn: $dn
objectClass: inetOrgPerson
objectClass: posixAccount
uid: $username
cn: $first $last
givenName: $first
sn: $last
mail: $email
o: $company
uidNumber: $uidnum
gidNumber: $gid
homeDirectory: /home/$username
loginShell: /bin/bash
userPassword: $hash
EOF
    record_credentials "$username" "$password" "$email" "${group:-}" "$dn"
    print_summary "$username" "$password" "$email" "${group:-none}" "$dn"
}

cmd_list() {
    L -b "$PEOPLE" "(objectClass=posixAccount)" uid mail gidNumber \
        | awk '/^uid:/{u=$2} /^mail:/{m=$2} /^gidNumber:/{g=$2; printf "%-16s %-34s gid=%s\n", u, m, g}'
}

cmd_show() { L -b "$PEOPLE" "(uid=$1)"; }

cmd_passwd() {
    local uid="$1"; local password="${2:-}"
    [ -n "$password" ] || password="$(random_password)"
    ldappasswd -x -H "$URI" -D "$ADMIN_DN" -w "$ADMIN_PW" -s "$password" "uid=$uid,$PEOPLE"
    echo "password for uid=$uid set to: $password"
}

cmd_update() {
    local uid="$1"; shift
    local changes=""
    while [ $# -gt 0 ]; do
        case "$1" in
            -e|--email)   changes="${changes}replace: mail\nmail: $2\n-\n"; shift 2;;
            -f|--first)   changes="${changes}replace: givenName\ngivenName: $2\n-\n"; shift 2;;
            -l|--last)    changes="${changes}replace: sn\nsn: $2\n-\n"; shift 2;;
            -c|--company) changes="${changes}replace: o\no: $2\n-\n"; shift 2;;
            *) echo "unknown option: $1" >&2; exit 2;;
        esac
    done
    [ -n "$changes" ] || { echo "nothing to update" >&2; exit 2; }
    printf "dn: uid=$uid,$PEOPLE\nchangetype: modify\n${changes%-\\n}" | M
    echo "updated uid=$uid"
}

cmd_seed() {
    ensure_ou "$PEOPLE"
    ensure_ou "$GROUP_BASE"
    ensure_group "administrators" 5001
    ensure_group "employees"      5002
    ensure_group "sales"          5003
    ensure_group "support"        5004
    ensure_group "clients"        5005

    # roster (unique to the OpenLDAP directory): First:Last:group
    local roster="
Lucas:Bennett:administrators
Chloe:Foster:administrators
Ethan:Rivera:administrators
Zoe:Coleman:administrators
Nathan:Perry:employees
Lily:Powell:employees
Owen:Bryant:employees
Hannah:Ross:employees
Caleb:Hughes:sales
Ella:Butler:sales
Ryan:Simmons:sales
Aria:Barnes:sales
Isaac:Ward:support
Nora:Gray:support
Levi:Watson:support
Layla:Price:support
Julian:Reed:clients
Aurora:Kelly:clients
Miles:Cooper:clients
Scarlett:Hayes:clients
"
    echo "$roster" | while IFS=: read -r first last group; do
        [ -n "$first" ] || continue
        uname="$(base_username "$first" "$last")"
        user_exists "$uname" && { echo "skip existing $uname"; continue; }
        cmd_create -u "$uname" --first "$first" --last "$last" --group "$group"
    done
    echo "seed complete. credentials -> $CRED_FILE"
}

sub="${1:-}"; shift || true
case "$sub" in
    seed)   cmd_seed "$@";;
    create) cmd_create "$@";;
    list)   cmd_list "$@";;
    show)   cmd_show "$@";;
    passwd) cmd_passwd "$@";;
    update) cmd_update "$@";;
    *) echo "usage: dirctl {seed|create|list|show|passwd|update} ..." >&2; exit 2;;
esac
