#!/bin/bash
# agilev-prova-dal-posto.sh — la prova di AgileV DAL BOX DEL CLIENTE, in un comando.
#
# A che serve. Dal box di AgileV sappiamo che il servizio risponde e quanto ci mette (log del bordo). Quello che
# da qui NON si vede è se dal TUO box, con la TUA chiave, attraverso la TUA rete, il servizio risponde, i vettori
# sono gli stessi con cui hai costruito il tuo indice e gli errori sono quelli documentati. Questa è quella prova.
# Va eseguita sul box del prodotto che consuma (nis2, lg231, trpg, taxai, aiepod, core), non su agilev-01.
#
# Il controllo che conta davvero è il COSENO COL VETTORE CUSTODITO: la frase fissa «Il contratto scade il
# 30 settembre» ha un vettore congelato qui dentro. Se il coseno resta ~1, i vettori che ricevi oggi sono
# compatibili con quelli che hai già in Qdrant / pgvector / MySQL. Se scende, il tuo indice non è più
# confrontabile con le query nuove: è il guasto più silenzioso che AgileV possa avere, e si vede solo da qui.
#
# Cosa NON fa: non legge i tuoi dati e non li manda da nessuna parte. I testi che spedisce sono tre frasi fisse
# scritte in questo file. Non stampa mai la tua chiave. Non scrive niente fuori da una cartella temporanea 0700.
#
# Cosa costa: in modalità piena spedisce 7 richieste (6 embed + 1 rerank) di poche decine di caratteri, contate
# nei tuoi consumi del mese. Con --niente-consumo spedisce SOLO chiamate che non consumano nulla (/health,
# /v1/models, i due 401 e /v1/usage): utile per provare la configurazione senza toccare la fattura.
#
# uso: agilev-prova-dal-posto.sh [--chiave FILE | AGILEV_API_KEY=...] [--url …] [--niente-consumo] [--ripetizioni N] [--riga]
#   --chiave FILE   file che contiene SOLO la chiave (consigliato 0600). In alternativa la variabile AGILEV_API_KEY.
#   --url URL       default https://api.agilev.agile.software
#   --niente-consumo  solo i controlli che non consumano richieste né quota
#   --ripetizioni N  quante misure di tempo su una frase corta (default 3): serve la mediana, non un colpo solo
#   --attesa-avvio S quanto aspettare che i modelli si carichino se il servizio si sta riscaldando (default 90).
#                    Subito dopo un deploy la porta risponde ma `loaded` è ancora false: aspettare non è un
#                    guasto, e con 0 il banco non aspetta affatto.
#   --riga          stampa solo la riga finale da rimandare ad AgileV
# Esce 0 se nessun KO. Serve: bash, curl, python3 (niente librerie).
set -u
URL=https://api.agilev.agile.software; CHIAVE=""; CONSUMA=1; RIP=3; SOLO_RIGA=0; ATTESA_AVVIO=90
while [ $# -gt 0 ]; do
  case "$1" in
    --chiave) CHIAVE=$2; shift 2;; --url) URL=$2; shift 2;; --niente-consumo) CONSUMA=0; shift;;
    --ripetizioni) RIP=$2; shift 2;; --riga) SOLO_RIGA=1; shift;;
    --attesa-avvio) ATTESA_AVVIO=$2; shift 2;;
    -h|--help) sed -n 2,30p "$0"; exit 0;;
    *) echo "argomento sconosciuto: $1" >&2; exit 2;;
  esac
done
URL=${URL%/}
command -v curl >/dev/null || { echo "serve curl" >&2; exit 2; }
command -v python3 >/dev/null || { echo "serve python3" >&2; exit 2; }

TMP=$(mktemp -d); chmod 700 "$TMP"; trap 'rm -rf "$TMP"' EXIT
KO=0; N=0
uscita(){ [ "$SOLO_RIGA" = 1 ] || printf '%s\n' "$1"; }
ok()   { N=$((N+1)); uscita "OK  $1"; }
ko()   { N=$((N+1)); KO=$((KO+1)); uscita "KO  $1"; }
salta(){ uscita "--  $1"; }
J() { python3 -c "import json,sys
try: d=json.load(sys.stdin)
except Exception: sys.exit(1)
$1" 2>/dev/null; }

# La chiave viaggia in un file di intestazioni 0600: non compare in `ps`, non finisce in un log, non si stampa.
if [ -z "$CHIAVE" ] && [ -n "${AGILEV_API_KEY:-}" ]; then printf '%s' "$AGILEV_API_KEY" > "$TMP/k"; chmod 600 "$TMP/k"; CHIAVE="$TMP/k"; fi
HK=""
if [ -n "$CHIAVE" ]; then
  if [ ! -r "$CHIAVE" ]; then echo "file chiave '$CHIAVE' non leggibile" >&2; exit 2; fi
  HK="$TMP/h"; { printf 'Authorization: Bearer '; tr -d '\n\r' < "$CHIAVE"; printf '\n'; } > "$HK"; chmod 600 "$HK"
fi
CT="Content-Type: application/json"
OWNER="?"; CONTRATTO="?"; COSENO="n/d"; MEDIANA="n/d"; SCOPES=""

# ------------------------------------------------------------------ 1. la rete: nome, TCP, TLS
HOST=$(printf '%s' "$URL" | sed -e 's|^[a-z]*://||' -e 's|/.*||' -e 's|:.*||')
T=$(curl -s -o /dev/null -m 20 -w '%{http_code} %{time_namelookup} %{time_connect} %{time_appconnect} %{remote_ip}' "$URL/health" 2>/dev/null)
set -- $T; CODE=${1:-000}; TDNS=${2:-0}; TCON=${3:-0}; TTLS=${4:-0}; RIP_ADDR=${5:-}
if [ "$CODE" = "000" ]; then ko "rete: $URL non risponde da questo box (nome, firewall o WireGuard)"
else ok "rete: $HOST -> ${RIP_ADDR:-?} (nome ${TDNS}s, TCP ${TCON}s, TLS ${TTLS}s)"; fi
case "$URL" in https://*)
  if command -v openssl >/dev/null; then
    FINE_CERT=$(echo | openssl s_client -connect "$HOST:443" -servername "$HOST" 2>/dev/null | openssl x509 -noout -enddate 2>/dev/null | cut -d= -f2)
    if [ -n "$FINE_CERT" ]; then
      GG=$(python3 -c "import sys,time,calendar;t=time.strptime(sys.argv[1],'%b %d %H:%M:%S %Y %Z');print(int((calendar.timegm(t)-time.time())//86400))" "$FINE_CERT" 2>/dev/null)
      if [ -n "$GG" ] && [ "$GG" -gt 14 ] 2>/dev/null; then ok "certificato: valido ancora $GG giorni"
      elif [ -n "$GG" ]; then ko "certificato: scade fra $GG giorni"
      else salta "certificato: data non interpretabile ($FINE_CERT)"; fi
    else salta "certificato: openssl non ha letto la catena"; fi
  else salta "certificato: openssl non installato"; fi;;
esac

# ------------------------------------------------------------------ 2. il servizio, senza chiave (non consuma)
H=$(curl -s -m 20 "$URL/health")
if [ -z "$H" ]; then ko "/health non risponde"; else
  CONTRATTO=$(echo "$H" | J 'print(d.get("contract",""))')
  L=$(echo "$H" | J 'print(d.get("loaded"))'); D=$(echo "$H" | J 'print(d.get("dim"))'); MO=$(echo "$H" | J 'print(d.get("model",""))')
  # `loaded:false` non è un guasto: è un servizio che si sta riscaldando. Dopo un deploy o un riavvio
  # la porta risponde subito e i modelli arrivano dopo (misurato il 29/9: pronti in 8 s dalla cache calda,
  # ~40 s a freddo). Chi dava KO qui otteneva un referto ROSSO su un servizio SANO, con i vettori giusti:
  # allora si aspetta, e si dice quanto si è aspettato. KO solo se non arriva entro l'attesa.
  if [ "$L" != "True" ]; then
    uscita "..  /health: loaded=$L — il servizio si sta riscaldando, aspetto fino a ${ATTESA_AVVIO}s"
    i=0
    while [ "$i" -lt "$ATTESA_AVVIO" ]; do
      sleep 2; i=$((i+2))
      H=$(curl -s -m 20 "$URL/health")
      L=$(echo "$H" | J 'print(d.get("loaded"))')
      [ "$L" = "True" ] && break
    done
    CONTRATTO=$(echo "$H" | J 'print(d.get("contract",""))')
    ASPETTATO=$i
  fi
  if [ "$L" = "True" ]; then
    [ "${ASPETTATO:-0}" = 0 ] && ok "/health: modelli caricati, contratto $CONTRATTO" \
                              || ok "/health: modelli caricati dopo ${ASPETTATO}s di riscaldamento, contratto $CONTRATTO"
  else
    ko "/health: loaded=$L anche dopo ${ATTESA_AVVIO}s (il servizio non si riscalda: fermati e avvisa AgileV)"
    uscita "---- $N controlli, $KO KO"
    L2="RIGA: agilev-prova-dal-posto non-pronto $(date -u +%Y-%m-%dT%H:%MZ) contratto=$CONTRATTO controlli=$N KO=$KO"
    [ "$SOLO_RIGA" = 1 ] && echo "$L2" || uscita "$L2"
    exit 1
  fi
  { [ "$D" = "1024" ] && [ "$MO" = "bge-m3" ]; } && ok "/health: modello bge-m3, 1024 dimensioni" || ko "/health: model=$MO dim=$D (attesi bge-m3 e 1024)"
  # La FILA del lavoro (campo `coda`, dal 29/9). Non è un controllo che possa dare KO: un servizio in
  # fila è VIVO, e chiamarlo malato farebbe riavviare chi stava soltanto aspettando. Serve a voi per
  # leggere i tempi: con la fila `satura` il tempo che misurate qui sotto è ATTESA, non calcolo.
  # Se il campo non c'è (servizio più vecchio) la riga non esce: un campo assente non è un guasto.
  CODA=$(echo "$H" | J 'c=d.get("coda")
if isinstance(c,dict) and c.get("stato"): print(c["stato"], c.get("occupati"), c.get("posti"), c.get("in_attesa"))')
  if [ -n "$CODA" ]; then
    set -- $CODA
    case "$1" in
      satura)       uscita "..  /health: fila del lavoro SATURA ($2 posti su $3 occupati, $4 in attesa) — i tempi qui sotto sono attesa, non calcolo" ;;
      indecidibile) uscita "..  /health: fila del lavoro non leggibile (il servizio dice che non lo sa: non e' un guasto)" ;;
      *)            uscita "..  /health: fila del lavoro $1 ($2 posti su $3 occupati)" ;;
    esac
  fi
fi
M=$(curl -s -m 20 "$URL/v1/models")
ELENCO=$(echo "$M" | J 'ms=[x["id"] for x in d["data"]]; print("agilev-m3" in ms, "agilev-rerank" in ms)')
case "$ELENCO" in "True True") ok "/v1/models: agilev-m3 e agilev-rerank disponibili";; *) ko "/v1/models: $(echo "$M" | head -c 120)";; esac

# ------------------------------------------------------------------ 3. il contratto degli errori (non consuma)
C=$(curl -s -m 20 -o "$TMP/e1" -w '%{http_code}' -X POST "$URL/v1/embeddings" -H "$CT" -d '{"input":["prova"]}')
E=$(J 'print(d["error"]["code"])' < "$TMP/e1")
{ [ "$C" = "401" ] && [ "$E" = "KEY_MISSING" ]; } && ok "senza chiave: 401 KEY_MISSING" || ko "senza chiave: $C $E (attesi 401 KEY_MISSING)"
C=$(curl -s -m 20 -o "$TMP/e2" -w '%{http_code}' -X POST "$URL/v1/embeddings" -H "$CT" -H 'Authorization: Bearer chiave-che-non-esiste' -d '{"input":["prova"]}')
E=$(J 'print(d["error"]["code"])' < "$TMP/e2")
{ [ "$C" = "401" ] && [ "$E" = "KEY_INVALID" ]; } && ok "chiave sbagliata: 401 KEY_INVALID" || ko "chiave sbagliata: $C $E (attesi 401 KEY_INVALID)"

if [ -z "$HK" ]; then
  salta "la tua chiave: non indicata (--chiave FILE oppure AGILEV_API_KEY): saltati i controlli che contano"
  uscita "---- $N controlli, $KO KO"
  [ "$SOLO_RIGA" = 1 ] && echo "RIGA: agilev-prova-dal-posto senza-chiave $(date -u +%Y-%m-%dT%H:%MZ) contratto=$CONTRATTO controlli=$N KO=$KO"
  exit $([ "$KO" = 0 ] && echo 0 || echo 1)
fi

# ------------------------------------------------------------------ 4. la tua chiave: chi sei e cosa puoi (non consuma)
#    /v1/usage non passa dal freno: leggere i propri consumi è gratis e non avvicina al 429 (misurato 28/9).
U0=$(curl -s -m 20 "$URL/v1/usage" -H @"$HK")
OWNER=$(echo "$U0" | J 'print(d.get("owner",""))')
if [ -z "$OWNER" ]; then
  # Chiave revocata, sbagliata o servizio muto: inutile spendere altre richieste, e sei righe di KO a cascata
  # nascondono l'unica che conta. Si ferma qui e dice il codice che ha ricevuto.
  ko "la tua chiave non e' stata riconosciuta: $(echo "$U0" | J 'print(d["error"]["code"], "-", d["error"]["message"])' || echo "$U0" | head -c 160)"
  uscita "---- $N controlli, $KO KO"
  L="RIGA: agilev-prova-dal-posto chiave-non-valida $(date -u +%Y-%m-%dT%H:%MZ) contratto=$CONTRATTO controlli=$N KO=$KO"
  [ "$SOLO_RIGA" = 1 ] && echo "$L" || uscita "$L"
  exit 1
else
  SCOPES=$(echo "$U0" | J 'print(",".join(d.get("scopes",[])))')
  RPM=$(echo "$U0" | J 'print(d.get("rate_per_min"))'); Q=$(echo "$U0" | J 'print(d.get("quota_chars_month") or "illimitata")')
  ok "la tua chiave: cliente '$OWNER', ambiti $SCOPES, $RPM richieste al minuto, quota $Q"
  echo "$U0" | J 'import time
m=time.strftime("%Y-%m"); r=[x for x in d["usage"] if x["month"]==m]
print("consumi del mese %s: %d richieste, %d testi, %d caratteri" % (m, sum(x["requests"] for x in r), sum(x["texts"] for x in r), sum(x["chars"] for x in r))) if r else print("consumi del mese %s: nessuno" % m)' > "$TMP/u0.txt"
  [ -s "$TMP/u0.txt" ] && ok "/v1/usage: $(cat "$TMP/u0.txt") (lettura gratis, non consuma)" || ko "/v1/usage: risposta non interpretabile"
fi
R0=$(echo "$U0" | J 'print(sum(x["requests"] for x in d["usage"]))'); R0=${R0:-0}

if [ "$CONSUMA" = 0 ]; then
  salta "vettori, cache, rerank e tempi: saltati (--niente-consumo)"
  uscita "---- $N controlli, $KO KO"
  L="RIGA: agilev-prova-dal-posto $OWNER $(date -u +%Y-%m-%dT%H:%MZ) contratto=$CONTRATTO controlli=$N KO=$KO (niente-consumo)"
  [ "$SOLO_RIGA" = 1 ] && echo "$L" || uscita "$L"
  exit $([ "$KO" = 0 ] && echo 0 || echo 1)
fi

# ------------------------------------------------------------------ 5. i vettori sono quelli del tuo indice
# Frase fissa e vettore custodito (float16, base64): lo stesso riferimento della prova di accettazione di AgileV.
FRASE="Il contratto scade il 30 settembre"
RIFERIMENTO16='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'
printf '{"model":"agilev-m3","input":["%s"]}' "$FRASE" > "$TMP/emb.json"
SPESE=0
T1=$(curl -s -m 180 -o "$TMP/r1" -D "$TMP/d1" -w '%{time_total}' -X POST "$URL/v1/embeddings" -H @"$HK" -H "$CT" --data @"$TMP/emb.json"); SPESE=$((SPESE+1))
DIM=$(J 'print(len(d["data"][0]["embedding"]))' < "$TMP/r1")
if [ "$DIM" != "1024" ]; then ko "/v1/embeddings: $(head -c 200 "$TMP/r1")"
else
  ok "/v1/embeddings: vettore da 1024 in ${T1}s"
  COSENO=$(python3 - "$TMP/r1" "$RIFERIMENTO16" <<'PY'
import json,sys,struct,base64,math
v=json.load(open(sys.argv[1]))["data"][0]["embedding"]
r=struct.unpack("<%de"%len(v), base64.b64decode(sys.argv[2]))
nv=math.sqrt(sum(x*x for x in v)); nr=math.sqrt(sum(x*x for x in r))
print("%.6f %.4f"%(sum(a*b for a,b in zip(v,r))/(nv*nr), nv))
PY
)
  NORMA=${COSENO#* }; COSENO=${COSENO%% *}
  case "$COSENO" in 0.9999*|1.0*)
      ok "VETTORI COMPATIBILI col tuo indice: coseno $COSENO sul vettore custodito";;
    *) ko "VETTORI CAMBIATI: coseno $COSENO (atteso >0,9999). Il tuo indice non e' piu' confrontabile: fermati e avvisa AgileV";;
  esac
  case "$NORMA" in 0.99*|1.0*) ok "vettore normalizzato (norma $NORMA): il coseno e' il prodotto scalare";; *) ko "norma $NORMA (attesa 1,0)";; esac
fi
# le intestazioni del freno (contratto 1.2 in avanti): sulle quattro operazioni di calcolo
RL=$(grep -i '^x-ratelimit-remaining:' "$TMP/d1" | tr -d '\r' | awk '{print $2}')
case "$CONTRATTO" in 1.0|1.1) salta "intestazioni X-RateLimit-*: arrivano col contratto 1.2 (qui $CONTRATTO)";;
  *) [ -n "$RL" ] && ok "freno visibile: X-RateLimit-Remaining $RL (e Retry-After sul 429)" || ko "intestazioni X-RateLimit-* assenti sul calcolo (attese dalla 1.2)";; esac

# ------------------------------------------------------------------ 6. la cache: la stessa frase costa quasi nulla
T2=$(curl -s -m 180 -o "$TMP/r2" -w '%{time_total}' -X POST "$URL/v1/embeddings" -H @"$HK" -H "$CT" --data @"$TMP/emb.json"); SPESE=$((SPESE+1))
IDENTICO=$(python3 -c "import json,sys
a=json.load(open(sys.argv[1]))['data'][0]['embedding']; b=json.load(open(sys.argv[2]))['data'][0]['embedding']
print('si' if a==b else 'no')" "$TMP/r1" "$TMP/r2" 2>/dev/null)
[ "$IDENTICO" = "si" ] && ok "ripetibile: la stessa frase da' lo stesso vettore, bit per bit (${T1}s -> ${T2}s)" \
                       || ko "la stessa frase NON ha dato lo stesso vettore: $(head -c 160 "$TMP/r2")"

# ------------------------------------------------------------------ 7. il formato storico /embed
V=$(curl -s -m 180 -X POST "$URL/embed" -H @"$HK" -H "$CT" -d '{"texts":["prova del formato storico"]}'); SPESE=$((SPESE+1))
DV=$(echo "$V" | J 'print(len(d["vectors"][0]), d.get("dim"))')
[ "$DV" = "1024 1024" ] && ok "/embed (formato storico): vectors da 1024" || ko "/embed: $(echo "$V" | head -c 160)"

# ------------------------------------------------------------------ 8. il reranker (se la tua chiave lo copre)
SPESE_RR=0
case ",$SCOPES," in *,rerank,*)
  printf '{"model":"agilev-rerank","query":"scadenza del contratto","documents":["%s","Ricetta della torta di mele"]}' "$FRASE" > "$TMP/rr.json"
  RR=$(curl -s -m 180 -X POST "$URL/v1/rerank" -H @"$HK" -H "$CT" --data @"$TMP/rr.json"); SPESE_RR=1
  TOP=$(echo "$RR" | J 'print(d["results"][0]["index"])')
  [ "$TOP" = "0" ] && ok "/v1/rerank: mette per primo il documento pertinente" || ko "/v1/rerank: $(echo "$RR" | head -c 160)";;
  *) salta "/v1/rerank: la tua chiave non ha l'ambito rerank ($SCOPES)";;
esac

# ------------------------------------------------------------------ 9. i tempi VISTI DA QUI
if [ "$RIP" -gt 0 ] 2>/dev/null; then
  printf '{"model":"agilev-m3","input":["misura di tempo dal box del cliente"]}' > "$TMP/t.json"
  TEMPI=""
  i=0; while [ $i -lt "$RIP" ]; do
    t=$(curl -s -o /dev/null -m 180 -w '%{time_total}' -X POST "$URL/v1/embeddings" -H @"$HK" -H "$CT" --data @"$TMP/t.json")
    TEMPI="$TEMPI $t"; SPESE=$((SPESE+1)); i=$((i+1))
  done
  MEDIANA=$(python3 -c "import sys,statistics
v=[float(x) for x in sys.argv[1:]]; print('%.3f %.3f'%(statistics.median(v), max(v)))" $TEMPI)
  MASSIMO=${MEDIANA#* }; MEDIANA=${MEDIANA%% *}
  ok "tempi da questo box su $RIP misure: mediana ${MEDIANA}s, massimo ${MASSIMO}s (andata e ritorno compresi)"
fi

# ------------------------------------------------------------------ 10. i consumi si sono mossi di quello che hai speso
U1=$(curl -s -m 20 "$URL/v1/usage" -H @"$HK")
R1=$(echo "$U1" | J 'print(sum(x["requests"] for x in d["usage"]))'); R1=${R1:-0}
ATTESE=$((SPESE+SPESE_RR)); DELTA=$((R1-R0))
[ "$DELTA" = "$ATTESE" ] && ok "contatori: +$DELTA richieste, esattamente quelle spedite da questa prova" \
                         || ko "contatori: +$DELTA richieste, ne ho spedite $ATTESE (se non sei l'unico a usare la chiave puo' essere normale)"

uscita "---- $N controlli, $KO KO"
L="RIGA: agilev-prova-dal-posto $OWNER $(date -u +%Y-%m-%dT%H:%MZ) contratto=$CONTRATTO controlli=$N KO=$KO coseno=$COSENO mediana=${MEDIANA}s"
[ "$SOLO_RIGA" = 1 ] && echo "$L" || uscita "$L"
[ "$KO" = 0 ]
