#!/usr/bin/env bash
# ============================================================
#  kahf-install.sh — Install Kahf Family Safety on OpenWrt
# ============================================================
#
#  What it does:
#    1. Guides you through connecting to your OpenWrt router
#    2. Verifies it really is OpenWrt
#    3. Downloads the Kahf auto-update agent from s3.kahf.co
#    4. Runs it once to install OAF + family-safe packages
#    5. Sets up a nightly cron job for automatic updates
#
#  Usage:
#    Interactive (recommended):  ./kahf-install.sh
#    Non-interactive:            ./kahf-install.sh --ip 192.168.1.1 --user root --password mypass
#    Dev channel:                ./kahf-install.sh --channel dev

# !! IMPORTANT: Bump INSTALLER_VERSION every time this file is modified.
#    It is printed on startup so users can confirm which version they're running.
#    Match it to the git tag you will push (e.g. dev-v1.0.8 → "1.0.8").
INSTALLER_VERSION="1.0.10"

set -euo pipefail

# When the script is run via `curl | bash` stdin is the pipe carrying the
# script itself, not the keyboard.  Every interactive read must use /dev/tty
# explicitly so it always reads from the terminal.
TTY=/dev/tty

# ── Colours ──────────────────────────────────────────────────────────────────
if [ -t 1 ] && tput colors &>/dev/null && [ "$(tput colors)" -ge 8 ]; then
    C_RESET='\033[0m'
    C_BOLD='\033[1m'
    C_BLUE='\033[1;34m'
    C_GREEN='\033[1;32m'
    C_YELLOW='\033[1;33m'
    C_RED='\033[1;31m'
    C_CYAN='\033[1;36m'
    C_DIM='\033[2m'
else
    C_RESET=''; C_BOLD=''; C_BLUE=''; C_GREEN=''
    C_YELLOW=''; C_RED=''; C_CYAN=''; C_DIM=''
fi

info()   { printf "${C_BLUE}[*]${C_RESET} %s\n" "$*"; }
ok()     { printf "${C_GREEN}[✓]${C_RESET} %s\n" "$*"; }
warn()   { printf "${C_YELLOW}[!]${C_RESET} %s\n" "$*"; }
err()    { printf "${C_RED}[✗]${C_RESET} %s\n" "$*" >&2; }
die()    { err "$*"; echo "" >&2; exit 1; }
# Use %b (interpret backslash escapes) so dim/banner work correctly when
# colour vars contain literal \033 sequences (e.g. when not a tty).
dim()    { printf "%b\n" "${C_DIM}$*${C_RESET}"; }
banner() { printf "%b\n" "${C_BOLD}$*${C_RESET}"; }
step()   {
    local n="$1" total="$2" title="$3"
    printf "\n${C_BOLD}${C_CYAN}──── Step %s of %s — %s ────${C_RESET}\n" \
        "${n}" "${total}" "${title}"
}

# ── Argument parsing (non-interactive mode) ───────────────────────────────────
ROUTER_IP=""
ROUTER_USER=""
ROUTER_PASS=""
CHANNEL="dev"
SSH_PORT=22
SKIP_CRON=0
DRY_RUN=0
NON_INTERACTIVE=0

while [ $# -gt 0 ]; do
    case "$1" in
        --ip|-i)       ROUTER_IP="$2";   NON_INTERACTIVE=1; shift 2 ;;
        --user|-u)     ROUTER_USER="$2"; shift 2 ;;
        --password|-p) ROUTER_PASS="$2"; shift 2 ;;
        --port)        SSH_PORT="$2";    shift 2 ;;
        --channel|-c)  CHANNEL="$2";     shift 2 ;;
        --skip-cron)   SKIP_CRON=1;      shift ;;
        --dry-run)     DRY_RUN=1;        shift ;;
        --help|-h)
            sed -n '/^#/!q;s/^# \{0,1\}//p' "$0"
            exit 0
            ;;
        *) die "Unknown option: $1  (run with --help for usage)" ;;
    esac
done

case "${CHANNEL}" in
    dev)  S3_BASE="https://s3.kahf.co/router-dev" ;;
    prod) S3_BASE="https://s3.kahf.co/router-prod" ;;
    *)    die "Unknown channel '${CHANNEL}'. Use 'prod' or 'dev'." ;;
esac
UPDATE_SCRIPT_URL="${S3_BASE}/oaf-update.sh"
INSTALL_PATH="/usr/sbin/oaf-update.sh"
CRON_FILE="/etc/crontabs/root"
CRON_ENTRY="0 3 * * * /usr/sbin/oaf-update.sh >> /var/log/oaf-update.log 2>&1"
# SSH_CONN_OPTS: base connection options shared by all SSH calls.
# SSH_BASE_OPTS adds -n (redirect ssh stdin from /dev/null) so that ssh never
# reads from the caller's stdin.  This is critical when the script is run via
# "curl | bash": bash's stdin IS the script pipe, and without -n the ssh client
# steals bytes from it, silently corrupting bash's execution after the call.
SSH_CONN_OPTS="-o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null -o ConnectTimeout=10 -o LogLevel=ERROR"
SSH_BASE_OPTS="${SSH_CONN_OPTS} -n"

# ── Banner ────────────────────────────────────────────────────────────────────
# Only clear the screen when running interactively from a terminal.
# When piped (curl | bash) clearing would discard the brew install output above.
[ -t 0 ] && clear 2>/dev/null || true
echo ""
banner "╔══════════════════════════════════════════════════════════════╗"
banner "║        Kahf Family Safety — OpenWrt Installer               ║"
banner "║   Installs OAF + family-safe packages via auto-update agent ║"
banner "╚══════════════════════════════════════════════════════════════╝"
dim    "  version ${INSTALLER_VERSION}"
echo ""
[ "${DRY_RUN}" = "1" ] && warn "DRY RUN — no changes will be made to the router"

# ── Dependency check (silent, done once upfront) ──────────────────────────────
if ! command -v ssh &>/dev/null; then
    die "ssh not found. Please install OpenSSH and try again."
fi

# ── Helper: auto-detect the default gateway (likely the router) ──────────────
_detect_gateway() {
    local gw=""
    # Linux
    if command -v ip &>/dev/null; then
        gw=$(ip route show default 2>/dev/null | awk '/default/{print $3; exit}')
    fi
    # macOS / BSD
    if [ -z "${gw}" ] && command -v route &>/dev/null; then
        gw=$(route -n get default 2>/dev/null | awk '/gateway/{print $2; exit}')
    fi
    # Last resort: netstat
    if [ -z "${gw}" ] && command -v netstat &>/dev/null; then
        gw=$(netstat -rn 2>/dev/null | awk '/^(default|0\.0\.0\.0)/{print $2; exit}')
    fi
    echo "${gw:-192.168.1.1}"
}

# ── SSH wrappers ──────────────────────────────────────────────────────────────
_ssh_cmd() {
    # Uses SSH_BASE_OPTS which includes -n: stdin is /dev/null.
    if command -v sshpass &>/dev/null && [ -n "${ROUTER_PASS}" ]; then
        SSHPASS="${ROUTER_PASS}" sshpass -e \
            ssh ${SSH_BASE_OPTS} -p "${SSH_PORT}" "${ROUTER_USER}@${ROUTER_IP}" "$@"
    else
        ssh ${SSH_BASE_OPTS} -o BatchMode=yes \
            -p "${SSH_PORT}" "${ROUTER_USER}@${ROUTER_IP}" "$@"
    fi
}
ssh_run() { _ssh_cmd "$@" 2>&1; }

_ssh_cmd_stdin() {
    # Like _ssh_cmd but uses SSH_CONN_OPTS (no -n) so the caller's stdin
    # (e.g. a here-string) is forwarded to the remote command.
    # Only use this when you explicitly pipe a script: ssh_run_stdin sh <<< "$script"
    if command -v sshpass &>/dev/null && [ -n "${ROUTER_PASS}" ]; then
        SSHPASS="${ROUTER_PASS}" sshpass -e \
            ssh ${SSH_CONN_OPTS} -p "${SSH_PORT}" "${ROUTER_USER}@${ROUTER_IP}" "$@"
    else
        ssh ${SSH_CONN_OPTS} -o BatchMode=yes \
            -p "${SSH_PORT}" "${ROUTER_USER}@${ROUTER_IP}" "$@"
    fi
}
ssh_run_stdin() { _ssh_cmd_stdin "$@" 2>&1; }

# ── Helper: test SSH connectivity and auth ────────────────────────────────────
# Returns: 0=ok, 1=unreachable, 2=auth_failed, 3=not_openwrt
_test_connection() {
    # 1. Reachability
    if command -v nc &>/dev/null; then
        if ! nc -z -w 5 "${ROUTER_IP}" "${SSH_PORT}" 2>/dev/null; then
            return 1
        fi
    fi

    # 2. Auth
    local out
    if command -v sshpass &>/dev/null && [ -n "${ROUTER_PASS}" ]; then
        out=$(SSHPASS="${ROUTER_PASS}" sshpass -e \
            ssh ${SSH_BASE_OPTS} -p "${SSH_PORT}" \
            "${ROUTER_USER}@${ROUTER_IP}" \
            "cat /etc/openwrt_release 2>/dev/null || echo NOT_OPENWRT" 2>&1) || true
    else
        out=$(ssh ${SSH_BASE_OPTS} -o BatchMode=yes \
            -p "${SSH_PORT}" "${ROUTER_USER}@${ROUTER_IP}" \
            "cat /etc/openwrt_release 2>/dev/null || echo NOT_OPENWRT" 2>&1) || true
    fi

    # Connection refused / timeout / host unreachable
    if echo "${out}" | grep -qiE "refused|timed out|No route|Network is unreachable|port [0-9]+ failed"; then
        return 1
    fi

    # Auth failure
    if echo "${out}" | grep -qiE "Permission denied|Authentication failed|password|publickey"; then
        return 2
    fi

    # SSH connected but not OpenWrt
    if echo "${out}" | grep -q "NOT_OPENWRT"; then
        return 3
    fi

    # Success — stash release info for later
    OWRT_RELEASE="${out}"
    return 0
}

# ── Helper: ensure sshpass is available when a password is given ──────────────
_ensure_sshpass() {
    [ -z "${ROUTER_PASS}" ] && return 0          # no password → key auth, no need
    command -v sshpass &>/dev/null && return 0   # already installed

    echo ""
    warn "A password was entered but ${C_BOLD}sshpass${C_RESET}${C_YELLOW} is not installed."
    warn "sshpass is needed to pass the password non-interactively."
    echo ""

    # Try to install automatically
    if command -v brew &>/dev/null; then
        info "Attempting to install sshpass via Homebrew..."
        if brew install sshpass </dev/null; then
            ok "sshpass installed"
            return 0
        fi
    elif command -v apt-get &>/dev/null; then
        info "Attempting to install sshpass via apt..."
        if sudo apt-get install -y sshpass </dev/null; then
            ok "sshpass installed"
            return 0
        fi
    fi

    echo ""
    err "Could not install sshpass automatically. Please install it manually:"
    err "  macOS:  brew install sshpass"
    err "  Ubuntu: sudo apt install sshpass"
    err "  Fedora: sudo dnf install sshpass"
    echo ""
    return 1
}

# ══════════════════════════════════════════════════════════════════════════════
# WIZARD LOOP — repeats on connection failure so user can correct credentials
# ══════════════════════════════════════════════════════════════════════════════
OWRT_RELEASE=""
CONNECT_ATTEMPT=0

while true; do
    CONNECT_ATTEMPT=$(( CONNECT_ATTEMPT + 1 ))

    # ── Step 1: Router IP ────────────────────────────────────────────────────
    step 1 3 "Router"

    if [ "${NON_INTERACTIVE}" = "1" ] && [ -n "${ROUTER_IP}" ] && [ "${CONNECT_ATTEMPT}" = "1" ]; then
        # Non-interactive: IP was passed on command line
        ok "Router IP: ${ROUTER_IP}  ${C_DIM}(from --ip flag)${C_RESET}"
    else
        SUGGESTED_IP="$(_detect_gateway)"
        echo ""
        if [ "${CONNECT_ATTEMPT}" -gt 1 ]; then
            warn "Previous connection attempt failed. Please check the details below."
            echo ""
        fi
        info "Your router's IP address is usually your network's default gateway."
        dim  "  (Detected: ${SUGGESTED_IP})"
        echo ""
        printf "  ${C_BOLD}Router IP${C_RESET} [${SUGGESTED_IP}]: "
        read -r INPUT_IP <"${TTY}"
        ROUTER_IP="${INPUT_IP:-${SUGGESTED_IP}}"
        ok "Router IP: ${ROUTER_IP}"
    fi

    # ── Step 2: Credentials ──────────────────────────────────────────────────
    step 2 3 "Login"
    echo ""

    if [ "${NON_INTERACTIVE}" = "1" ] && [ "${CONNECT_ATTEMPT}" = "1" ]; then
        ROUTER_USER="${ROUTER_USER:-root}"
        ok "Username: ${ROUTER_USER}  ${C_DIM}(from --user flag)${C_RESET}"
        [ -n "${ROUTER_PASS}" ] && ok "Password: ****  ${C_DIM}(from --password flag)${C_RESET}"
    else
        printf "  ${C_BOLD}Username${C_RESET} [root]: "
        read -r INPUT_USER <"${TTY}"
        ROUTER_USER="${INPUT_USER:-root}"

        echo ""
        info "Enter the router's root password."
        dim  "  (Default for most routers: admin  |  Leave blank if no password is set)"
        echo ""
        if command -v sshpass &>/dev/null; then
            printf "  ${C_BOLD}Password${C_RESET} [admin]: "
            read -rs INPUT_PASS <"${TTY}"
            echo ""
            ROUTER_PASS="${INPUT_PASS:-admin}"
        else
            printf "  ${C_BOLD}Password${C_RESET} [admin]: "
            read -rs INPUT_PASS <"${TTY}"
            echo ""
            ROUTER_PASS="${INPUT_PASS:-admin}"
            # Attempt to get sshpass; if it fails we warn and try key auth
            if ! _ensure_sshpass; then
                warn "Falling back to SSH key authentication (no password will be used)."
                ROUTER_PASS=""
            fi
        fi
    fi

    echo ""
    info "Connecting to ${ROUTER_USER}@${ROUTER_IP}:${SSH_PORT} ..."

    # ── Step 3: Connect & verify ─────────────────────────────────────────────
    step 3 3 "Connecting"
    echo ""

    CONN_RESULT=0
    _test_connection || CONN_RESULT=$?

    case "${CONN_RESULT}" in
        0)
            ok "Connected successfully"
            ;;
        1)
            echo ""
            err "Cannot reach ${ROUTER_IP}:${SSH_PORT}"
            echo ""
            warn "Possible reasons:"
            warn "  • Wrong IP address"
            warn "  • Router is off or rebooting"
            warn "  • Your device is on a different network"
            warn "  • SSH is disabled on the router"
            echo ""
            printf "  ${C_BOLD}Try a different IP?${C_RESET} [Y/n]: "
            read -r RETRY <"${TTY}"
            case "${RETRY:-Y}" in
                [Nn]*) die "Installation cancelled." ;;
                *) continue ;;
            esac
            ;;
        2)
            echo ""
            err "Authentication failed for ${ROUTER_USER}@${ROUTER_IP}"
            echo ""
            warn "Possible reasons:"
            warn "  • Wrong username or password"
            warn "  • Root SSH login is disabled (enable in LuCI: System → Administration)"
            echo ""
            printf "  ${C_BOLD}Try again with different credentials?${C_RESET} [Y/n]: "
            read -r RETRY <"${TTY}"
            case "${RETRY:-Y}" in
                [Nn]*) die "Installation cancelled." ;;
                *) NON_INTERACTIVE=0; continue ;;
            esac
            ;;
        3)
            echo ""
            err "Connected to ${ROUTER_IP} but this does not appear to be an OpenWrt router."
            warn "  /etc/openwrt_release not found."
            warn "  This installer only supports OpenWrt routers."
            echo ""
            printf "  ${C_BOLD}Try a different IP?${C_RESET} [Y/n]: "
            read -r RETRY <"${TTY}"
            case "${RETRY:-Y}" in
                [Nn]*) die "Installation cancelled." ;;
                *) continue ;;
            esac
            ;;
    esac

    # Connection succeeded — break out of the wizard loop
    break
done

# ── Parse OpenWrt info from the cached release file ───────────────────────────
# Pure-bash parsing: no external commands (grep/cut/tr), no exit-code hazards
# with set -euo pipefail.  Strips both double- and single-quotes from values.
DISTRIB_RELEASE=""
DISTRIB_TARGET=""
DISTRIB_ARCH=""
while IFS= read -r _line; do
    case "${_line}" in
        DISTRIB_RELEASE=*) DISTRIB_RELEASE="${_line#DISTRIB_RELEASE=}"; DISTRIB_RELEASE="${DISTRIB_RELEASE//\"/}"; DISTRIB_RELEASE="${DISTRIB_RELEASE//\'/}" ;;
        DISTRIB_TARGET=*)  DISTRIB_TARGET="${_line#DISTRIB_TARGET=}";   DISTRIB_TARGET="${DISTRIB_TARGET//\"/}";   DISTRIB_TARGET="${DISTRIB_TARGET//\'/}"  ;;
        DISTRIB_ARCH=*)    DISTRIB_ARCH="${_line#DISTRIB_ARCH=}";       DISTRIB_ARCH="${DISTRIB_ARCH//\"/}";       DISTRIB_ARCH="${DISTRIB_ARCH//\'/}"    ;;
    esac
done <<< "${OWRT_RELEASE}"

echo ""
ok "OpenWrt ${DISTRIB_RELEASE:-unknown} detected"
dim "  Target : ${DISTRIB_TARGET:-unknown}"
dim "  Arch   : ${DISTRIB_ARCH:-unknown}"

echo ""
printf "  ${C_BOLD}Proceed with installation?${C_RESET} [Y/n]: "
read -r PROCEED <"${TTY}"
case "${PROCEED:-Y}" in
    [Nn]*) die "Installation cancelled." ;;
esac

# ══════════════════════════════════════════════════════════════════════════════
# INSTALLATION
# ══════════════════════════════════════════════════════════════════════════════

# ── Readiness checks ──────────────────────────────────────────────────────────
echo ""
info "Checking router readiness..."

PKG_MGR="$(ssh_run "command -v apk >/dev/null 2>&1 && echo apk || command -v opkg >/dev/null 2>&1 && echo opkg || echo none" | tr -d '[:space:]')"
case "${PKG_MGR}" in
    apk)  ok "Package manager: apk (OpenWrt 25.x+)" ;;
    opkg) ok "Package manager: opkg (OpenWrt 23.x/24.x)" ;;
    *)    die "No supported package manager (neither apk nor opkg). Requires OpenWrt 23.05+." ;;
esac

TMP_FREE_KB="$(ssh_run "df /tmp 2>/dev/null | awk 'NR==2{print \$4}'" | tr -d '[:space:]')"
if [ -n "${TMP_FREE_KB}" ] && [ "${TMP_FREE_KB}" -lt 4096 ] 2>/dev/null; then
    die "Less than 4 MB free in /tmp (${TMP_FREE_KB} KB). Please free up space first."
fi
ok "Disk: /tmp has ${TMP_FREE_KB:-?} KB free"

info "Checking router internet access..."
INET_OUT="$(ssh_run "wget -q --timeout=10 -O /dev/null https://s3.kahf.co 2>&1; echo exit:\$?")"
if echo "${INET_OUT}" | grep -q "exit:0"; then
    ok "Router can reach s3.kahf.co"
else
    warn "Router may not have internet access — downloads may fail."
fi

# ── Install auto-update agent ─────────────────────────────────────────────────
echo ""
info "Downloading auto-update agent from ${S3_BASE}..."

if [ "${DRY_RUN}" = "1" ]; then
    ok "[DRY RUN] Would install ${UPDATE_SCRIPT_URL} → ${INSTALL_PATH}"
else
    INSTALL_CMD="$(cat <<EOF
set -e
wget -q --timeout=30 -O '${INSTALL_PATH}' '${UPDATE_SCRIPT_URL}' || {
    echo "DOWNLOAD_FAILED"
    exit 1
}
chmod +x '${INSTALL_PATH}'
echo "AGENT_READY"
EOF
)"
    DL_OUT="$(ssh_run_stdin "sh" <<< "${INSTALL_CMD}")"
    if echo "${DL_OUT}" | grep -q "AGENT_READY"; then
        AGENT_SIZE="$(ssh_run "wc -c < '${INSTALL_PATH}'" 2>/dev/null | tr -d '[:space:]')"
        ok "Auto-update agent copied to ${INSTALL_PATH} (${AGENT_SIZE:-?} bytes)"
    else
        err "Failed to download the auto-update agent."
        err "  URL: ${UPDATE_SCRIPT_URL}"
        die "Check that the router has internet access and try again."
    fi

    if [ "${CHANNEL}" != "prod" ]; then
        ssh_run "sh -c 'echo UPDATE_CHANNEL=${CHANNEL} > /etc/oaf-update.conf'"
        ok "Channel configured: ${CHANNEL}"
    else
        ssh_run "rm -f /etc/oaf-update.conf" || true
        ok "Channel: prod (default)"
    fi
fi

# ── Run first update ──────────────────────────────────────────────────────────
echo ""
info "Installing / verifying packages (this may take 2–5 minutes on first run)..."
dim  "  Packages: kmod-oaf  appfilter  luci-app-oaf  family-safe"
echo ""
dim  "┄┄┄┄┄┄┄┄┄┄ oaf-update output ┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄"

if [ "${DRY_RUN}" = "1" ]; then
    ok "[DRY RUN] Would run: sh ${INSTALL_PATH}"
else
    UPDATE_EXIT=0
    # No --force: lets oaf-update.sh print "[=] pkg (up to date)" when already current
    ssh_run "sh '${INSTALL_PATH}'" || UPDATE_EXIT=$?
    dim  "┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄"
    echo ""
    if [ "${UPDATE_EXIT}" -ne 0 ]; then
        warn "Update agent exited with code ${UPDATE_EXIT} (kmod kernel mismatch is normal on first run)."
    else
        ok "oaf-update.sh finished successfully"
    fi
fi

# ── Verify ────────────────────────────────────────────────────────────────────
echo ""
info "Verifying installation..."

if [ "${DRY_RUN}" != "1" ]; then
    VERIFY_SCRIPT='
errors=0
if command -v apk >/dev/null 2>&1; then
    apk info -e appfilter    >/dev/null 2>&1 && echo "INSTALLED:appfilter"    || { echo "MISSING:appfilter";    errors=$((errors+1)); }
    apk info -e family-safe  >/dev/null 2>&1 && echo "INSTALLED:family-safe"  || { echo "MISSING:family-safe";  errors=$((errors+1)); }
    apk info -e luci-app-oaf >/dev/null 2>&1 && echo "INSTALLED:luci-app-oaf" || echo "MISSING_OPT:luci-app-oaf"
elif command -v opkg >/dev/null 2>&1; then
    PKG_LIST="$(opkg list-installed 2>/dev/null)"
    echo "${PKG_LIST}" | grep -q "^appfilter "    && echo "INSTALLED:appfilter"    || { echo "MISSING:appfilter";    errors=$((errors+1)); }
    echo "${PKG_LIST}" | grep -q "^family-safe "  && echo "INSTALLED:family-safe"  || { echo "MISSING:family-safe";  errors=$((errors+1)); }
    echo "${PKG_LIST}" | grep -q "^luci-app-oaf " && echo "INSTALLED:luci-app-oaf" || echo "MISSING_OPT:luci-app-oaf"
fi
pgrep oafd >/dev/null 2>&1 && echo "RUNNING:oafd" || echo "NOT_RUNNING:oafd"
exit ${errors}
'
    VERIFY_OUT="$(ssh_run_stdin "sh" <<< "${VERIFY_SCRIPT}")" || true
    MISSING_COUNT=0
    while IFS= read -r line; do
        case "${line}" in
            INSTALLED:*)    ok "Installed   : ${line#INSTALLED:}" ;;
            MISSING:*)      warn "Missing     : ${line#MISSING:}"; MISSING_COUNT=$((MISSING_COUNT+1)) ;;
            MISSING_OPT:*)  warn "Not installed (optional): ${line#MISSING_OPT:}" ;;
            RUNNING:*)      ok "Running     : ${line#RUNNING:}" ;;
            NOT_RUNNING:*)  warn "Not running : ${line#NOT_RUNNING:}" ;;
        esac
    done <<< "${VERIFY_OUT}"
    echo ""
    if [ "${MISSING_COUNT}" = "0" ]; then
        ok "All required packages installed successfully"
    else
        warn "${MISSING_COUNT} required package(s) missing — check the output above"
    fi
fi

# ── Nightly cron ─────────────────────────────────────────────────────────────
if [ "${SKIP_CRON}" = "0" ] && [ "${DRY_RUN}" != "1" ]; then
    echo ""
    info "Setting up nightly auto-update cron..."
    CRON_RESULT="$(ssh_run_stdin "sh" <<EOF
mkdir -p "\$(dirname '${CRON_FILE}')"; touch '${CRON_FILE}'
grep -q "oaf-update" '${CRON_FILE}' 2>/dev/null \
    && echo ALREADY_SET \
    || { echo '${CRON_ENTRY}' >> '${CRON_FILE}'; echo ADDED; }
/etc/init.d/cron reload 2>/dev/null || /etc/init.d/cron restart 2>/dev/null || true
EOF
)"
    echo "${CRON_RESULT}" | grep -q "ADDED"       && ok "Cron job added — auto-updates nightly at 03:00 UTC"
    echo "${CRON_RESULT}" | grep -q "ALREADY_SET" && ok "Cron job already configured"
fi

# ── Done ─────────────────────────────────────────────────────────────────────
echo ""
banner "╔══════════════════════════════════════════════════════════════╗"
if [ "${DRY_RUN}" = "1" ]; then
    banner "║   Dry run complete — no changes were made                   ║"
else
    banner "║   Kahf Family Safety installation complete! 🎉              ║"
fi
banner "╚══════════════════════════════════════════════════════════════╝"
echo ""
ok "Router         : ${ROUTER_USER}@${ROUTER_IP}"
ok "Update agent   : ${INSTALL_PATH}"
ok "Channel        : ${CHANNEL}  (${S3_BASE})"
[ "${SKIP_CRON}" = "0" ] && ok "Auto-updates   : nightly at 03:00 UTC"
echo ""
info "Useful commands:"
dim  "  Trigger update now : ssh ${ROUTER_USER}@${ROUTER_IP} sh ${INSTALL_PATH} --force"
dim  "  View update log    : ssh ${ROUTER_USER}@${ROUTER_IP} cat /var/log/oaf-update.log"
dim  "  Router web UI      : http://${ROUTER_IP}"
echo ""
