#!/bin/bash
# veyon-sync — Vitalinux Veyon Sync
#
# Daemon que observa el registro VAS y, cuando detecta un cambio de versión,
# importa el inventario de equipos en Veyon mediante veyon-cli.
#
# Soporta dos modos según el rol de la máquina:
#   SOURCE=vas  → obtiene versión e inventario por HTTP desde VAS
#   SOURCE=vac  → lee los ficheros locales escritos por VAC
#
# Flujo de cada ciclo:
#   1. Leer versión local (/var/lib/veyon-sync/version)
#   2. Obtener versión remota (HTTP o fichero según SOURCE)
#   3. Si coincide: sleep CHECK_SECONDS
#   4. Si difiere: obtener inventario → generar CSV → importar en Veyon
#   5. Actualizar versión local → sleep CHECK_SECONDS
#
# Journalctl: journalctl -u veyon-sync -f
#   Filtrar por subcomponente:
#     journalctl -u veyon-sync -f | grep "\[VEYON-SYNC-ERROR\]"

set -euo pipefail

# ---------------------------------------------------------------------------
# Rutas y valores por defecto
# ---------------------------------------------------------------------------
CONF_FILE="/etc/veyon-sync/veyon-sync.conf"
CONF_DIR="/etc/veyon-sync/veyon-sync.conf.d"
STATE_DIR="/var/lib/veyon-sync"
VERSION_FILE="${STATE_DIR}/version"
CLIENTS_FILE="${STATE_DIR}/clients.json"
TMP_CLIENTS="${STATE_DIR}/clients.json.tmp"
CSV_FILE="${STATE_DIR}/computers.csv"

SOURCE="vas"
VAS_HOST=""
CHECK_SECONDS=300
RETRY_SECONDS=60
VEYON_LOCATION="Autoregistrados"
VAC_STATE_DIR="/var/lib/vac"

# ---------------------------------------------------------------------------
# Carga de configuración
# ---------------------------------------------------------------------------

# Parser seguro: lee clave=valor sin ejecutar código del fichero.
# Solo acepta las variables conocidas; el resto se ignora.
load_conf() {
    local file="$1"
    [ -f "$file" ] || return 0

    local loaded=0
    while IFS='=' read -r key val; do
        key="$(echo "$key" | xargs 2>/dev/null || true)"
        val="$(echo "$val" | xargs 2>/dev/null | sed 's/^"//; s/"$//' || true)"
        [ -z "$key" ] && continue

        case "$key" in
            SOURCE)           SOURCE="$val";           (( ++loaded )) ;;
            VAS_HOST)         VAS_HOST="$val";         (( ++loaded )) ;;
            CHECK_SECONDS)    CHECK_SECONDS="$val";    (( ++loaded )) ;;
            RETRY_SECONDS)    RETRY_SECONDS="$val";    (( ++loaded )) ;;
            VEYON_LOCATION)   VEYON_LOCATION="$val";   (( ++loaded )) ;;
            VAC_STATE_DIR)    VAC_STATE_DIR="$val";    (( ++loaded )) ;;
        esac
    done < <(grep -v '^\s*#' "$file" | grep '=' || true)

    echo "[VEYON-SYNC] Config cargada desde $file: $loaded clave(s)"
}

load_conf "$CONF_FILE"

if [[ -d "$CONF_DIR" ]]; then
    for cfg in "$CONF_DIR"/*.conf; do
        [[ -f "$cfg" ]] || continue
        load_conf "$cfg"
    done
fi

# ---------------------------------------------------------------------------
# Inicialización del estado local
# ---------------------------------------------------------------------------
mkdir -p "$STATE_DIR"

if [[ ! -f "$VERSION_FILE" ]]; then
    echo "0" > "$VERSION_FILE"
    echo "[VEYON-SYNC] Fichero de versión inicializado: $VERSION_FILE → 0"
fi

# ---------------------------------------------------------------------------
# Funciones de logging
# ---------------------------------------------------------------------------

# Prefijo [VEYON-SYNC] en todos los mensajes para facilitar filtrado:
#   journalctl -u veyon-sync -f | grep "\[VEYON-SYNC\]"
log() {
    echo "[VEYON-SYNC] $*" >&2
}

# Banner de arranque con configuración efectiva
log "=== Iniciando veyon-sync ==="
log "SOURCE:         $SOURCE"
log "CHECK:          ${CHECK_SECONDS}s"
log "RETRY:          ${RETRY_SECONDS}s"
log "LOCATION:       '${VEYON_LOCATION}'"
log "STATE_DIR:      $STATE_DIR"
[[ "$SOURCE" == "vas" ]] && log "VAS_HOST:       ${VAS_HOST:-'(no definido)'}"
[[ "$SOURCE" == "vac" ]] && log "VAC_STATE_DIR:  $VAC_STATE_DIR"

# veyon-cli es imprescindible; si no existe, no tiene sentido seguir.
if ! command -v veyon-cli >/dev/null 2>&1; then
    log "ERROR: veyon-cli no encontrado en PATH. Abortando."
    exit 1
fi
log "veyon-cli: $(command -v veyon-cli)"

# ---------------------------------------------------------------------------
# Funciones principales
# ---------------------------------------------------------------------------

# Devuelve la versión remota según SOURCE:
#   vas → GET /version desde VAS (extrae .version del JSON)
#   vac → lee VAC_STATE_DIR/version directamente
# Devuelve cadena vacía si la consulta falla.
get_remote_version() {
    local ver=""
    case "$SOURCE" in
        vas)
            ver="$(curl -fsS --max-time 10 --connect-timeout 5 \
                "${VAS_HOST%/}/version" 2>/dev/null \
                | jq -r '.version' 2>/dev/null \
                || echo "")"
            log "[VERSION] Versión remota (VAS): ${ver:-(vacía)}"
            ;;
        vac)
            local vac_version="${VAC_STATE_DIR}/version"
            if [[ -f "$vac_version" ]]; then
                ver="$(tr -d '[:space:]' < "$vac_version")"
                log "[VERSION] Versión remota (VAC fichero): ${ver:-(vacía)}"
            else
                log "[VERSION] Fichero VAC no encontrado: $vac_version"
            fi
            ;;
        *)
            log "[VERSION] SOURCE desconocido: '$SOURCE'. Valores válidos: vas, vac."
            ;;
    esac
    echo "$ver"
}

# Obtiene el inventario de clientes según SOURCE y lo guarda en CLIENTS_FILE.
# SOURCE=vas: descarga GET /clients con fichero temporal para atomicidad.
# SOURCE=vac: copia el fichero ya escrito por VAC (también actualizado atómicamente por VAC).
# Imprime el número de equipos encontrados. Devuelve 1 si falla.
fetch_clients() {
    case "$SOURCE" in
        vas)
            log "[FETCH] Descargando inventario desde ${VAS_HOST%/}/clients"
            if curl -fsS --max-time 15 --connect-timeout 5 \
                "${VAS_HOST%/}/clients" -o "$TMP_CLIENTS" 2>/dev/null; then
                local count
                count="$(jq '.clients | length' "$TMP_CLIENTS" 2>/dev/null || echo '?')"
                mv "$TMP_CLIENTS" "$CLIENTS_FILE"
                log "[FETCH] Inventario guardado: $CLIENTS_FILE ($count equipo(s))"
                return 0
            else
                log "[VEYON-SYNC-ERROR] Error descargando inventario desde VAS. CLIENTS_FILE no modificado."
                rm -f "$TMP_CLIENTS"
                return 1
            fi
            ;;
        vac)
            local vac_clients="${VAC_STATE_DIR}/clients.json"
            if [[ -f "$vac_clients" ]]; then
                cp "$vac_clients" "$CLIENTS_FILE"
                local count
                count="$(jq '.clients | length' "$CLIENTS_FILE" 2>/dev/null || echo '?')"
                log "[FETCH] Inventario copiado desde VAC: $CLIENTS_FILE ($count equipo(s))"
                return 0
            else
                log "[VEYON-SYNC-ERROR] No se encontró ${vac_clients}. ¿Está VAC instalado y activo?"
                return 1
            fi
            ;;
    esac
}

# Convierte CLIENTS_FILE (JSON) a CSV e importa los equipos en Veyon.
# Pasos:
#   1. Sanitizar VEYON_LOCATION (eliminar ';' para no romper el formato CSV)
#   2. jq → CSV: una línea "computer;hostname;ip;mac;location" por equipo
#   3. veyon-cli networkobjects remove <location>  (limpia estado previo)
#   4. veyon-cli networkobjects import <csv>       (importa nuevo estado)
# Devuelve 0 si la importación tuvo éxito, 1 en caso contrario.
sync_veyon() {
    local safe_location="${VEYON_LOCATION//;/}"

    log "[SYNC] Generando CSV para location '${safe_location}'"

    if ! jq -r \
        --arg location "$safe_location" \
        '.clients[]? | "computer;\((.hostname // "" | gsub(";";"")));\((.ip // "" | gsub(";";"")));\((.mac // "" | gsub(";";"")));\($location)"' \
        "$CLIENTS_FILE" > "$CSV_FILE" 2>/dev/null; then
        log "[VEYON-SYNC-ERROR] Error al convertir JSON a CSV."
        return 1
    fi

    local count
    count="$(wc -l < "$CSV_FILE" | tr -d ' ')"
    log "[SYNC] CSV generado: $CSV_FILE ($count entrada(s))"

    log "[SYNC] Limpiando location existente en Veyon: '${safe_location}'"
    if ! timeout 30 veyon-cli networkobjects remove "$safe_location" \
        >/dev/null 2>&1; then
        log "[SYNC] Aviso: no se pudo limpiar la location (puede que no existiera). Continuando."
    fi

    log "[SYNC] Importando ${count} equipo(s) en Veyon"
    if timeout 30 veyon-cli networkobjects import "$CSV_FILE" \
        format "%type%;%name%;%host%;%mac%;%location%" \
        >/dev/null 2>&1; then
        log "[SYNC] Sincronización completada: ${count} equipo(s) importado(s) en '${safe_location}'."
        return 0
    else
        log "[VEYON-SYNC-ERROR] Error al importar en Veyon."
        return 1
    fi
}

# ---------------------------------------------------------------------------
# Bucle principal
# ---------------------------------------------------------------------------
while true; do

    # Guardar marca de tiempo del inicio del ciclo para logs contextuales
    cycle_start="$(date -u '+%Y-%m-%d %H:%M:%S UTC')"
    log "--- Ciclo iniciado: $cycle_start ---"

    # Guardia: con SOURCE=vas, VAS_HOST debe estar definido
    if [[ "$SOURCE" == "vas" && -z "$VAS_HOST" ]]; then
        log "VAS_HOST no definido con SOURCE=vas. Esperando ${RETRY_SECONDS}s."
        sleep "$RETRY_SECONDS"
        continue
    fi

    # --- Paso 1: Leer versiones -------------------------------------------
    local_version="$(tr -d '[:space:]' < "$VERSION_FILE")"
    log "[VERSION] Versión local: $local_version"

    remote_version="$(get_remote_version)"

    if [[ -z "$remote_version" ]]; then
        log "[VERSION] No se pudo obtener versión remota. Reintentando en ${RETRY_SECONDS}s."
        sleep "$RETRY_SECONDS"
        continue
    fi

    # La versión debe ser numérica (YYYYMMDDHHMMSSmmm). Rechazar valores malformados.
    if [[ ! "$remote_version" =~ ^[0-9]+$ ]]; then
        log "[VERSION] Versión remota inválida: '$remote_version'. Reintentando en ${RETRY_SECONDS}s."
        sleep "$RETRY_SECONDS"
        continue
    fi

    if [[ "$remote_version" == "$local_version" ]]; then
        log "[VERSION] Sin cambios. Próxima comprobación en ${CHECK_SECONDS}s."
        sleep "$CHECK_SECONDS"
        continue
    fi

    log "[SYNC] Nueva versión detectada: $local_version → $remote_version"

    # --- Paso 2: Obtener inventario actualizado ----------------------------
    if ! fetch_clients; then
        log "[VEYON-SYNC-ERROR] No se pudo obtener el inventario. Reintentando en ${RETRY_SECONDS}s."
        sleep "$RETRY_SECONDS"
        continue
    fi

    # --- Paso 3: Importar en Veyon ----------------------------------------
    if sync_veyon; then
        echo "$remote_version" > "$VERSION_FILE"
        log "[SYNC] Versión local actualizada: $remote_version"
    else
        log "[VEYON-SYNC-ERROR] Fallo en sincronización. Reintentando en ${RETRY_SECONDS}s."
        sleep "$RETRY_SECONDS"
        continue
    fi

    log "--- Ciclo completado. Próxima comprobación en ${CHECK_SECONDS}s. ---"
    sleep "$CHECK_SECONDS"
done
