"""Dominio puro del módulo "Reacciones Químicas" (4.1).

Sin dependencias de Flask. Define la clave de respuestas y la calificación por
tema (cada tema sobre 100), con normalización de respuestas y descuento por
incidencias. Totalmente testeable con unittest.
"""

from reacciones_config import DESCUENTO_INCIDENTE, TEMAS

# Clave de respuestas correctas por tema.
#  - Selección múltiple (4.1.1, 4.1.2): letras A/B/C/D.
#  - Respuesta corta (4.1.3): términos normalizados.
#  - Desplegable de clasificación (4.1.4): tipo de reacción (sin acentos).
CLAVE = {
    "4.1.1": {"0": "B", "1": "A", "2": "C", "3": "D", "4": "B"},
    "4.1.2": {"0": "A", "1": "B", "2": "A", "3": "C", "4": "D"},
    "4.1.3": {
        "0": "reactivos",
        "1": "productos",
        "2": "ecuacionquimica",
        "3": "coeficiente",
        "4": "gaseoso",
    },
    "4.1.4": {
        "0": "sintesis", "1": "descomposicion",
        "2": "sustitucionsimple", "3": "doblesustitucion", "4": "combustion",
        "5": "sintesis", "6": "descomposicion",
        "7": "sustitucionsimple", "8": "doblesustitucion", "9": "combustion",
    },
}

_MAP_ACENTOS = str.maketrans(
    "áéíóúüñÁÉÍÓÚÜÑ", "aeiouunAEIOUUN"
)


def normalizar(valor):
    """Normaliza una respuesta: minúsculas, sin acentos, sin espacios extra."""
    if not isinstance(valor, str):
        return ""
    texto = valor.replace(" ", "").replace("_", "")
    texto = texto.translate(_MAP_ACENTOS).lower()
    return texto.strip()


def puntos_posibles(tema):
    """Puntos posibles del tema (según TEMAS)."""
    return TEMAS[tema][2]


def calificar_tema(tema, respuestas):
    """Califica un tema y devuelve {puntos, posibles, nota_100}.

    respuestas: dict {"0": valor, "1": ...}. Solo se cuentan claves conocidas.
    """
    if tema not in CLAVE:
        raise ValueError(f"Tema desconocido: {tema}")
    clave = CLAVE[tema]
    posibles = puntos_posibles(tema)
    puntos = 0
    for indice, correcto in clave.items():
        valor = respuestas.get(indice)
        if valor is None:
            continue
        if normalizar(valor) == normalizar(correcto):
            puntos += 1
    nota_100 = round(puntos / posibles * 100) if posibles else 0
    return {"puntos": puntos, "posibles": posibles, "nota_100": nota_100}


def nota_final(nota_100, n_incidentes):
    """Aplica descuento por incidencias a una nota sobre 100 (piso en 0)."""
    return max(0, nota_100 - DESCUENTO_INCIDENTE * n_incidentes)


def nota_modulo(respuestas_por_tema, n_incidentes):
    """Calcula la nota final unificada del módulo (sobre 100).

    Suma los puntos obtenidos en todos los temas y divide entre los puntos
    totales posibles, luego aplica el descuento por incidentes.
    """
    total_puntos = 0
    total_posibles = 0
    for tema in TEMAS:
        resumen = calificar_tema(tema, respuestas_por_tema.get(tema, {}))
        total_puntos += resumen["puntos"]
        total_posibles += resumen["posibles"]
    if total_posibles == 0:
        return 0
    nota_bruta = round(total_puntos / total_posibles * 100)
    return nota_final(nota_bruta, n_incidentes)


def calificar_examen(respuestas_por_tema, n_incidentes):
    """Califica todos los temas y aplica el descuento global.

    Devuelve: {nota_final, notas: {tema: nota_final_100}, notas_brutas: {...},
               incidentes, descuento_total}
    """
    notas = {}
    notas_brutas = {}
    for tema in TEMAS:
        resumen = calificar_tema(tema, respuestas_por_tema.get(tema, {}))
        notas_brutas[tema] = resumen["nota_100"]
        notas[tema] = nota_final(resumen["nota_100"], n_incidentes)
    nota_final_modulo = nota_modulo(respuestas_por_tema, n_incidentes)
    return {
        "nota_final": nota_final_modulo,
        "notas": notas,
        "notas_brutas": notas_brutas,
        "incidentes": n_incidentes,
        "descuento_total": DESCUENTO_INCIDENTE * n_incidentes,
    }