#!/usr/bin/env bash
# =============================================================================
# V-Connect PBX — New Client Setup
# =============================================================================
# Run this ONCE on a freshly cloned client VM.
# Collects all client-specific settings interactively, writes .env,
# configures Apache, obtains SSL certificate, and updates Asterisk.
#
# Usage:
#   sudo bash /var/www/html/scripts/pbx-setup-client.sh
# =============================================================================

set -uo pipefail

APP_DIR="/var/www/html"
APACHE_CONF="/etc/apache2/sites-enabled/000-pbx.conf"
PJSIP_CONF="/etc/asterisk/pjsip.conf"

# ── LAN mode ──────────────────────────────────────────────────────────────────
# Desk-phones-only deployment on a private LAN with NO public hostname and NO
# SSL certificate: the GUI is served over plain HTTP on the LAN IP, and the
# browser webphone / mobile app (which need WSS + a cert) are intentionally not
# available. Pass --lan-mode (or --lan) to take this path. Without it the script
# runs the normal public-hostname + Let's Encrypt flow.
LAN_MODE=0
for arg in "$@"; do
    case "$arg" in
        --lan-mode|--lan) LAN_MODE=1 ;;
    esac
done

# Ensure all users (including www-data) can run git in this directory.
# --system writes to /etc/gitconfig so it applies to every user on the machine.
git config --system --add safe.directory "$APP_DIR" 2>/dev/null || true

# Ignore file-permission (mode) changes so the deploy's chmod on storage/
# never makes git see tracked files (e.g. storage/**/.gitignore) as modified
# and block future `git pull` updates.
git config core.fileMode false 2>/dev/null || true

# ── Colour helpers ────────────────────────────────────────────────────────────
info()    { echo -e "\n\033[0;34m▸ $*\033[0m"; }
success() { echo -e "\033[0;32m  ✓ $*\033[0m"; }
warn()    { echo -e "\033[0;33m  ⚠ $*\033[0m"; }
ask()     { echo -e "\033[0;36m$*\033[0m"; }
die()     { echo -e "\033[0;31mERROR: $*\033[0m"; exit 1; }

# Read a value from the existing .env if present
env_get() {
    local key="$1"
    grep -E "^${key}=" "$APP_DIR/.env" 2>/dev/null | head -1 | cut -d= -f2- | tr -d '"' || echo ""
}

prompt() {
    # prompt "Question" "default_value" → stores result in REPLY
    local question="$1"
    local default="${2:-}"
    if [[ -n "$default" ]]; then
        ask "$question [$default]:"
    else
        ask "$question:"
    fi
    read -rp "  > " REPLY
    if [[ -z "$REPLY" && -n "$default" ]]; then
        REPLY="$default"
    fi
}

prompt_password() {
    local question="$1"
    ask "$question:"
    read -rsp "  > " REPLY
    echo ""
}

# ── Permission check ──────────────────────────────────────────────────────────
[[ $EUID -ne 0 ]] && die "Must be run as root: sudo bash $0"

# ── Step -1: Pull the latest code BEFORE doing anything else ─────────────────
# THE most important hardening on a fresh clone. The golden image is a snapshot
# frozen at whatever commit it was built from — it can be many releases behind.
# If we deploy from that stale code, the box ships with old bugs and WITHOUT the
# self-healing fixes (missing-dialplan repair, recording-subroutine reseed) that
# protect it going forward. Pulling first guarantees every new client is set up
# with the current, known-good code regardless of how old the image is.
#
# After a successful pull that actually moved HEAD we re-exec this script, because
# bash reads the script file as it runs — continuing in-place after the file
# changed underneath us is unsafe. A one-shot env-var guard prevents a re-exec
# loop. A pull failure (offline / no Bitbucket key) is non-fatal: we warn and
# continue with the image's code rather than block a setup that has no network.
if [[ "${PBX_SETUP_PULLED:-0}" != "1" ]]; then
    export PBX_SETUP_PULLED=1
    info "Updating to the latest PBX code before setup"
    PULL_BEFORE=$(git -C "$APP_DIR" rev-parse HEAD 2>/dev/null || echo none)
    if ! git -C "$APP_DIR" diff --quiet 2>/dev/null; then
        warn "Local uncommitted changes present — stashing before pull"
        git -C "$APP_DIR" stash push -m "pbx-setup auto-stash $(date +%Y%m%d-%H%M%S)" >/dev/null 2>&1 || true
    fi
    if git -C "$APP_DIR" pull origin main --ff-only >/dev/null 2>&1; then
        PULL_AFTER=$(git -C "$APP_DIR" rev-parse HEAD 2>/dev/null || echo none)
        if [[ "$PULL_BEFORE" != "$PULL_AFTER" ]]; then
            success "Updated to $(git -C "$APP_DIR" rev-parse --short HEAD) — restarting setup with the new code"
            exec bash "$APP_DIR/scripts/pbx-setup-client.sh" "$@"
        fi
        success "Already on the latest code ($(git -C "$APP_DIR" rev-parse --short HEAD))"
    else
        warn "Could not pull latest code (offline or no Bitbucket key) — continuing with the code already on this image"
    fi
fi

# ── Banner ────────────────────────────────────────────────────────────────────
clear
echo ""
echo "╔═══════════════════════════════════════════════════════╗"
echo "║        V-Connect PBX — New Client Setup               ║"
echo "╚═══════════════════════════════════════════════════════╝"
echo ""
if [[ $LAN_MODE -eq 1 ]]; then
echo "  MODE: LAN / desk-phones-only (no domain, no SSL certificate)"
echo ""
echo "  This script will:"
echo "  • Collect client details"
echo "  • Write /var/www/html/.env (GUI over http://<lan-ip>)"
echo "  • Configure Apache for plain HTTP on the LAN"
echo "  • Update Asterisk SIP transport for desk phones"
echo "  • Run pbx:deploy (firewall, cron, sudoers, etc.)"
echo ""
echo "  NOTE: the browser webphone and mobile app are NOT available in"
echo "  LAN mode (they require HTTPS + a certificate). Desk phones only."
else
echo "  This script will:"
echo "  • Collect client details"
echo "  • Write /var/www/html/.env"
echo "  • Configure Apache + SSL certificate"
echo "  • Update Asterisk SIP transport"
echo "  • Run pbx:deploy (firewall, cron, sudoers, etc.)"
fi
echo ""

# ── Step 00: Regenerate machine identity (fresh clones only) ─────────────────
# An appliance is cloned from a golden image, so every clone starts with the
# SAME SSH host keys and machine-id. Left unchanged, all clients would share an
# SSH identity (security risk + "host key" confusion) and a machine-id (DHCP/
# journald clashes). We regenerate both ONCE per machine, guarded by a marker so
# re-running setup on a live client never churns the host keys (which would force
# every admin to clear "host key changed" warnings).
IDENTITY_MARKER="/var/lib/vconnect/.identity-regenerated"
info "Machine identity (unique per appliance)"
if [[ -f "$IDENTITY_MARKER" ]]; then
    success "Already regenerated on this machine — skipping"
else
    # SSH host keys
    rm -f /etc/ssh/ssh_host_*
    if command -v ssh-keygen >/dev/null 2>&1; then
        ssh-keygen -A >/dev/null 2>&1
    else
        dpkg-reconfigure openssh-server >/dev/null 2>&1
    fi
    # machine-id — remove BOTH /etc/machine-id AND the dbus copy FIRST, or
    # systemd-machine-id-setup will just COPY the (inherited) dbus id back and
    # the clone keeps the master's machine-id. Remove both → generate fresh →
    # re-link dbus to the new id.
    rm -f /etc/machine-id /var/lib/dbus/machine-id
    systemd-machine-id-setup >/dev/null 2>&1
    ln -sf /etc/machine-id /var/lib/dbus/machine-id
    mkdir -p "$(dirname "$IDENTITY_MARKER")"
    date -u +"regenerated %Y-%m-%dT%H:%M:%SZ" > "$IDENTITY_MARKER"
    systemctl restart ssh 2>/dev/null || systemctl restart sshd 2>/dev/null || true
    success "New SSH host keys + machine-id generated"
    warn "Your SSH client will warn that the host key changed on next connect — that is expected for a new appliance (clear the old known_hosts entry)."
fi
echo ""

# ── Step 0: Ensure composer dependencies are installed ───────────────────────
# Must happen before ANYTHING else — artisan won't work without vendor/
info "Checking PHP dependencies"
if [[ ! -f "$APP_DIR/vendor/autoload.php" ]] || [[ ! -d "$APP_DIR/vendor/myclabs" ]]; then
    warn "vendor/ incomplete — running composer install"
    if ! command -v composer &>/dev/null; then
        echo "  Installing composer..."
        php -r "copy('https://getcomposer.org/installer', '/tmp/composer-setup.php');"
        php /tmp/composer-setup.php --install-dir=/usr/local/bin --filename=composer --quiet
        rm -f /tmp/composer-setup.php
    fi
    composer install --no-dev --optimize-autoloader --no-interaction --quiet --working-dir="$APP_DIR"
    chown -R www-data:www-data "$APP_DIR/vendor"
    success "composer install complete"
else
    success "vendor/ OK"
fi

# ── Auto-detect public IP ─────────────────────────────────────────────────────
DETECTED_IP=$(curl -s --max-time 5 https://api.ipify.org 2>/dev/null \
    || curl -s --max-time 5 https://ifconfig.me 2>/dev/null \
    || hostname -I | awk '{print $1}')

# ── Collect client information ────────────────────────────────────────────────
info "Client Information"

# Pull existing values from .env if this is a clone/re-run
EXISTING_NAME=$(env_get "APP_NAME" | sed 's/ PBX$//')
EXISTING_URL=$(env_get "APP_URL" | sed 's|https://||')
EXISTING_IP=$(env_get "SIP_REALM")
EXISTING_MAIL_FROM=$(env_get "MAIL_FROM_ADDRESS")

prompt "Client / Company name (e.g. Acme Corp)" "${EXISTING_NAME:-}"
CLIENT_NAME="$REPLY"
[[ -z "$CLIENT_NAME" ]] && die "Company name cannot be empty."

WAN_IP=""
if [[ $LAN_MODE -eq 1 ]]; then
    # LAN deployment: collect the private LAN IP the PBX will live on. This is
    # what desk phones register to and what the GUI is reached on over HTTP.
    # Default to the box's ACTUAL primary LAN address — never the inherited
    # SIP_REALM (that's the master image's IP and would be wrong on a clone).
    LOCAL_IP_GUESS=$(hostname -I 2>/dev/null | awk '{print $1}')
    prompt "Server LAN IP address (e.g. 192.168.1.50)" "$LOCAL_IP_GUESS"
    LAN_IP="$REPLY"
    [[ -z "$LAN_IP" ]] && die "LAN IP cannot be empty."
    PBX_HOSTNAME="$LAN_IP"   # reused below for vhost ServerName / machine name
    PUBLIC_IP="$LAN_IP"

    echo ""
    echo "  If the SIP trunk is hosted on the internet and your router does NOT"
    echo "  forward SIP/RTP to this PBX, enter your site's public/WAN IP so"
    echo "  Asterisk can advertise it to the trunk. Leave blank if your trunk"
    echo "  provider handles NAT (symmetric RTP) — most do."
    prompt "Public/WAN IP for the SIP trunk (optional)" ""
    WAN_IP="$REPLY"

    prompt "Admin email address (for system emails)" "${EXISTING_MAIL_FROM:-admin@example.com}"
    ADMIN_EMAIL="$REPLY"

    prompt "From email address for PBX notifications" "${EXISTING_MAIL_FROM:-pbx@example.com}"
    MAIL_FROM="$REPLY"
else
    prompt "PBX hostname — DNS must already point here (e.g. pbx.acme.com)" "${EXISTING_URL:-}"
    PBX_HOSTNAME="$REPLY"
    [[ -z "$PBX_HOSTNAME" ]] && die "Hostname cannot be empty."

    prompt "Server public IP" "${EXISTING_IP:-$DETECTED_IP}"
    PUBLIC_IP="$REPLY"
    [[ -z "$PUBLIC_IP" ]] && die "IP address cannot be empty."

    prompt "Admin email address (for SSL cert + system emails)" "${EXISTING_MAIL_FROM:-admin@${PBX_HOSTNAME}}"
    ADMIN_EMAIL="$REPLY"

    prompt "From email address for PBX notifications" "${EXISTING_MAIL_FROM:-pbx@${PBX_HOSTNAME}}"
    MAIL_FROM="$REPLY"
fi

info "Database & Security"

EXISTING_DB_PASS=$(env_get "DB_PASSWORD")
EXISTING_AMI_PASS=$(env_get "AMI_PASS")

# Generate a strong random password if they just press Enter
GEN_DB_PASS=$(openssl rand -base64 16 | tr -dc 'A-Za-z0-9' | head -c 20)
prompt "Database password (leave blank to auto-generate)" "${EXISTING_DB_PASS:-}"
if [[ -z "$REPLY" ]]; then
    DB_PASS="$GEN_DB_PASS"
    echo "  → Auto-generated: $DB_PASS"
else
    DB_PASS="$REPLY"
fi

GEN_AMI_PASS=$(openssl rand -base64 16 | tr -dc 'A-Za-z0-9' | head -c 20)
prompt "Asterisk AMI password (leave blank to auto-generate)" "${EXISTING_AMI_PASS:-}"
if [[ -z "$REPLY" ]]; then
    AMI_PASS="$GEN_AMI_PASS"
    echo "  → Auto-generated: $AMI_PASS"
else
    AMI_PASS="$REPLY"
fi

info "Mail Settings (for voicemail emails, missed call alerts)"
echo "  Leave blank to use the default OpenV SocketLabs account."
echo ""

EXISTING_MAIL_HOST=$(env_get "MAIL_HOST")
EXISTING_MAIL_PORT=$(env_get "MAIL_PORT")
EXISTING_MAIL_USER=$(env_get "MAIL_USERNAME")

prompt "SMTP host" "${EXISTING_MAIL_HOST:-smtp.socketlabs.com}"
MAIL_HOST="$REPLY"

prompt "SMTP port" "${EXISTING_MAIL_PORT:-587}"
MAIL_PORT="$REPLY"

prompt "SMTP username" "${EXISTING_MAIL_USER:-server19903}"
MAIL_USER="$REPLY"

prompt_password "SMTP password (leave blank to use default)"
if [[ -z "$REPLY" ]]; then
    MAIL_PASS="e3BFo9t6REg72JmZp48Q"
    echo "  → Using default"
else
    MAIL_PASS="$REPLY"
fi

# ── Confirmation ──────────────────────────────────────────────────────────────
echo ""
echo "╔═══════════════════════════════════════════════════════╗"
echo "║  Review — about to apply these settings               ║"
echo "╠═══════════════════════════════════════════════════════╣"
printf "║  %-20s %-34s ║\n" "Company:"     "$CLIENT_NAME"
if [[ $LAN_MODE -eq 1 ]]; then
printf "║  %-20s %-34s ║\n" "Mode:"        "LAN / HTTP (desk phones only)"
printf "║  %-20s %-34s ║\n" "LAN IP:"      "$PBX_HOSTNAME"
[[ -n "$WAN_IP" ]] && printf "║  %-20s %-34s ║\n" "Trunk WAN IP:" "$WAN_IP"
else
printf "║  %-20s %-34s ║\n" "Hostname:"    "$PBX_HOSTNAME"
printf "║  %-20s %-34s ║\n" "Public IP:"   "$PUBLIC_IP"
fi
printf "║  %-20s %-34s ║\n" "Admin email:" "$ADMIN_EMAIL"
printf "║  %-20s %-34s ║\n" "Mail from:"   "$MAIL_FROM"
printf "║  %-20s %-34s ║\n" "DB password:" "${DB_PASS:0:6}…"
printf "║  %-20s %-34s ║\n" "AMI password:" "${AMI_PASS:0:6}…"
echo "╚═══════════════════════════════════════════════════════╝"
echo ""
ask "Apply these settings? (y/N)"
read -rp "  > " CONFIRM
[[ "${CONFIRM,,}" != "y" ]] && { echo "Aborted."; exit 0; }

# ── 1. Set machine hostname ───────────────────────────────────────────────────
info "Setting machine hostname"

# Derive a clean short hostname.
#  • Public mode: from the PBX hostname (pbx.acme.com → pbx-acme).
#  • LAN mode: PBX_HOSTNAME is an IP, so use a slug of the company name instead
#    (e.g. "Acme Corp" → "acme-corp") to avoid an ugly "192-168" machine name.
if [[ $LAN_MODE -eq 1 ]]; then
    SHORT_HOSTNAME=$(echo "$CLIENT_NAME" | tr '[:upper:]' '[:lower:]' | tr -cs 'a-z0-9' '-' | sed 's/^-*//;s/-*$//' | cut -c1-30)
    [[ -z "$SHORT_HOSTNAME" ]] && SHORT_HOSTNAME="vconnect-pbx"
else
    SHORT_HOSTNAME=$(echo "$PBX_HOSTNAME" | awk -F. '{
        if (NF >= 3) print $1"-"$2
        else if (NF == 2) print $1
        else print $1
    }')
fi

hostnamectl set-hostname "$SHORT_HOSTNAME"
# Update /etc/hosts so localhost resolves correctly
if grep -q "127.0.1.1" /etc/hosts; then
    sed -i "s/^127\.0\.1\.1.*/127.0.1.1\t${SHORT_HOSTNAME}/" /etc/hosts
else
    echo "127.0.1.1	${SHORT_HOSTNAME}" >> /etc/hosts
fi
success "Machine hostname set to: $SHORT_HOSTNAME"

# ── 2. Write .env ─────────────────────────────────────────────────────────────
info "Writing .env"

# Generate a fresh APP_KEY
APP_KEY=$(php "$APP_DIR/artisan" key:generate --show --no-ansi 2>/dev/null | tr -d '\r\n')
# Fallback: generate manually if artisan fails for any reason
if [[ -z "$APP_KEY" || "$APP_KEY" != base64:* ]]; then
    APP_KEY="base64:$(openssl rand -base64 32)"
fi

# LAN mode serves the GUI over plain HTTP, so the session cookie must NOT be
# secure-only (a secure cookie is never sent over HTTP → you can't log in), and
# there is no WSS webphone endpoint. Public mode keeps HTTPS + WSS.
if [[ $LAN_MODE -eq 1 ]]; then
    APP_URL_VAL="http://${PBX_HOSTNAME}"
    SECURE_COOKIE_VAL="false"
    WSS_URL_VAL=""
else
    APP_URL_VAL="https://${PBX_HOSTNAME}"
    SECURE_COOKIE_VAL="true"
    WSS_URL_VAL="wss://${PBX_HOSTNAME}/ws"
fi

cat > "$APP_DIR/.env" << ENV
APP_NAME="${CLIENT_NAME} PBX"
APP_ENV=production
APP_KEY=${APP_KEY}
APP_DEBUG=false
APP_URL=${APP_URL_VAL}
APP_LOCALE=en
APP_FALLBACK_LOCALE=en
APP_FAKER_LOCALE=en_US
APP_TIMEZONE=Africa/Johannesburg
APP_MAINTENANCE_DRIVER=file
PHP_CLI_SERVER_WORKERS=4
BCRYPT_ROUNDS=12

LOG_CHANNEL=stack
LOG_STACK=single
LOG_DEPRECATIONS_CHANNEL=null
LOG_LEVEL=error

WEBRTC_WSS_URL=${WSS_URL_VAL}
SIP_REALM=${PUBLIC_IP}
SIP_DEFAULT_EXTENSION=

DB_CONNECTION=mysql
DB_HOST=127.0.0.1
DB_PORT=3306
DB_DATABASE=asterisk
DB_USERNAME=asterisk
DB_PASSWORD=${DB_PASS}
DB_SOCKET=
DB_CHARSET=utf8mb4
DB_COLLATION=utf8mb4_unicode_ci

AMI_HOST=127.0.0.1
AMI_PORT=5038
AMI_USER=admin
AMI_PASS=${AMI_PASS}

SESSION_DRIVER=file
SESSION_DOMAIN=
SESSION_COOKIE=pbx_session
SESSION_SECURE_COOKIE=${SECURE_COOKIE_VAL}
SESSION_SAME_SITE=lax
SESSION_PATH=/
SESSION_LIFETIME=120

CACHE_STORE=database
QUEUE_CONNECTION=database
FILESYSTEM_DISK=local
BROADCAST_CONNECTION=log

MAIL_MAILER=smtp
MAIL_HOST=${MAIL_HOST}
MAIL_PORT=${MAIL_PORT}
MAIL_USERNAME=${MAIL_USER}
MAIL_PASSWORD=${MAIL_PASS}
MAIL_ENCRYPTION=tls
MAIL_FROM_ADDRESS="${MAIL_FROM}"
MAIL_FROM_NAME="${CLIENT_NAME} PBX"

REDIS_CLIENT=phpredis
REDIS_HOST=127.0.0.1
REDIS_PASSWORD=null
REDIS_PORT=6379

RECORDINGS_PATH=/var/spool/asterisk/monitor
PBX_ANSWER_THRESHOLD=3

VITE_APP_NAME="${CLIENT_NAME} PBX"
ENV

chown www-data:www-data "$APP_DIR/.env"
chmod 640 "$APP_DIR/.env"
success ".env written"

# ── 2. Update MariaDB password ────────────────────────────────────────────────
info "Updating database password"

mysql -u root -e "ALTER USER 'asterisk'@'localhost' IDENTIFIED BY '${DB_PASS}'; FLUSH PRIVILEGES;" 2>/dev/null \
    && success "MariaDB asterisk user password updated" \
    || warn "Could not update MariaDB password — do it manually if needed"

# ── 3. Update Asterisk AMI password ──────────────────────────────────────────
info "Updating Asterisk AMI password"

AMI_CONF="/etc/asterisk/manager.conf"
if [[ -f "$AMI_CONF" ]]; then
    sed -i "s|^secret\s*=.*|secret = ${AMI_PASS}|" "$AMI_CONF"
    success "AMI password updated in $AMI_CONF"
else
    warn "$AMI_CONF not found — skipping"
fi

# ── 4. Apache vhost ───────────────────────────────────────────────────────────
info "Writing Apache vhost"

if [[ $LAN_MODE -eq 1 ]]; then
    # LAN mode: serve the GUI directly over HTTP on the LAN — no HTTPS redirect,
    # no certificate, no WSS proxy (the browser webphone is not available here).
    cat > "$APACHE_CONF" << APACHECONF
<VirtualHost *:80>
    ServerName ${PBX_HOSTNAME}

    DocumentRoot ${APP_DIR}/public
    DirectoryIndex index.php index.html

    <Directory ${APP_DIR}/public>
        Options -Indexes +FollowSymLinks
        AllowOverride All
        Require all granted
    </Directory>

    ErrorLog  \${APACHE_LOG_DIR}/pbx-error.log
    CustomLog \${APACHE_LOG_DIR}/pbx-access.log combined
</VirtualHost>
APACHECONF
    success "HTTP (LAN) vhost written — GUI at http://${PBX_HOSTNAME}"
else
    cat > "$APACHE_CONF" << APACHECONF
<VirtualHost *:80>
    ServerName ${PBX_HOSTNAME}
    ServerAlias ${PUBLIC_IP}

    DocumentRoot ${APP_DIR}/public
    DirectoryIndex index.php index.html

    <Directory ${APP_DIR}/public>
        Options -Indexes +FollowSymLinks
        AllowOverride All
        Require all granted
    </Directory>

    Alias /.well-known/acme-challenge/ ${APP_DIR}/.well-known/acme-challenge/
    <Directory ${APP_DIR}/.well-known/acme-challenge/>
        Options None
        AllowOverride None
        Require all granted
    </Directory>

    RewriteEngine On
    RewriteCond %{REMOTE_ADDR} !^127\\.0\\.0\\.1\$
    RewriteCond %{REQUEST_URI} !^/\\.well-known/acme-challenge/
    RewriteRule ^(.*)\$ https://%{HTTP_HOST}\$1 [R=301,L]
</VirtualHost>
APACHECONF

    success "HTTP vhost written"
fi

# ── 5. SSL certificate ────────────────────────────────────────────────────────
if [[ $LAN_MODE -eq 1 ]]; then
    info "Skipping SSL certificate (LAN mode — GUI runs over HTTP)"
else
info "Obtaining SSL certificate for ${PBX_HOSTNAME}"

systemctl reload apache2

CERT_PATH="/etc/letsencrypt/live/${PBX_HOSTNAME}"

if certbot certonly \
    --webroot \
    --webroot-path="${APP_DIR}/public" \
    --domain "${PBX_HOSTNAME}" \
    --non-interactive \
    --agree-tos \
    --email "${ADMIN_EMAIL}" \
    --no-eff-email 2>&1; then
    success "Let's Encrypt certificate issued"
else
    warn "Let's Encrypt failed — using self-signed certificate"
    warn "Re-run: certbot certonly --webroot -w ${APP_DIR}/public -d ${PBX_HOSTNAME}"
    CERT_PATH="/etc/ssl/pbx-${PBX_HOSTNAME}"
    mkdir -p "$CERT_PATH"
    openssl req -x509 -nodes -days 365 -newkey rsa:2048 \
        -keyout "${CERT_PATH}/privkey.pem" \
        -out    "${CERT_PATH}/fullchain.pem" \
        -subj "/CN=${PBX_HOSTNAME}" 2>/dev/null
    success "Self-signed certificate created"
fi

# ── 6. HTTPS vhost ────────────────────────────────────────────────────────────
info "Writing HTTPS vhost"

cat >> "$APACHE_CONF" << SSLCONF

<VirtualHost *:443>
    ServerName ${PBX_HOSTNAME}
    ServerAlias ${PUBLIC_IP}

    DocumentRoot ${APP_DIR}/public
    DirectoryIndex index.php index.html

    <Directory ${APP_DIR}/public>
        Options -Indexes +FollowSymLinks
        AllowOverride All
        Require all granted
    </Directory>

    SSLEngine on
    SSLCertificateFile      ${CERT_PATH}/fullchain.pem
    SSLCertificateKeyFile   ${CERT_PATH}/privkey.pem

    SSLProxyEngine on
    SSLProxyVerify none
    SSLProxyCheckPeerCN off
    SSLProxyCheckPeerName off

    RewriteEngine on
    RewriteCond %{HTTP:Upgrade} websocket [NC]
    RewriteCond %{HTTP:Connection} upgrade [NC]
    RewriteRule ^/ws\$ wss://127.0.0.1:8089/ws [P,L]

    ProxyPass        /ws wss://127.0.0.1:8089/ws upgrade=websocket timeout=86400 keepalive=On
    ProxyPassReverse /ws wss://127.0.0.1:8089/ws
    ProxyTimeout 86400

    ErrorLog  \${APACHE_LOG_DIR}/pbx-ssl-error.log
    CustomLog \${APACHE_LOG_DIR}/pbx-ssl-access.log combined
</VirtualHost>
SSLCONF

success "HTTPS vhost written"
fi  # end public-mode SSL + HTTPS vhost (skipped in LAN mode)

# ── 7. Asterisk PJSIP transport ───────────────────────────────────────────────
info "Updating Asterisk PJSIP transport"

if [[ -f "$PJSIP_CONF" ]]; then
    SUBNET=$(echo "$PUBLIC_IP" | awk -F. '{print $1"."$2"."$3".0/24"}')

    if [[ $LAN_MODE -eq 1 && -z "$WAN_IP" ]]; then
        # Pure LAN, no NAT and no hosted-trunk WAN IP given: do NOT pin an
        # external address. Asterisk then advertises its real interface IP, so a
        # DHCP→static (or any) IP change needs no pjsip edit. Comment out any
        # external_* inherited from the golden image. (When a hosted SIP trunk is
        # added later, set external_*_address to the site's PUBLIC/WAN IP then.)
        sed -i 's|^external_signaling_address=.*|;external_signaling_address=|g' "$PJSIP_CONF"
        sed -i 's|^external_media_address=.*|;external_media_address=|g'         "$PJSIP_CONF"
        EXT_DESC="(none — uses live interface IP)"
    else
        # Public mode, or LAN with a hosted-trunk WAN IP: advertise that address.
        EXT_IP=$([[ $LAN_MODE -eq 1 ]] && echo "$WAN_IP" || echo "$PUBLIC_IP")
        sed -i "s|^;*external_signaling_address=.*|external_signaling_address=${EXT_IP}|g" "$PJSIP_CONF"
        sed -i "s|^;*external_media_address=.*|external_media_address=${EXT_IP}|g"         "$PJSIP_CONF"
        EXT_DESC="$EXT_IP"
    fi

    # local_net = the LAN subnet (stable across IP changes within the subnet).
    sed -i "s|^local_net=.*|local_net=${SUBNET}|g" "$PJSIP_CONF"
    success "PJSIP external IP → ${EXT_DESC}, local_net → ${SUBNET}"
else
    warn "$PJSIP_CONF not found — skipping"
fi

# ── 8. Reload services ────────────────────────────────────────────────────────
info "Reloading services"

apache2ctl configtest 2>&1 | grep -v "^Syntax OK" || true
systemctl reload apache2
success "Apache reloaded"

asterisk -rx "pjsip reload" 2>/dev/null \
    || sudo -n asterisk -rx "pjsip reload" 2>/dev/null \
    || warn "Reload PJSIP manually: asterisk -rx 'pjsip reload'"
success "Asterisk PJSIP reloaded"

# ── 8. Factory reset (optional) ──────────────────────────────────────────────
info "Database reset"
echo "  This machine may contain data from a previous client (clone)."
echo "  A factory reset wipes all extensions, CDR, queues, users etc."
echo "  and seeds a fresh admin@example.com / 0penV70penV7 login."
echo ""
ask "Wipe database and start fresh? (Y/n)"
read -rp "  > " RESET_CONFIRM
if [[ "${RESET_CONFIRM,,}" != "n" ]]; then
    echo ""
    warn "Running factory reset — this is irreversible."
    if php "$APP_DIR/scripts/php/factory-reset.php" --yes-i-am-sure --keep-media; then
        success "Database wiped and seeded"
    else
        warn "Factory reset failed — run manually after setup:"
        warn "  sudo php $APP_DIR/scripts/php/factory-reset.php --yes-i-am-sure"
    fi
else
    success "Database left as-is"
fi

# ── 8c. Purge previous client's recordings, voicemail & traces ────────────────
# A cloned image still carries the SOURCE machine's call recordings, voicemail
# messages, database backups and logs on disk — the factory reset only wipes the
# DATABASE, not these files. Leaving them on a new client's box is a privacy
# breach. This step deletes them, but ONLY on an explicit typed "yes" (a blank
# Enter is treated as "no" so it can never be wiped by accident).
info "Remove previous client's recordings & private data"
echo "  A re-imaged machine still has the PREVIOUS client's data on disk:"
echo "    • Call recordings   (/var/spool/asterisk/monitor)"
echo "    • Voicemail messages (/var/spool/asterisk/voicemail)"
echo "    • Database backups   (/var/backups/pbx)"
echo "    • Asterisk + app logs"
echo ""
echo "  This is private call data and should be removed before go-live."
echo "  This cannot be undone."
echo ""
ask "Delete all of the above? Type 'yes' to confirm (anything else keeps it):"
read -rp "  > " PURGE_CONFIRM
if [[ "$PURGE_CONFIRM" == "yes" || "$PURGE_CONFIRM" == "YES" ]]; then
    echo ""
    warn "Deleting previous client's private data — irreversible."

    # Call recordings (files only — keep the directory + its permissions)
    if [[ -d /var/spool/asterisk/monitor ]]; then
        find /var/spool/asterisk/monitor -mindepth 1 -delete 2>/dev/null || true
        success "Call recordings deleted"
    fi

    # Voicemail messages
    if [[ -d /var/spool/asterisk/voicemail ]]; then
        find /var/spool/asterisk/voicemail -mindepth 1 -delete 2>/dev/null || true
        success "Voicemail messages deleted"
    fi

    # Transcription temp artifacts
    if [[ -d "$APP_DIR/storage/app/transcriptions" ]]; then
        find "$APP_DIR/storage/app/transcriptions" -mindepth 1 -delete 2>/dev/null || true
    fi

    # Pre-update database backups (full dumps of the previous client's data)
    if [[ -d /var/backups/pbx ]]; then
        find /var/backups/pbx -type f -delete 2>/dev/null || true
        success "Old database backups deleted"
    fi

    # Truncate logs (call traces / PII from the source machine)
    : > /var/log/asterisk/full 2>/dev/null || true
    : > /var/log/asterisk/messages 2>/dev/null || true
    find "$APP_DIR/storage/logs" -type f -name '*.log' -exec truncate -s 0 {} \; 2>/dev/null || true
    success "Logs truncated"

    # Stale AstDB keys from the previous client's extensions/trunks. pbx:deploy +
    # pbx:reconcile-astdb (run later) rebuild the families for THIS client from
    # the freshly-seeded DB, so clearing them here just removes orphaned keys.
    for fam in EXTROUTE TRUNKSTATUS CFNA CFB CF DND REC BLF BOSS BOSS_SEC BOSS_WL; do
        asterisk -rx "database deltree ${fam}" >/dev/null 2>&1 || true
    done
    success "Stale routing keys cleared from AstDB"
else
    warn "Previous client's recordings & data KEPT — clear them before go-live if this was a clone"
fi

# ── 9. Laravel caches ─────────────────────────────────────────────────────────
info "Rebuilding Laravel caches"
# Fix ownership first — some earlier steps may have run as root
chown -R www-data:www-data "$APP_DIR/storage" "$APP_DIR/bootstrap/cache"
chmod -R 775 "$APP_DIR/storage" "$APP_DIR/bootstrap/cache"
success "Storage permissions fixed"
sudo -u www-data php "$APP_DIR/artisan" config:cache --quiet
sudo -u www-data php "$APP_DIR/artisan" route:cache  --quiet
sudo -u www-data php "$APP_DIR/artisan" view:clear   --quiet
success "Caches rebuilt"

# ── 9b. Database migrations ───────────────────────────────────────────────────
# MUST run before pbx:deploy. Fresh appliances are cloned from a base image
# whose schema may predate recent tables (asterisk_dialplan) and seed data
# (recording subroutines, system feature codes). Without this, internal calls,
# call recording, and feature codes silently fail on a new client. All
# migrations are idempotent (guarded with hasTable / per-row checks), so this
# is safe to run on every setup and re-run.
info "Running database migrations"
if sudo -u www-data php "$APP_DIR/artisan" migrate --force; then
    success "Migrations applied"
else
    warn "Migrations reported an error — review above before going live"
fi

# ── 10. pbx:deploy ────────────────────────────────────────────────────────────
info "Running pbx:deploy"
php "$APP_DIR/artisan" pbx:deploy

# ── 10b. Post-setup health verification (calls + recording) ──────────────────
# Final gate before we declare the box ready. These two checks cover the EXACT
# failures that have shipped on cloned boxes before:
#   (1) endpoints that exist but have NO from-internal dialplan → every call
#       fails with "Extension does not exist in context from-internal";
#   (2) an EMPTY subStartRec subroutine → calls connect but nothing records.
# Both are meant to be handled by pbx:deploy, but here we VERIFY and, if anything
# is still wrong, run the targeted self-heal and re-check — so a broken box can
# never pass setup silently. HEALTH_OK is surfaced loudly in the summary.
info "Verifying call routing and recording are ready"

HEALTH_OK=0
verify_health() {
    MISSING_DP=$(mysql -N -u root asterisk -e "
        SELECT COUNT(*) FROM ps_endpoints e
        LEFT JOIN asterisk_dialplan d
          ON d.exten = e.id AND d.context='from-internal'
        WHERE e.id REGEXP '^[0-9]+\$'
          AND CAST(e.id AS UNSIGNED) BETWEEN 1000 AND 9999
          AND d.exten IS NULL;" 2>/dev/null | tr -d '[:space:]')
    SUBREC_ROWS=$(mysql -N -u root asterisk -e "
        SELECT COUNT(*) FROM asterisk_dialplan WHERE context='subStartRec';" 2>/dev/null | tr -d '[:space:]')
    MISSING_DP=${MISSING_DP:-unknown}
    SUBREC_ROWS=${SUBREC_ROWS:-0}
}

verify_health
if [[ "$MISSING_DP" != "0" || "${SUBREC_ROWS:-0}" -lt 6 ]]; then
    warn "Health gaps found (extensions missing dialplan: $MISSING_DP, subStartRec rows: $SUBREC_ROWS) — running self-heal"
    php "$APP_DIR/artisan" pbx:sync-extension-dialplan --force >/dev/null 2>&1 || true
    php "$APP_DIR/artisan" pbx:audit-recording --fix >/dev/null 2>&1 || true
    asterisk -rx "dialplan reload" >/dev/null 2>&1 \
        || sudo -n asterisk -rx "dialplan reload" >/dev/null 2>&1 || true
    verify_health
fi

if [[ "$MISSING_DP" == "0" && "${SUBREC_ROWS:-0}" -ge 6 ]]; then
    success "Call routing OK (no extensions missing dialplan), recording subroutine present (${SUBREC_ROWS} rows)"
    HEALTH_OK=1
else
    warn "Health check STILL failing (missing dialplan: $MISSING_DP, subStartRec rows: $SUBREC_ROWS)"
fi

# ── Summary ───────────────────────────────────────────────────────────────────
echo ""
echo "╔═══════════════════════════════════════════════════════╗"
echo "║  Setup complete!                                      ║"
echo "╠═══════════════════════════════════════════════════════╣"
printf "║  %-20s %-34s ║\n" "Hostname:"    "$SHORT_HOSTNAME"
if [[ $LAN_MODE -eq 1 ]]; then
printf "║  %-20s %-34s ║\n" "GUI:"         "http://${PBX_HOSTNAME}"
printf "║  %-20s %-34s ║\n" "Mode:"        "LAN / desk phones only"
else
printf "║  %-20s %-34s ║\n" "GUI:"         "https://${PBX_HOSTNAME}"
printf "║  %-20s %-34s ║\n" "WSS:"         "wss://${PBX_HOSTNAME}/ws"
fi
printf "║  %-20s %-34s ║\n" "Login:"       "admin@example.com"
printf "║  %-20s %-34s ║\n" "Password:"    "0penV70penV7"
echo "╠═══════════════════════════════════════════════════════╣"
echo "║  IMPORTANT: Change the admin password on first login  ║"
echo "╚═══════════════════════════════════════════════════════╝"
echo ""
if [[ "${HEALTH_OK:-0}" == "1" ]]; then
    echo -e "\033[0;32m  ✓ Health check passed — call routing and recording are ready.\033[0m"
else
    echo -e "\033[0;31m  ✗ HEALTH CHECK FAILED — DO NOT GO LIVE YET.\033[0m"
    echo -e "\033[0;31m    Calls and/or recording may not work on this box.\033[0m"
    echo "    Run these, then re-check, before handing the box over:"
    echo "      sudo bash ${APP_DIR}/scripts/pbx-update.sh"
    echo "      php ${APP_DIR}/artisan pbx:sync-extension-dialplan --force"
    echo "      php ${APP_DIR}/artisan pbx:audit-recording --fix"
fi
echo ""
if [[ $LAN_MODE -eq 1 ]]; then
echo "  LAN mode notes:"
echo "    • Browser webphone and mobile app are not available (no SSL)."
echo "    • Reach the GUI from the LAN at http://${PBX_HOSTNAME}"
echo "    • To add webphone/mobile later: assign a domain + cert and"
echo "      re-run this setup WITHOUT --lan-mode."
echo ""
fi
echo "  Saved settings:"
echo "    DB password : ${DB_PASS}"
echo "    AMI password: ${AMI_PASS}"
echo ""
echo "  To update this machine in future:"
echo "    sudo bash ${APP_DIR}/scripts/pbx-update.sh"
echo ""
