#!/usr/bin/env python3
"""Que ha aprendido JARVIS del uso, y que conviene cambiar.

    ./mejora.py             que hay en el diario
    ./mejora.py --propone   que acciones o frases faltan
    ./mejora.py --mide      cuanto acierta ahora mismo

## En que se diferencia de la mejora nocturna que se retiro

La de antes corria sola de madrugada, se modificaba el codigo y se commiteaba.
Nueve horas, 39 rondas, cero commits utiles. Fallo por tres cosas, y las tres
estan corregidas aqui:

  1. **Minaba ruido.** Su fuente era un registro donde el 60 % era JARVIS
     oyendose a si mismo y alucinaciones de Whisper. Ahora la guarda del eco y
     el filtro de ruido limpian antes, y el diario apunta la VIA de cada frase,
     asi que el ruido esta separado y etiquetado en vez de mezclado.

  2. **Su medida tenia mas ruido que su mejora.** Generaba audio nuevo cada
     ronda: ±1,9 puntos de ruido contra +1,45 de mejora acumulada, o sea que
     "¿esto mejora o empeora?" se decidia a cara o cruz. Ahora se mide contra
     `datos/banco.json`, que es fijo y determinista.

  3. **Actuaba sola.** Un bucle que se reescribe y se aprueba a si mismo acaba
     pasando siempre. Esto PROPONE y para: lo que se aplica lo decide una
     persona mirandolo.

Y una cuarta, de fondo: aquella tocaba el codigo. Esta solo mira el catalogo,
que es lo unico que se puede validar de verdad con el banco.
"""
import argparse
import collections
import os
import sys

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

from manos import Manos                                    # noqa: E402
from manos.acciones import normaliza, parecido             # noqa: E402
from manos.diario import lee                               # noqa: E402


def color(t, c):
    return f"\033[{c}m{t}\033[0m" if sys.stdout.isatty() else t


# ------------------------------------------------------------------ mirar

def resumen(lineas):
    if not lineas:
        print("  el diario esta vacio: usa JARVIS un rato y vuelve")
        return 1

    vias = collections.Counter(l.get("via", "?") for l in lineas)
    tot = sum(vias.values())
    print(f"\n  {tot} frases desde {lineas[0]['t'][:10]}\n")
    print(f"  {'via':14s} {'n':>5s} {'%':>7s}")
    print("  " + "-" * 32)
    ORDEN = ["catalogo", "cerebro", "respuesta", "ruido", "sin-nombre",
             "bloqueado", "sin-cerebro"]
    for v in ORDEN + [k for k in vias if k not in ORDEN]:
        if v in vias:
            print(f"  {v:14s} {vias[v]:5d} {100*vias[v]/tot:6.1f} %")

    atendidas = vias["catalogo"] + vias["cerebro"]
    if atendidas:
        print(f"\n  de lo ATENDIDO ({atendidas}), el catalogo cogio "
              f"{100*vias['catalogo']/atendidas:.0f} %")
        segs = [l.get("seg", 0) for l in lineas if l.get("via") == "catalogo"]
        segc = [l.get("seg", 0) for l in lineas if l.get("via") == "cerebro"]
        if segs:
            print(f"    catalogo: {sum(segs)/len(segs):.2f} s de media")
        if segc:
            print(f"    cerebro:  {sum(segc)/len(segc):.1f} s de media")
            ahorro = (sum(segc)/len(segc) - (sum(segs)/len(segs) if segs else 0))
            print(f"\n  cada accion que se añada al catalogo ahorra ~{ahorro:.0f} s")

    top = collections.Counter(l["accion"] for l in lineas
                              if l.get("via") == "catalogo" and l.get("accion"))
    if top:
        print("\n  lo que mas usas:")
        for a, n in top.most_common(8):
            print(f"    {n:4d}  {a}")
    return 0


# --------------------------------------------------------------- proponer

def propone(lineas, manos):
    """Lo que fue al cerebro y podria ser una accion del catalogo."""
    # "sin-cerebro" cuenta igual: es lo mismo —una frase que el catalogo no
    # cubrio— y solo se diferencia en que no habia modelo conectado para
    # atenderla. El hueco del catalogo es el mismo.
    alCerebro = [l for l in lineas if l.get("via") in ("cerebro", "sin-cerebro")]
    if not alCerebro:
        print("\n  nada ha ido al cerebro todavia: no hay hueco que rellenar")
        return 0

    # Agrupar lo parecido: si has pedido lo mismo de tres formas, es UNA accion
    # que falta, no tres. Y que lo hayas repetido es justo la señal de que
    # importa.
    grupos = []
    for l in alCerebro:
        t = normaliza(l["oido"])
        if len(t.split()) < 2:
            continue
        for g in grupos:
            if parecido(t, g["patron"]) > 0.62:
                g["frases"].append(l["oido"])
                break
        else:
            grupos.append({"patron": t, "frases": [l["oido"]],
                           "dicho": l.get("dicho", "")})

    grupos.sort(key=lambda g: -len(g["frases"]))
    print(f"\n  {len(alCerebro)} frases fueron al cerebro, en {len(grupos)} grupos.")
    print("  Las que mas se repiten son las acciones que faltan:\n")

    for g in grupos[:12]:
        n = len(g["frases"])
        marca = color(f"{n:3d} veces", "33" if n > 1 else "90")
        print(f"  {marca}  {g['frases'][0][:64]}")
        for f in g["frases"][1:4]:
            print(f"             {color('tambien: ' + f[:56], '90')}")
        if g["dicho"]:
            print(f"             {color('contesto: ' + g['dicho'][:56], '90')}")
        print()

    repetidas = [g for g in grupos if len(g["frases"]) > 1]
    print(f"  {len(repetidas)} se han pedido mas de una vez: por ahi empezaria.\n")
    print(color("  Esto NO cambia nada. Para añadir una accion se edita\n"
                "  datos/_generar.py —que es la FUENTE— y se regenera.\n", "90"))
    return 0


# ------------------------------------------------------------------ medir

def mide(manos):
    """El acierto del emparejador contra el banco fijo."""
    import json
    banco = os.path.join(AQUI, "datos", "banco.json")
    try:
        with open(banco, encoding="utf-8") as f:
            casos = json.load(f)["casos"]
    except OSError:
        print("  no hay banco: pruebas/banco.py --crear")
        return 1
    bien = otra = 0
    for c in casos:
        a, _ = manos.busca(c["oido"])
        if a is None:
            continue
        if a.id == c["id"]:
            bien += 1
        else:
            otra += 1
    print(f"\n  acierto {100*bien/len(casos):.1f} % sobre {len(casos)} frases")
    print(f"  ordenes que acaban en la accion equivocada: {otra}")
    print(color("\n  Este numero es comparable entre dias: el banco esta grabado\n"
                "  una vez y no cambia. Era lo que le faltaba a la mejora\n"
                "  nocturna, cuyo ruido de medida era mayor que su mejora.\n", "90"))
    return 0


def main() -> int:
    p = argparse.ArgumentParser(description="Que ha aprendido JARVIS del uso")
    p.add_argument("--propone", action="store_true")
    p.add_argument("--mide", action="store_true")
    a = p.parse_args()

    manos = Manos()
    lineas = lee()
    if a.mide:
        return mide(manos)
    if a.propone:
        return propone(lineas, manos)
    return resumen(lineas)


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