#!/usr/bin/env bash
# La puerta: decide si un cambio automatico se queda o se revierte.
#
# Es la pieza central del pipeline nocturno. Todo lo que el sistema se cambia a
# si mismo pasa por aqui, y lo que no la pasa se deshace ENTERO. Sin esto,
# dejar algo modificando codigo sin nadie delante es una forma elaborada de
# romper un proyecto que funcionaba.
#
# Que comprueba, en orden de lo mas barato a lo mas caro (para fallar pronto):
#   1. que el Python compile
#   2. que el catalogo sea JSON valido y el generador lo reproduzca
#   3. que el emparejador no haya perdido ordenes (08)
#   4. que las acciones sigan respondiendo (repaso completo)
#   5. la suite entera
#
#   puerta.sh rapida    solo 1-3, para iterar
#   puerta.sh completa  todo, antes de commitear
set -uo pipefail
RAIZ=$(cd -P "$(dirname "${BASH_SOURCE[0]:-$0}")/.." && pwd)
MODO=${1:-completa}
cd "$RAIZ"

fallo() { echo "PUERTA: $1" >&2; exit 1; }

# --- 1. compila el Python -------------------------------------------------
python3 -m compileall -q newelle/src config voz/*.py >/dev/null 2>&1 \
    || fallo "hay Python que no compila"

# --- 2. el catalogo es valido y reproducible ------------------------------
python3 -c "
import json,sys
d=json.load(open('newelle/data/acciones/acciones.json'))
acc=d['acciones']
assert len(acc)>100, f'solo {len(acc)} acciones'
ids=[a['id'] for a in acc]
assert len(ids)==len(set(ids)), 'ids repetidos'
for a in acc:
    assert a.get('frases'), f\"{a['id']} sin frases\"
    assert a.get('acuse'), f\"{a['id']} sin acuse\"
    assert 'señor' in a['acuse'].lower(), f\"{a['id']} no trata de señor\"
    if a.get('captura') and a.get('comando'):
        assert '{argumento' in a['comando'], f\"{a['id']} captura y no usa el argumento\"
" 2>/dev/null || fallo "el catalogo esta mal formado"

# El generador es la fuente: si no reproduce el catalogo, alguien edito el
# producto y el proximo que regenere pierde el trabajo.
cp newelle/data/acciones/acciones.json /tmp/jarvis-puerta-antes.json
(cd newelle/data/acciones && python3 _generar.py >/dev/null 2>&1)
if ! diff -q /tmp/jarvis-puerta-antes.json newelle/data/acciones/acciones.json >/dev/null 2>&1; then
    cp /tmp/jarvis-puerta-antes.json newelle/data/acciones/acciones.json
    fallo "el generador no reproduce el catalogo (se edito el producto)"
fi

# --- 3. el emparejador no ha perdido ordenes ------------------------------
python3 - <<'FIN' || fallo "el emparejador ha perdido ordenes que antes acertaba"
import importlib.util, sys
spec = importlib.util.spec_from_file_location("acc", "newelle/src/utility/acciones.py")
m = importlib.util.module_from_spec(spec); spec.loader.exec_module(m)
cat = m.Catalogo(["newelle/data/acciones/acciones.json"])

# El minimo innegociable: si alguna de estas deja de funcionar, el cambio se
# revierte pase lo que pase. Son las que el usuario usa de verdad.
IMPRESCINDIBLES = [
    ("cuanto espacio queda", "disco_libre"),
    ("cuanta memoria", "memoria"),
    ("que temperatura hay", "temperatura"),
    ("abre una terminal", "terminal_abrir"),
    ("apaga la pantalla", "apagar_pantalla"),
    ("captura de pantalla", "captura_pantalla"),
    ("abre una ventana privada", "ff_firefox_privado"),
    ("pon la ventana a la izquierda", "ventana_izquierda"),
    ("necesito que pongas la pelicula de iron man", "pelicula_abrir"),
    ("podrias abrir una terminal", "terminal_abrir"),
]
malas = []
for frase, esperado in IMPRESCINDIBLES:
    a, _ = cat.busca(frase)
    if a is None or a.id != esperado:
        malas.append(f"{frase!r}→{a.id if a else 'ninguna'} (deberia {esperado})")

# Y la conversacion no puede engancharse a una accion
CHARLA = ["que tal estas", "necesito un abrazo", "quiero contarte una cosa",
          "cuentame un chiste", "que piensas de la vida", "necesito pensar"]
coladas = [c for c in CHARLA if cat.busca(c)[0] is not None]
if malas or coladas:
    for x in malas: print("  perdida:", x, file=sys.stderr)
    for c in coladas: print("  charla colada:", c, file=sys.stderr)
    sys.exit(1)
FIN

[ "$MODO" = rapida ] && { echo "PUERTA: rapida OK"; exit 0; }

# --- 4. las acciones responden -------------------------------------------
SALIDA=$(timeout 900 python3 config/repasar_acciones.py 2>&1 | tail -3)
echo "$SALIDA" | grep -q "de .* responden bien" || fallo "el repaso de acciones no termino"
ROTAS=$(echo "$SALIDA" | grep -oE "fallan: .*" || true)
[ -n "$ROTAS" ] && fallo "hay acciones rotas → $ROTAS"

# --- 5. la suite ----------------------------------------------------------
# Se salta la 07, que abre la app y se queda colgada sin nadie delante.
SUITE=$(cd pruebas && for t in [0-9][0-9]_*.sh; do
            case "$t" in 05_*|07_*) continue;; esac
            timeout 400 "./$t" 2>&1 | tail -1
        done)
FALLOS=$(echo "$SUITE" | grep -oE "[0-9]+ fallan" | awk '{s+=$1} END{print s+0}')
[ "${FALLOS:-0}" -gt 0 ] && fallo "la suite tiene $FALLOS comprobaciones en rojo"

echo "PUERTA: completa OK"
