"""Corrector de referencia del banco público abierto de Derecho español de AIM LEX.

Uso: corregir(caso, respuesta) para los bancos de respuestas; evaluar_caso_banco(caso, citas) para el de
verificación (citas: [{"veredicto": "verde" | "amarillo" | "rojo"}]). Sin dependencias."""

from __future__ import annotations

import re
import unicodedata


def normalizar_para_banco(t: str) -> str:
    """Minúsculas, acentos fuera, y TODO espacio —incluido el irrompible— a un
    espacio simple.

    El 05/09/2026 el banco mercantil marcó una respuesta CORRECTA como caída:
    decía «60 días» con espacio irrompible y el caso exigía «60 dias». Un banco
    que falla por tipografía enseña a ignorar sus alarmas.
    """
    t = unicodedata.normalize("NFKD", t or "")
    t = "".join(ch for ch in t if not unicodedata.combining(ch))
    return _cifras(re.sub(r"\s+", " ", t).lower().strip())


# LA CIFRA, DIGA COMO LA DIGA (01/10/2026). El banco mercantil marcó como caída
# una respuesta CORRECTA a la franquicia: decía «veinte días hábiles», copiando
# el Real Decreto, y el caso exigía «20 dias». Es el mismo fallo que el del
# espacio irrompible: un banco que falla por cómo se escribe la cifra enseña a
# ignorar sus alarmas. Se convierten los números en letra a cifras, a los DOS
# lados (respuesta y término), así que «treinta y tres» y «33» casan siempre.
_UNIDADES = {
    "cero": 0, "un": 1, "uno": 1, "una": 1, "dos": 2, "tres": 3, "cuatro": 4, "cinco": 5,
    "seis": 6, "siete": 7, "ocho": 8, "nueve": 9, "diez": 10, "once": 11, "doce": 12,
    "trece": 13, "catorce": 14, "quince": 15, "dieciseis": 16, "diecisiete": 17,
    "dieciocho": 18, "diecinueve": 19, "veinte": 20, "veintiun": 21, "veintiuno": 21,
    "veintiuna": 21, "veintidos": 22, "veintitres": 23, "veinticuatro": 24,
    "veinticinco": 25, "veintiseis": 26, "veintisiete": 27, "veintiocho": 28,
    "veintinueve": 29, "cien": 100,
}
_DECENAS = {"treinta": 30, "cuarenta": 40, "cincuenta": 50, "sesenta": 60,
            "setenta": 70, "ochenta": 80, "noventa": 90}
_COMPUESTO = re.compile(r"\b(%s) y (uno|una|un|dos|tres|cuatro|cinco|seis|siete|ocho|nueve)\b"
                        % "|".join(_DECENAS))
_SIMPLE = re.compile(r"\b(%s)\b" % "|".join(sorted(list(_UNIDADES) + list(_DECENAS), key=len, reverse=True)))


def _cifras(t: str) -> str:
    t = _COMPUESTO.sub(lambda m: str(_DECENAS[m.group(1)] + _UNIDADES[m.group(2)]), t)
    return _SIMPLE.sub(lambda m: str(_UNIDADES.get(m.group(1), _DECENAS.get(m.group(1)))), t)


# MENCIONAR NO ES AFIRMAR (12/09/2026). Los bancos declaran términos
# `prohibe` —la cifra derogada, el régimen que no aplica— y se buscaban como
# simple subcadena. Medido: a «indemnización por despido improcedente» el
# sistema respondía **33 días** (correcto) y además explicaba que hasta la
# reforma de 2012 eran 45; el banco lo marcaba como fallo por contener «45
# dias». Esa respuesta es mejor, no peor, y un banco que la castiga empuja al
# producto a ocultar contexto útil.
#
# Un término prohibido cuenta solo si se AFIRMA: si aparece sin marca histórica
# alrededor y antes de que el texto haya dicho lo correcto.
_MARCAS_HISTORICAS = (
    "antes de", "anterior a", "anteriormente", "redaccion anterior",
    "antigua redaccion", "hasta la reforma", "tras la reforma", "reforma de",
    "derogad", "ya no", "dejo de", "con anterioridad", "histor", "en su dia",
    "hasta 20", "desde 20", "sustituy", "paso de", "pasaron de",
)
_VENTANA_HISTORICA = 160


def se_afirma(texto: str, termino: str, desde: int = -1) -> bool:
    """¿El texto AFIRMA ese término, o solo lo menciona como contexto?

    `texto` y `termino` ya normalizados. `desde` es la posición donde el texto
    dice lo correcto: lo que viene después es explicación, no la respuesta.
    """
    t, term = texto or "", (termino or "").strip()
    if not t or not term:
        return False
    i = t.find(term)
    while i >= 0:
        alrededor = t[max(0, i - _VENTANA_HISTORICA):i + len(term) + _VENTANA_HISTORICA]
        historico = any(marca in alrededor for marca in _MARCAS_HISTORICAS)
        if not historico and (desde < 0 or i < desde):
            return True
        i = t.find(term, i + 1)
    return False


_NEGACIONES = ("no", "nunca", "ni", "tampoco", "jamas")


def afirma_sin_negar(texto: str, frase: str) -> bool:
    """¿Aparece `frase` AFIRMADA —no negada— en `texto`? (Ambos ya normalizados.)

    El banco de premisas marcaba como «da la razón a la premisa» la respuesta
    «La reforma de 2015 NO amplió a quince años el plazo, sino que lo redujo a
    cinco», una corrección impecable, porque encontraba «amplió a quince» dentro
    de «no amplió a quince». Durante días eso se leyó como el fallo más grave del
    producto —«el sistema valida premisas falsas»— cuando era un fallo del
    instrumento. Una aparición precedida de negación (en las tres palabras
    anteriores) no es una afirmación.
    """
    if not texto or not frase:
        return False
    i = texto.find(frase)
    while i >= 0:
        previas = texto[max(0, i - 40):i].split()[-3:]
        if not any(p.strip(".,;:«»\"'()") in _NEGACIONES for p in previas):
            return True
        i = texto.find(frase, i + 1)
    return False


def corregir(caso: dict, respuesta: str) -> dict:
    """El corrector de referencia de los bancos de respuestas (exige / prohibe)."""
    _n = normalizar_para_banco
    r = _n(respuesta or "")
    exige = caso.get("exige") or []
    falta = [] if (not exige or any(_n(x) in r for x in exige)) else list(exige)
    cuela = [x for x in caso.get("prohibe") or [] if _n(x) in r]
    return {"bien": not falta and not cuela, "falta": falta, "cuela": cuela}


def evaluar_caso_banco(caso: dict, citas: list) -> bool:
    """La MISMA logica que scripts/bench_verifica.py, extraida para que el ciclo
    y el script no diverjan. Dos evaluadores acaban diciendo cosas distintas."""
    if not citas:
        obtenido = "sin_citas"
    else:
        obtenido = ("rojo" if any(c.get("veredicto") == "rojo" for c in citas)
                    else "no_rojo")
    if caso["espera"] == "no_verde":
        obtenido = ("verde" if any(c.get("veredicto") == "verde" for c in citas)
                    else "no_verde")
    # «DEBE SALIR EN VERDE» (0.911). El banco tenia 17 casos «no_rojo» —citas
    # correctas que no pueden acusarse— y el AMBAR cumple ese criterio: el fallo
    # de los apartados, que dejo 18 de 18 citas sin confirmar el 11/09/2026,
    # habria pasado este banco EN VERDE. Cazar las citas falsas es media
    # herramienta; nadie comprobaba que las verdaderas se confirmasen.
    if caso["espera"] == "verde":
        obtenido = ("verde" if any(c.get("veredicto") == "verde" for c in citas)
                    else ("sin_citas" if not citas else "no_verde"))
    return obtenido == caso["espera"]
