#!/bin/bash
# ================================================================
# Manage SIP Extensions
#
# Writes to /etc/asterisk/managed/extensions-sip.conf (persistent volume)
# Entrypoint merges base + managed ? pjsip.conf on startup
#
# Auto-detects TLS domain from cert:
#   Wildcard cert (*.mforce.asia) ? from_domain=ai.mforce.asia
#   Self-signed cert              ? from_domain=<server_ip>
#
# Usage:
#   manage-extensions add <ext> <password> [name] [can_call] [can_recv]
#   manage-extensions remove <ext>
#   manage-extensions set-perms <ext> <can_call> <can_recv>
#   manage-extensions list
#   manage-extensions web-enable <ext|all>   add the web client (browser) endpoint to existing extensions
#   manage-extensions web-secret <ext>       new web secret: every browser of this extension is signed out
#
# M-Phone web client: every extension gets a SECOND endpoint "<ext>-web"
# (WebRTC: SIP over WebSocket, DTLS-SRTP, ICE) inside the same block. It has
# its own registrations and its own secret (never the SIP password of the
# phones), the same caller ID and the same permissions. The dialplan
# ([mp-devices]) rings both endpoints of a person together.
# ================================================================

MANAGED_FILE="/etc/asterisk/managed/extensions-sip.conf"
FULL_CONF="/etc/asterisk/pjsip.conf"

# Get TLS port dynamically from transport-tls bind address
TLS_PORT=$(awk '/\[transport-tls\]/{found=1} found && /^bind=/{print $0; exit}' /etc/asterisk/pjsip.conf 2>/dev/null | grep -o ':[0-9]*' | tr -d ':')
[ -z "$TLS_PORT" ] && TLS_PORT=$(awk '/\[transport-tls\]/{found=1} found && /^bind=/{print $0; exit}' /etc/asterisk/pjsip.base.conf 2>/dev/null | grep -o ':[0-9]*' | tr -d ':')
[ -z "$TLS_PORT" ] && TLS_PORT=5061

# Get external IP
EXTERNAL_IP=$(grep 'external_media_address' /etc/asterisk/pjsip.conf 2>/dev/null | head -1 | cut -d= -f2 | tr -d ' ')
[ -z "$EXTERNAL_IP" ] && EXTERNAL_IP=$(grep 'external_media_address' /etc/asterisk/pjsip.base.conf 2>/dev/null | head -1 | cut -d= -f2 | tr -d ' ')

# Auto-detect TLS domain
# Check for domain file (written by add-company.sh when wildcard cert is used)
TLS_DOMAIN=""
if [ -f /etc/asterisk/keys/tls-domain ]; then
    TLS_DOMAIN=$(cat /etc/asterisk/keys/tls-domain 2>/dev/null | tr -d '[:space:]')
fi

if [ -n "$TLS_DOMAIN" ]; then
    FROM_DOMAIN="${TLS_DOMAIN}"
else
    FROM_DOMAIN="${EXTERNAL_IP}"
fi

# Ensure managed file exists
[ ! -f "$MANAGED_FILE" ] && echo "; Managed extensions" > "$MANAGED_FILE"

_random_secret() { head -c 24 /dev/urandom | base64 | tr -dc 'A-Za-z0-9' | head -c 24; }

# web secret of an existing extension ("" when it has no web endpoint)
_web_secret_of() {
    awk -v ext="$1" '
        $0 ~ "^; --- Extension " ext ":"      { inblk=1 }
        $0 ~ "^; --- End Extension " ext " -" { inblk=0 }
        inblk && $0 == "[" ext "-web]"        { web=1 }
        inblk && web && /^password=/          { sub(/^password=/, ""); print; exit }
    ' "$MANAGED_FILE"
}

# the web endpoint of one extension, as config text
_web_block() {
    local EXT=$1 NAME=$2 CONTEXT=$3 WEBSECRET=$4
    cat <<EOF

[${EXT}-web]
type=auth
auth_type=userpass
username=${EXT}-web
password=${WEBSECRET}

[${EXT}-web]
type=aor
max_contacts=5
remove_existing=yes
qualify_frequency=30

[${EXT}-web]
type=endpoint
context=${CONTEXT}
disallow=all
allow=opus
allow=ulaw
allow=alaw
webrtc=yes
auth=${EXT}-web
aors=${EXT}-web
callerid="${NAME}" <${EXT}>
allow_transfer=yes
dtmf_mode=rfc4733
force_rport=yes
rewrite_contact=yes
rtp_symmetric=yes
direct_media=no
rtp_timeout=30
rtp_timeout_hold=300
from_domain=${FROM_DOMAIN}
EOF
}

add_extension() {
    local EXT=$1
    local PASS=$2
    local NAME=${3:-"Extension ${EXT}"}
    local CAN_CALL=${4:-yes}
    local CAN_RECV=${5:-yes}
    local WEBSECRET=${6:-$(_random_secret)}

    if grep -q "^; --- Extension ${EXT}:" "$MANAGED_FILE" 2>/dev/null; then
        echo "ERROR: Extension ${EXT} already exists."
        return 1
    fi

    local CONTEXT="from-internal"
    if [ "$CAN_CALL" = "no" ]; then
        CONTEXT="receive-only"
    fi

    cat >> "$MANAGED_FILE" <<EOF

; --- Extension ${EXT}: ${NAME} | call=${CAN_CALL} recv=${CAN_RECV} ---
[${EXT}]
type=auth
auth_type=userpass
username=${EXT}
password=${PASS}

[${EXT}]
type=aor
max_contacts=2
remove_existing=yes
qualify_frequency=30

[${EXT}]
type=endpoint
transport=transport-tls
context=${CONTEXT}
disallow=all
allow=ulaw
allow=alaw
auth=${EXT}
aors=${EXT}
callerid="${NAME}" <${EXT}>
allow_transfer=yes
dtmf_mode=rfc4733
force_rport=yes
rewrite_contact=yes
rtp_symmetric=yes
direct_media=no
from_domain=${FROM_DOMAIN}
$(_web_block "$EXT" "$NAME" "$CONTEXT" "$WEBSECRET")
; --- End Extension ${EXT} ---
EOF

    # Rebuild full pjsip.conf (base + managed)
    _rebuild_conf

    # Set blocked flag in AstDB if can_recv is no
    if [ "$CAN_RECV" = "no" ]; then
        asterisk -rx "database put blocked ${EXT} 1" 2>/dev/null
    else
        asterisk -rx "database del blocked ${EXT}" 2>/dev/null
    fi

    asterisk -rx "core reload" 2>/dev/null
    echo "OK: Extension ${EXT} (${NAME}) added."
    echo "    Softphone: Username=${EXT}  Password=${PASS}"
    echo "    Transport: TLS (port ${TLS_PORT})"
    echo "    From domain: ${FROM_DOMAIN}"
    echo "    Can make calls: ${CAN_CALL}"
    echo "    Can receive internal: ${CAN_RECV}"
    echo "    Can receive AI: always"
}

remove_extension() {
    local EXT=$1

    if ! grep -q "^; --- Extension ${EXT}:" "$MANAGED_FILE" 2>/dev/null; then
        echo "ERROR: Extension ${EXT} not found."
        return 1
    fi

    local BEFORE_COUNT
    BEFORE_COUNT=$(grep -c "^; --- Extension " "$MANAGED_FILE" 2>/dev/null || echo 0)

    # Remove the extension block from managed config
    awk -v ext="$EXT" '
        $0 ~ "^; --- Extension " ext ":" { skip=1; next }
        $0 ~ "^; --- End Extension " ext " ---" { skip=0; next }
        !skip { print }
    ' "$MANAGED_FILE" > /tmp/ext_tmp_$$.conf

    # Verify deletion actually worked before overwriting
    if grep -q "^; --- Extension ${EXT}:" /tmp/ext_tmp_$$.conf 2>/dev/null; then
        echo "ERROR: Failed to remove extension ${EXT} from config."
        rm -f /tmp/ext_tmp_$$.conf
        return 1
    fi

    cat /tmp/ext_tmp_$$.conf > "$MANAGED_FILE"
    rm -f /tmp/ext_tmp_$$.conf

    local AFTER_COUNT
    AFTER_COUNT=$(grep -c "^; --- Extension " "$MANAGED_FILE" 2>/dev/null || echo 0)

    # Rebuild full pjsip.conf (base + managed)
    _rebuild_conf

    # Clean AstDB
    asterisk -rx "database del blocked ${EXT}" 2>/dev/null

    # FIX: Destroy endpoint from PJSIP memory THEN do full module reload
    # core reload does NOT unregister removed PJSIP endpoints � they stay
    # in memory and ARI still reports them, causing ext-sync to re-add them
    _drop_web_devices "$EXT"
    asterisk -rx "pjsip send unregister ${EXT}" 2>/dev/null
    asterisk -rx "module reload res_pjsip.so" 2>/dev/null
    sleep 1

    echo "OK: Extension ${EXT} removed. (${BEFORE_COUNT} ? ${AFTER_COUNT} extensions)"
}

set_perms() {
    local EXT=$1
    local CAN_CALL=${2:-yes}
    local CAN_RECV=${3:-yes}

    if ! grep -q "^; --- Extension ${EXT}:" "$MANAGED_FILE" 2>/dev/null; then
        echo "ERROR: Extension ${EXT} not found."
        return 1
    fi

    # Extract current name and password
    local HEADER=$(grep "^; --- Extension ${EXT}:" "$MANAGED_FILE")
    local NAME=$(echo "$HEADER" | sed 's/; --- Extension [^:]*: \([^|]*\).*/\1/' | sed 's/ *$//')
    local PASS=$(awk "/^; --- Extension ${EXT}:/{found=1} found && /^password=/{print \$0; exit}" "$MANAGED_FILE" | cut -d= -f2)
    local WEBSEC=$(_web_secret_of "$EXT")

    # Remove and re-add
    awk "/^; --- Extension ${EXT}:/{skip=1} /^; --- End Extension ${EXT} ---/{skip=0; next} !skip" "$MANAGED_FILE" > /tmp/ext_tmp.conf
    cat /tmp/ext_tmp.conf > "$MANAGED_FILE"
    rm -f /tmp/ext_tmp.conf

    add_extension "$EXT" "$PASS" "$NAME" "$CAN_CALL" "$CAN_RECV" "$WEBSEC"
}

set_password() {
    local EXT=$1
    local NEWPASS=$2

    if [ -z "$NEWPASS" ]; then
        echo "ERROR: Password is required."
        return 1
    fi

    if ! grep -q "^; --- Extension ${EXT}:" "$MANAGED_FILE" 2>/dev/null; then
        echo "ERROR: Extension ${EXT} not found."
        return 1
    fi

    # Preserve current name + permissions from the header, change only the password
    local HEADER=$(grep "^; --- Extension ${EXT}:" "$MANAGED_FILE")
    local NAME=$(echo "$HEADER" | sed 's/; --- Extension [^:]*: \([^|]*\).*/\1/' | sed 's/ *$//')
    local CAN_CALL=$(echo "$HEADER" | grep -o 'call=[a-z]*' | cut -d= -f2)
    local CAN_RECV=$(echo "$HEADER" | grep -o 'recv=[a-z]*' | cut -d= -f2)
    [ -z "$CAN_CALL" ] && CAN_CALL=yes
    [ -z "$CAN_RECV" ] && CAN_RECV=yes
    # the web secret is separate: a new phone password does not sign the browsers out
    local WEBSEC=$(_web_secret_of "$EXT")

    # Remove the existing block, then re-add with the new password
    awk "/^; --- Extension ${EXT}:/{skip=1} /^; --- End Extension ${EXT} ---/{skip=0; next} !skip" "$MANAGED_FILE" > /tmp/ext_tmp.conf
    cat /tmp/ext_tmp.conf > "$MANAGED_FILE"
    rm -f /tmp/ext_tmp.conf

    add_extension "$EXT" "$NEWPASS" "$NAME" "$CAN_CALL" "$CAN_RECV" "$WEBSEC"

    # Force PJSIP to drop the old auth so the phone must re-register with the new password
    asterisk -rx "module reload res_pjsip.so" 2>/dev/null
    echo "OK: Extension ${EXT} password updated."
}

# browser registrations of one extension, removed from the registrar
_drop_web_devices() {
    asterisk -rx "database show registrar/contact" 2>/dev/null | grep -o "^/registrar/contact/$1-web;@[0-9a-f]*" | while read key; do
        asterisk -rx "database del registrar/contact ${key#/registrar/contact/}" >/dev/null 2>&1
    done
}

# Add the web endpoint to one existing extension (or to all) that does not have it yet.
web_enable() {
    local WHICH=$1 EXT n=0
    for EXT in $(grep "^; --- Extension " "$MANAGED_FILE" 2>/dev/null | sed 's/; --- Extension \([^:]*\):.*/\1/'); do
        [ "$WHICH" = "all" ] || [ "$WHICH" = "$EXT" ] || continue
        [ -n "$(_web_secret_of "$EXT")" ] && continue
        local HEADER=$(grep "^; --- Extension ${EXT}:" "$MANAGED_FILE")
        local NAME=$(echo "$HEADER" | sed 's/; --- Extension [^:]*: \([^|]*\).*/\1/' | sed 's/ *$//')
        local CAN_CALL=$(echo "$HEADER" | grep -o 'call=[a-z]*' | cut -d= -f2)
        local CONTEXT="from-internal"; [ "$CAN_CALL" = "no" ] && CONTEXT="receive-only"
        _web_block "$EXT" "$NAME" "$CONTEXT" "$(_random_secret)" > /tmp/web_blk_$$.conf
        awk -v ext="$EXT" -v blk="/tmp/web_blk_$$.conf" '
            $0 ~ "^; --- End Extension " ext " ---" { while ((getline l < blk) > 0) print l; close(blk) }
            { print }
        ' "$MANAGED_FILE" > /tmp/ext_tmp_$$.conf
        cat /tmp/ext_tmp_$$.conf > "$MANAGED_FILE"; rm -f /tmp/ext_tmp_$$.conf /tmp/web_blk_$$.conf
        n=$((n + 1))
    done
    _rebuild_conf
    # endpoints are ADDED only: a reload is enough, calls and registrations are not touched
    asterisk -rx "module reload res_pjsip.so" 2>/dev/null
    echo "OK: web client enabled for ${n} extension(s)."
}

# New web secret for one extension: every browser of it is signed out, the phones are not touched.
web_secret() {
    local EXT=$1
    if [ -z "$(_web_secret_of "$EXT")" ]; then
        echo "ERROR: Extension ${EXT} has no web client."
        return 1
    fi
    local NEW=$(_random_secret)
    awk -v ext="$EXT" -v pw="$NEW" '
        $0 ~ "^; --- Extension " ext ":"      { inblk=1 }
        $0 ~ "^; --- End Extension " ext " -" { inblk=0; web=0 }
        inblk && $0 == "[" ext "-web]"        { web=1 }
        inblk && web == 1 && /^password=/     { print "password=" pw; web=2; next }
        { print }
    ' "$MANAGED_FILE" > /tmp/ext_tmp_$$.conf
    cat /tmp/ext_tmp_$$.conf > "$MANAGED_FILE"; rm -f /tmp/ext_tmp_$$.conf
    _rebuild_conf
    _drop_web_devices "$EXT"
    asterisk -rx "module reload res_pjsip.so" 2>/dev/null
    echo "OK: Extension ${EXT} web secret renewed."
}

list_extensions() {
    echo "=== Extensions ==="
    grep "^; --- Extension " "$MANAGED_FILE" 2>/dev/null | while read line; do
        local ext=$(echo "$line" | sed 's/; --- Extension \([^:]*\):.*/\1/')
        local call=$(echo "$line" | grep -o 'call=[a-z]*' || echo "call=yes")
        local recv=$(echo "$line" | grep -o 'recv=[a-z]*' || echo "recv=yes")
        echo "  ${ext} [${call} ${recv}]"
    done
    if ! grep -q "^; --- Extension " "$MANAGED_FILE" 2>/dev/null; then
        echo "  (none)"
    fi
    echo ""
    echo "=== Live status ==="
    asterisk -rx "pjsip list endpoints" 2>/dev/null || echo "  (asterisk not running)"
}

_rebuild_conf() {
    # Rebuild pjsip.conf = base + managed extensions
    cat /etc/asterisk/pjsip.base.conf > /etc/asterisk/pjsip.conf

    # Point the TLS transport at the decrypted-in-RAM cert (start.sh decrypts
    # the vault's encrypted cert into /dev/shm/aiphone-tls at boot).
    if [ -f /dev/shm/aiphone-tls/asterisk.pem ]; then
        sed -i 's|cert_file=/etc/asterisk/keys/asterisk.pem|cert_file=/dev/shm/aiphone-tls/asterisk.pem|' /etc/asterisk/pjsip.conf
        sed -i 's|priv_key_file=/etc/asterisk/keys/asterisk.key|priv_key_file=/dev/shm/aiphone-tls/asterisk.key|' /etc/asterisk/pjsip.conf
    fi

    # Chat messages between extensions: every managed endpoint gets
    # message_context=mphone-msg (a dialplan context with no extensions, so a
    # SIP MESSAGE goes to the bridge, see src/msg-store.js). Added to this
    # assembled copy only — the managed file itself is left as it is, and
    # because the copy is rebuilt from scratch every time this cannot double up.
    #
    # Phone endpoints (<ext>, not <ext>-web which has its own) also get
    # rtp_timeout=30 / rtp_timeout_hold=300: a phone that vanished without
    # sending BYE (app killed, network lost, or its BYE could not reach the
    # phone system) is hung up after 30 s without sound, so the person on the
    # other side is never left in a call that is over (300 s while on hold).
    awk '
        /^; --- Extension [^:]*:/      { inblk=1 }
        /^; --- End Extension /        { inblk=0 }
        inblk && /^\[/                 { isweb = ($0 ~ /-web\]/) }
        inblk && /^message_context=/   { next }
        inblk && !isweb && /^rtp_timeout(_hold)?=/ { next }
        { print }
        inblk && /^context=/           { print "message_context=mphone-msg"; if (!isweb) { print "rtp_timeout=30"; print "rtp_timeout_hold=300" } }
    ' "$MANAGED_FILE" >> /etc/asterisk/pjsip.conf

    # No TLS certificate installed (fresh box ships none, deliberately): the
    # TLS transport must not load (it would ERROR every boot with missing cert
    # files) and extension endpoints fall back to UDP. The managed file keeps
    # transport-tls canonically — only this assembled copy is downgraded — so
    # the moment a certificate is installed from the dashboard (container
    # restarts), this rebuild runs again and TLS comes back by itself.
    if [ ! -f /dev/shm/aiphone-tls/asterisk.pem ]; then
        awk 'BEGIN{s=0} /^\[transport-tls\]/{s=1; next} s==1 && /^\[/{s=0} s==0{print}' /etc/asterisk/pjsip.conf > /etc/asterisk/pjsip.conf.notls
        cat /etc/asterisk/pjsip.conf.notls > /etc/asterisk/pjsip.conf
        rm -f /etc/asterisk/pjsip.conf.notls
        sed -i 's|^transport=transport-tls$|transport=transport-udp|' /etc/asterisk/pjsip.conf
    fi
}

case "$1" in
    add)
        [ -z "$2" ] || [ -z "$3" ] && { echo "Usage: manage-extensions add <ext> <password> [name] [can_call] [can_recv]"; exit 1; }
        add_extension "$2" "$3" "$4" "$5" "$6"
        ;;
    remove)
        [ -z "$2" ] && { echo "Usage: manage-extensions remove <ext>"; exit 1; }
        remove_extension "$2"
        ;;
    set-perms)
        [ -z "$2" ] && { echo "Usage: manage-extensions set-perms <ext> <can_call> <can_recv>"; exit 1; }
        set_perms "$2" "$3" "$4"
        ;;
    set-pass)
        [ -z "$2" ] || [ -z "$3" ] && { echo "Usage: manage-extensions set-pass <ext> <password>"; exit 1; }
        set_password "$2" "$3"
        ;;
    web-enable)
        [ -z "$2" ] && { echo "Usage: manage-extensions web-enable <ext|all>"; exit 1; }
        web_enable "$2"
        ;;
    web-secret)
        [ -z "$2" ] && { echo "Usage: manage-extensions web-secret <ext>"; exit 1; }
        web_secret "$2"
        ;;
    list)
        list_extensions
        ;;
    rebuild)
        _rebuild_conf
        echo "OK: pjsip.conf rebuilt (base + managed)."
        ;;
    *)
        echo "Usage: manage-extensions {add|remove|set-perms|set-pass|web-enable|web-secret|list|rebuild}"
        exit 1
        ;;
esac