#!/usr/bin/env bash
# SPDX-License-Identifier: LicenseRef-CMSD-1.0
# SecuBox-Deb :: lyrionctl
# CyberMind — https://cybermind.fr
#
# Lyrion host-side controller. LXC at 10.100.0.100 on br-lxc.
# Three-fold introspection (components/status/access) + dashboard, datasource,
# alert, user, api-key, install, reload verbs.
#
# Grammar: docs/grammar.md, OPS MONITORING layer.
# Conventions: docs/MODULE-GUIDELINES.md §7.

set -u

readonly VERSION="1.0.1"
readonly CONFIG_FILE="${SECUBOX_LYRION_CONFIG:-/etc/secubox/lyrion.toml}"
readonly STATE_DIR="${SECUBOX_LYRION_STATE:-/var/lib/secubox/lyrion}"
readonly SECRETS_DIR="${SECUBOX_SECRETS_DIR:-/etc/secubox/secrets}"
readonly INSTALL_LIB="${SECUBOX_INSTALL_LIB:-/usr/share/secubox/lib/lyrion/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[lyrion]%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 — same as giteactl) ─────────────────
config_get() {
    # Minimal TOML scalar read: strips inline `# comments`, surrounding
    # whitespace, and the optional "..." / '...' quotes. The previous
    # version concatenated comment text into the value (eg HTTP_PORT
    # became `9000 # web admin UI` which then broke URL construction).
    local key="$1" default="${2:-}" raw=""
    if [ -f "$CONFIG_FILE" ]; then
        raw=$(grep -E "^[[:space:]]*${key}[[:space:]]*=" "$CONFIG_FILE" 2>/dev/null | head -1)
    fi
    if [ -z "$raw" ]; then
        echo "$default"
        return
    fi
    # value-after-equals, strip inline comment, trim whitespace, drop quotes
    local val
    val=$(echo "$raw" | cut -d= -f2- | sed 's/[[:space:]]*#.*$//' \
        | sed -E 's/^[[:space:]]+//; s/[[:space:]]+$//' \
        | tr -d '"' | tr -d "'")
    if [ -z "$val" ]; then
        echo "$default"
    else
        echo "$val"
    fi
}

LXC_NAME=$(config_get "name" "lyrion")
LXC_IP=$(config_get "ip" "10.100.0.100")
LXC_PATH=$(config_get "path" "/data/lxc")
HTTP_PORT=$(config_get "http_port" "3000")
PUBLIC_HOSTNAME=$(config_get "public_hostname" "lyrion.gk2.secubox.in")

# ── 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" ]; }

daemon_running() {
    lxc_running || return 1
    # The upstream .deb installs unit `lyrionmusicserver.service`; the
    # historic Logitech-era unit `logitechmediaserver.service` is the
    # legacy fallback if someone provisioned an older release.
    lxc-attach -n "$LXC_NAME" -P "$LXC_PATH" -- systemctl is-active --quiet lyrionmusicserver 2>/dev/null \
        || lxc-attach -n "$LXC_NAME" -P "$LXC_PATH" -- systemctl is-active --quiet logitechmediaserver 2>/dev/null
}

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

# ── Three-fold output (matches docs/MODULE-GUIDELINES.md §7) ──────────────────
emit_components_json() {
    local lxc_st daemon_st api_st
    if lxc_exists; then
        lxc_st=$(lxc_state)
        [ -z "$lxc_st" ] && lxc_st="absent"
    else
        lxc_st="absent"
    fi
    daemon_running && daemon_st="running" || daemon_st="stopped"
    host_api_running && api_st="running" || api_st="stopped"

    cat <<EOF
{
  "module": "lyrion",
  "version": "$VERSION",
  "components": [
    {"name": "lxc",      "state": "$lxc_st",      "detail": "$LXC_NAME @ $LXC_IP on br-lxc"},
    {"name": "daemon",   "state": "$daemon_st",   "detail": "lyrion, port $HTTP_PORT"},
    {"name": "host-api", "state": "$api_st",      "detail": "secubox-lyrion.service (uvicorn @ /run/secubox/lyrion.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 'lyrionctl install'"
    fi
    if daemon_running; then
        printf '%-12s %-12s %s\n' "daemon" "running" "lyrion, port $HTTP_PORT"
    else
        printf '%-12s %-12s %s\n' "daemon" "stopped" "lyrion, port $HTTP_PORT"
    fi
    if host_api_running; then
        printf '%-12s %-12s %s\n' "host-api" "running" "secubox-lyrion.service (uvicorn)"
    else
        printf '%-12s %-12s %s\n' "host-api" "stopped" "secubox-lyrion.service (uvicorn)"
    fi
}

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

emit_access_json() {
    # Real LMS Music UI URLs — NOT the /lyrion/ admin subpath. The admin
    # webui's "Open Music UI" button reads this; consistency comes from
    # one source.
    #   lan:    http://<host>:9000/   (direct nginx vhost, LAN-only)
    #   public: https://<PUBLIC_HOSTNAME>/   (Authelia-gated dedicated vhost)
    local lan_url public_url
    lan_url="http://$(hostname -I | awk '{print $1}'):${HTTP_PORT}/"
    if [ -n "$PUBLIC_HOSTNAME" ]; then
        public_url="https://${PUBLIC_HOSTNAME}/"
    else
        public_url=""
    fi
    cat <<EOF
{
  "module": "lyrion",
  "access": [
    {"url": "$lan_url",    "auth": "lan-only",       "scope": "lan"}$( [ -n "$public_url" ] && echo ',' )
    $( [ -n "$public_url" ] && cat <<PUB
{"url": "$public_url", "auth": "Authelia SSO",   "scope": "public"}
PUB
)
  ]
}
EOF
}

# ── 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 ;;
        ""|list) 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:-lan}"
            emit_access_json | python3 -c "import json,sys; d=json.load(sys.stdin); m=[a for a in d['access'] if a['scope']=='$name']; print(m[0] 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" \
        bash "$INSTALL_LIB"
    log "Starting host-side FastAPI ..."
    systemctl start secubox-lyrion.service 2>/dev/null || true
    log "Done. Try 'lyrionctl status' to verify."
}

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

cmd_help() {
    cat <<'HELP'
lyrionctl — SecuBox Lyrion music-server controller (HOSTING layer)

Three-fold introspection:
  lyrionctl components [--json]      # LXC + lyrion daemon + host-API states
  lyrionctl status     [--json]      # overall green/yellow/red
  lyrionctl access     [list|show <name>] [--json]

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

Music-server nouns (v1.1.0):
  lyrionctl player       list|play <id>|pause <id>|next <id>|prev <id>|volume <id> <0-100>
  lyrionctl library      scan|refresh|status|wipe
  lyrionctl playlist     list|add <name>|remove <name>|tracks <name>
  lyrionctl plugin       list|install <name>|uninstall <name>

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

For grammar details: /usr/share/doc/secubox-lyrion/README.gz
                     docs/grammar.md (HOSTING layer, #244)
HELP
}

# ── Dispatch ──────────────────────────────────────────────────────────────────
main() {
    local noun="${1:-help}"
    shift || true
    case "$noun" in
        components|status|access|install|reload) "cmd_${noun}" "$@" ;;
        # The remaining nouns are stubs in v1.0.0 — see #230 task G5 for
        # full implementation. They print a clear "not implemented yet" so
        # the help/grammar surface is consistent now and the API contract
        # is testable.
        player|library|playlist|plugin)
            err "noun '$noun' not yet implemented in v$VERSION — see #244 v1.1.0 follow-up"
            exit 2
            ;;
        --help|-h|help|"") cmd_help ;;
        --version|-V) echo "lyrionctl $VERSION" ;;
        *) err "unknown noun: $noun"; cmd_help; exit 1 ;;
    esac
}

main "$@"
