#!/usr/bin/env bash
# SPDX-License-Identifier: LicenseRef-CMSD-1.0
# SecuBox-Deb :: mqttctl
# CyberMind — https://cybermind.fr
#
# Mosquitto host-side controller. LXC at 10.100.0.110 on br-lxc.
# Three-fold introspection (components/status/access) + install/reload +
# topic/client/acl/user nouns.
#
# Grammar: docs/grammar.md, WALL layer.
# Conventions: docs/MODULE-GUIDELINES.md §7.

set -u

readonly VERSION="2.4.0"
readonly CONFIG_FILE="${SECUBOX_MQTT_CONFIG:-/etc/secubox/mqtt.toml}"
readonly STATE_DIR="${SECUBOX_MQTT_STATE:-/var/lib/secubox/mqtt}"
readonly SECRETS_DIR="${SECUBOX_SECRETS_DIR:-/etc/secubox/secrets}"
readonly INSTALL_LIB="${SECUBOX_INSTALL_LIB:-/usr/share/secubox/lib/mqtt/install-lxc.sh}"

readonly RED='\033[0;31m'
readonly GREEN='\033[0;32m'
readonly YELLOW='\033[1;33m'
readonly NC='\033[0m'

log()   { printf '%b[mqtt]%b %s\n' "$GREEN" "$NC" "$*"; }
warn()  { printf '%b[warn]%b %s\n'    "$YELLOW" "$NC" "$*" >&2; }
err()   { printf '%b[error]%b %s\n'   "$RED"    "$NC" "$*" >&2; }

# ── TOML config (best-effort, grep-style) ────────────────────────────────────
config_get() {
    local key="$1" default="${2:-}"
    if [ -f "$CONFIG_FILE" ]; then
        grep -E "^[[:space:]]*${key}[[:space:]]*=" "$CONFIG_FILE" 2>/dev/null \
            | head -1 | cut -d= -f2- | tr -d ' "' | tr -d "'" || echo "$default"
    else
        echo "$default"
    fi
}

LXC_NAME=$(config_get "name" "mqtt")
LXC_IP=$(config_get "ip" "10.100.0.110")
LXC_PATH=$(config_get "path" "/data/lxc")
MQTT_PORT=$(config_get "port" "1883")

# ── LXC helpers ───────────────────────────────────────────────────────────────
lxc_state() {
    lxc-info -n "$LXC_NAME" -P "$LXC_PATH" 2>/dev/null \
        | awk -F: '/^State:/ { gsub(/ /,"",$2); print tolower($2) }'
}

lxc_running() { [ "$(lxc_state)" = "running" ]; }
lxc_exists()  { [ -d "$LXC_PATH/$LXC_NAME/rootfs" ]; }

broker_running() {
    lxc_running || return 1
    lxc-attach -n "$LXC_NAME" -P "$LXC_PATH" -- systemctl is-active --quiet mosquitto 2>/dev/null
}

host_api_running() {
    systemctl is-active --quiet secubox-mqtt.service 2>/dev/null
}

# Pub/sub round-trip — proves the broker is actually responsive, not just up.
broker_healthy() {
    local user="secubox-api" pw_file="$SECRETS_DIR/mqtt-secubox-api"
    [ -f "$pw_file" ] || return 1
    local pw; pw=$(cat "$pw_file")
    timeout 3 mosquitto_pub -h "$LXC_IP" -p "$MQTT_PORT" -u "$user" -P "$pw" \
        -t "secubox/healthcheck" -m "ok" >/dev/null 2>&1
}

# ── Three-fold output ────────────────────────────────────────────────────────
emit_components_json() {
    local lxc_st broker_st api_st health_st
    if lxc_exists; then
        lxc_st=$(lxc_state); [ -z "$lxc_st" ] && lxc_st="absent"
    else
        lxc_st="absent"
    fi
    broker_running   && broker_st="running" || broker_st="stopped"
    host_api_running && api_st="running"    || api_st="stopped"
    broker_healthy   && health_st="ok"      || health_st="degraded"

    cat <<EOF
{
  "module": "mqtt",
  "version": "$VERSION",
  "components": [
    {"name": "lxc",      "state": "$lxc_st",     "detail": "$LXC_NAME @ $LXC_IP on br-lxc"},
    {"name": "broker",   "state": "$broker_st",  "detail": "mosquitto, port $MQTT_PORT"},
    {"name": "health",   "state": "$health_st",  "detail": "pub/sub round-trip via secubox-api"},
    {"name": "host-api", "state": "$api_st",     "detail": "secubox-mqtt.service (uvicorn @ /run/secubox/mqtt.sock)"}
  ]
}
EOF
}

emit_components_text() {
    printf '%-12s %-12s %s\n' "COMPONENT" "STATE" "DETAIL"
    if lxc_exists; then
        local s; s=$(lxc_state); [ -z "$s" ] && s="absent"
        printf '%-12s %-12s %s\n' "lxc" "$s" "$LXC_NAME @ $LXC_IP on br-lxc"
    else
        printf '%-12s %-12s %s\n' "lxc" "absent" "$LXC_NAME @ $LXC_IP on br-lxc — run 'mqttctl install'"
    fi
    broker_running   && printf '%-12s %-12s %s\n' "broker"   "running"  "mosquitto, port $MQTT_PORT" \
                     || printf '%-12s %-12s %s\n' "broker"   "stopped"  "mosquitto, port $MQTT_PORT"
    broker_healthy   && printf '%-12s %-12s %s\n' "health"   "ok"       "pub/sub round-trip via secubox-api" \
                     || printf '%-12s %-12s %s\n' "health"   "degraded" "pub/sub round-trip failed"
    host_api_running && printf '%-12s %-12s %s\n' "host-api" "running"  "secubox-mqtt.service (uvicorn)" \
                     || printf '%-12s %-12s %s\n' "host-api" "stopped"  "secubox-mqtt.service (uvicorn)"
}

emit_status_json() {
    local overall
    if lxc_running && broker_running && broker_healthy && host_api_running; then
        overall="green"
    elif lxc_exists && (broker_running || host_api_running); then
        overall="yellow"
    else
        overall="red"
    fi
    cat <<EOF
{ "module": "mqtt", "version": "$VERSION", "overall": "$overall" }
EOF
}

# Access: which users/topics can connect? Reads the ACL config from the LXC.
emit_access_json() {
    local acl_file="/etc/mosquitto/acl.d/secubox.acl"
    local users
    if lxc_running; then
        users=$(lxc-attach -n "$LXC_NAME" -P "$LXC_PATH" -- awk '/^user /{print $2}' "$acl_file" 2>/dev/null | sort -u)
    fi
    printf '{ "module": "mqtt", "access": ['
    local first=1
    for u in $users; do
        local topic
        topic=$(lxc-attach -n "$LXC_NAME" -P "$LXC_PATH" -- awk -v u="$u" '
            $1=="user" && $2==u {found=1; next}
            $1=="user" && $2!=u {found=0}
            found && $1=="topic" {sub(/^topic +/, "", $0); print; exit}
        ' "$acl_file" 2>/dev/null)
        [ $first -eq 1 ] && first=0 || printf ','
        printf '\n  {"user": "%s", "topic": "%s", "scope": "lan"}' "$u" "${topic:-?}"
    done
    printf '\n] }\n'
}

# ── Verbs ─────────────────────────────────────────────────────────────────────
cmd_components() {
    case "${1:-}" in
        --json) emit_components_json ;;
        ""|list|status) emit_components_text ;;
        *) err "unknown verb for 'components': $1"; exit 1 ;;
    esac
}

cmd_status() {
    case "${1:-}" in
        --json) emit_status_json ;;
        "")
            emit_components_text
            echo
            emit_status_json | python3 -c 'import json,sys; d=json.load(sys.stdin); print("overall:", d["overall"])'
            ;;
        *) err "unknown verb for 'status': $1"; exit 1 ;;
    esac
}

cmd_access() {
    case "${1:-}" in
        --json) emit_access_json ;;
        ""|list) emit_access_json | python3 -m json.tool ;;
        show)
            local name="${2:-}"
            [ -z "$name" ] && { err "access show <user>"; exit 1; }
            emit_access_json | python3 -c "
import json,sys
d=json.load(sys.stdin)
m=[a for a in d['access'] if a['user']=='$name']
print(json.dumps(m[0], indent=2) if m else 'not found')
"
            ;;
        *) err "unknown verb for 'access': $1"; exit 1 ;;
    esac
}

cmd_install() {
    if [ ! -x "$INSTALL_LIB" ]; then
        err "install script not found at $INSTALL_LIB (package not installed correctly?)"
        exit 2
    fi
    log "Running LXC bootstrap from $INSTALL_LIB ..."
    SECUBOX_LXC_NAME="$LXC_NAME" SECUBOX_LXC_IP="$LXC_IP" SECUBOX_LXC_PATH="$LXC_PATH" \
        SECUBOX_MQTT_PORT="$MQTT_PORT" \
        bash "$INSTALL_LIB"
    log "Starting host-side FastAPI ..."
    systemctl start secubox-mqtt.service 2>/dev/null || true
    log "Done. Try 'mqttctl status' to verify."
}

cmd_reload() {
    systemctl restart secubox-mqtt.service 2>/dev/null || true
    if lxc_running; then
        lxc-attach -n "$LXC_NAME" -P "$LXC_PATH" -- systemctl restart mosquitto 2>/dev/null || true
    fi
    log "Reloaded host FastAPI and mosquitto (if running)."
}

# Module nouns — list-only stubs in v2.4.0. Writes (add/remove) are
# proxied via /api/v1/mqtt and require operator authentication; the CLI
# stubs print a clear "use the API" hint.
cmd_topic() {
    case "${1:-list}" in
        list)
            # Pub-subscribe to # for a brief window and list seen topics.
            local user="secubox-api" pw_file="$SECRETS_DIR/mqtt-secubox-api" pw
            [ -f "$pw_file" ] || { err "missing $pw_file"; exit 2; }
            pw=$(cat "$pw_file")
            log "Sampling broker for 3s (mosquitto_sub -C 0 -W 3) ..."
            timeout 3 mosquitto_sub -h "$LXC_IP" -p "$MQTT_PORT" -u "$user" -P "$pw" \
                -t '#' -v 2>/dev/null | awk '{print $1}' | sort -u
            ;;
        *) err "topic: only 'list' is implemented in v$VERSION"; exit 2 ;;
    esac
}

cmd_client() {
    case "${1:-list}" in
        list)
            local user="secubox-api" pw_file="$SECRETS_DIR/mqtt-secubox-api" pw
            [ -f "$pw_file" ] || { err "missing $pw_file"; exit 2; }
            pw=$(cat "$pw_file")
            # Broker $SYS topic exposes client counts.
            timeout 3 mosquitto_sub -h "$LXC_IP" -p "$MQTT_PORT" -u "$user" -P "$pw" \
                -t '$SYS/broker/clients/+' -W 2 -v 2>/dev/null
            ;;
        *) err "client: only 'list' is implemented in v$VERSION"; exit 2 ;;
    esac
}

cmd_acl() {
    case "${1:-list}" in
        list)
            if lxc_running; then
                lxc-attach -n "$LXC_NAME" -P "$LXC_PATH" -- cat /etc/mosquitto/acl.d/secubox.acl
            else
                err "lxc not running"; exit 2
            fi
            ;;
        *) err "acl: only 'list' is implemented in v$VERSION (edit /etc/mosquitto/acl.d/secubox.acl + 'mqttctl reload')"; exit 2 ;;
    esac
}

cmd_user() {
    case "${1:-list}" in
        list)
            if lxc_running; then
                lxc-attach -n "$LXC_NAME" -P "$LXC_PATH" -- cut -d: -f1 /etc/mosquitto/passwd.d/secubox 2>/dev/null
            else
                err "lxc not running"; exit 2
            fi
            ;;
        *) err "user: only 'list' is implemented in v$VERSION"; exit 2 ;;
    esac
}

cmd_help() {
    cat <<'HELP'
mqttctl — SecuBox Mosquitto broker controller (WALL layer)

Three-fold introspection:
  mqttctl components [--json]      # LXC + broker + health + host-API states
  mqttctl status     [--json]      # overall green/yellow/red
  mqttctl access     [list|show <user>] [--json]

Lifecycle (per docs/MODULE-GUIDELINES.md §7):
  mqttctl install                  # idempotent LXC + Mosquitto bootstrap
  mqttctl reload                   # restart host FastAPI + mosquitto
  mqttctl repair                   # detect + fix drift (NOT YET IMPLEMENTED)
  mqttctl wizard                   # interactive seed + install (NOT YET IMPLEMENTED)
  mqttctl uninstall                # remove LXC + state + secrets (NOT YET IMPLEMENTED)

Module nouns (v2.4.0 — list-only; writes via /api/v1/mqtt):
  mqttctl topic   list             # 3s sample of seen topics
  mqttctl client  list             # broker $SYS/clients counters
  mqttctl acl     list             # cat /etc/mosquitto/acl.d/secubox.acl
  mqttctl user    list             # users from passwd.d/secubox

  (--json on any verb returns machine-readable output)

For grammar details: /usr/share/doc/secubox-mqtt/README.gz
                     docs/grammar.md (WALL layer, #240)
HELP
}

# ── Dispatch ──────────────────────────────────────────────────────────────────
main() {
    local noun="${1:-help}"
    shift || true
    case "$noun" in
        components|status|access|install|reload) "cmd_${noun}" "$@" ;;
        topic|client|acl|user)                   "cmd_${noun}" "$@" ;;
        --help|-h|help|"") cmd_help ;;
        --version|-V) echo "mqttctl $VERSION" ;;
        *) err "unknown noun: $noun"; cmd_help; exit 1 ;;
    esac
}

main "$@"
