#!/usr/bin/env python3
"""Banco fijo para medir el emparejador sin volver a grabar.

    banco.py --crear [n]   graba y transcribe una vez (lento)
    banco.py --mide        puntua el catalogo actual (instantaneo)
    banco.py --fallos      enseña que se sigue fallando

## Por que hace falta

La mejora nocturna medía generando audio nuevo cada ronda: nueve horas, 39
rondas, +1,45 puntos de mejora contra ±1,9 puntos de ruido de medida. El ruido
era mayor que la mejora, asi que "¿esto mejora o empeora?" se decidia a cara o
cruz. Hoy ha vuelto a pasar: los flags de Whisper parecian dar +5 puntos con 40
muestras y con 150 no dan nada.

Aqui se graba UNA vez con semilla fija y se guardan las TRANSCRIPCIONES. A
partir de ahi, probar un cambio en el emparejador es instantaneo y determinista:
la misma entrada siempre, asi que dos medidas son comparables de verdad.

## La trampa que ya costo una conclusion equivocada

El 24 % del catalogo son acciones que piden argumento. Diciendolas sueltas
—"busca el texto"— el emparejador NO las dispara, y hace bien: no son una orden.
Un banco que las dice sueltas mide 56 % donde la realidad es 79 %, y hace
parecer que el emparejador esta roto cuando lo que esta roto es el banco.
Por eso `--crear` les añade argumento.
"""
import argparse
import collections
import difflib
import json
import os
import random
import subprocess
import sys
import tempfile

AQUI = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
sys.path.insert(0, AQUI)

from manos.acciones import Catalogo, normaliza          # noqa: E402
from oido.whisper import Oido, es_ruido                 # noqa: E402

RAIZ = os.path.dirname(AQUI)
PIPER = os.path.join(RAIZ, "config", "jarvis-piper.sh")
CATALOGO = os.path.join(AQUI, "datos", "acciones.json")
BANCO = os.path.join(AQUI, "datos", "banco.json")

# Fijas a proposito: son lo que hace el banco reproducible. Cambiarlas obliga a
# rehacerlo, y esa es justamente la señal de que ya no es comparable.
VOCES = ["es_ES-davefx-medium", "es_MX-claude-high", "es_ES-sharvard-medium"]
SEMILLA = 20260815
ARGUMENTOS = ["informes", "descargas", "musica", "el proyecto", "documentos"]


def crear(n: int) -> int:
    cat = Catalogo([CATALOGO])
    rnd = random.Random(SEMILLA)
    conf = [a for a in cat.acciones if a.frases]

    casos = []
    for a in rnd.sample(conf, min(n, len(conf))):
        frase = rnd.choice(a.frases)
        if a.captura:
            frase += " " + rnd.choice(ARGUMENTOS)
        casos.append({"id": a.id, "frase": frase, "voz": rnd.choice(VOCES),
                      "captura": a.captura})

    print(f"  grabando {len(casos)} frases...", flush=True)
    tmp = tempfile.mkdtemp(prefix="banco")
    for i, c in enumerate(casos):
        wav = os.path.join(tmp, f"{i:03d}.wav")
        subprocess.run([PIPER, wav, c["frase"]],
                       env={**os.environ, "JARVIS_PIPER_VOZ": c["voz"],
                            "JARVIS_PIPER_TONO": "1.0"},
                       capture_output=True, timeout=90)
        c["wav"] = wav if os.path.exists(wav) and os.path.getsize(wav) else None

    oreja = Oido(modelo="small", beam=5)
    if not oreja.arranca():
        print("  whisper no arranco")
        return 2
    print("  transcribiendo...", flush=True)
    for i, c in enumerate(casos):
        c["oido"] = oreja.transcribe(c["wav"]) if c["wav"] else ""
        c.pop("wav", None)
        if (i + 1) % 40 == 0:
            print(f"    {i+1}/{len(casos)}", flush=True)
    oreja.para()
    subprocess.run(["rm", "-rf", tmp])

    casos = [c for c in casos if c.get("oido")]
    os.makedirs(os.path.dirname(BANCO), exist_ok=True)
    with open(BANCO, "w", encoding="utf-8") as f:
        json.dump({"semilla": SEMILLA, "modelo": "small", "casos": casos},
                  f, ensure_ascii=False, indent=1)
    print(f"  guardado: {len(casos)} transcripciones en {BANCO}")
    return 0


def carga():
    try:
        with open(BANCO, encoding="utf-8") as f:
            return json.load(f)["casos"]
    except (OSError, json.JSONDecodeError, KeyError):
        return []


def mide(detalle=False) -> int:
    casos = carga()
    if not casos:
        print("  no hay banco: crealo con --crear")
        return 1
    cat = Catalogo([CATALOGO])

    bien = 0
    oye_mal, no_relaciona, otra, ruido = [], [], [], []
    for c in casos:
        t = c["oido"]
        if es_ruido(t):
            ruido.append(c)
            continue
        a, _ = cat.busca(t)
        if a is not None and a.id == c["id"]:
            bien += 1
            continue
        p = difflib.SequenceMatcher(None, normaliza(t), normaliza(c["frase"])).ratio()
        if p < 0.75:
            oye_mal.append((c, t, p))
        elif a is None:
            no_relaciona.append((c, t, p))
        else:
            otra.append((c, t, a.id))

    n = len(casos)
    print(f"  {n} frases · acierto {100*bien/n:.1f} %\n")
    print(f"  {'que pasa':28s} {'n':>4s} {'%':>7s}")
    print("  " + "-" * 44)
    for nombre, k in [("bien", bien), ("oye bien, NO RELACIONA", len(no_relaciona)),
                      ("OYE MAL la frase", len(oye_mal)),
                      ("oye bien, OTRA accion", len(otra)),
                      ("lo tira por ruido", len(ruido))]:
        print(f"  {nombre:28s} {k:4d} {100*k/n:6.1f} %")

    if detalle:
        print("\n  --- oye bien pero no relaciona ---")
        for c, t, p in no_relaciona:
            print(f"    {c['id']:24s} dijo: {c['frase']}")
            print(f"    {'':24s} oyo:  {t}   ({p:.2f})")
        print("\n  --- se la lleva otra accion (lo peor: ejecuta lo que no es) ---")
        for c, t, o in otra:
            print(f"    {c['id']:24s} -> {o}   oyo: {t}")
    return 0


def main() -> int:
    p = argparse.ArgumentParser()
    p.add_argument("--crear", action="store_true")
    p.add_argument("--mide", action="store_true")
    p.add_argument("--fallos", action="store_true")
    p.add_argument("n", nargs="?", type=int, default=150)
    a = p.parse_args()
    if a.crear:
        return crear(a.n)
    return mide(detalle=a.fallos)


if __name__ == "__main__":
    sys.exit(main())
