"""Módulo de dominio puro para calificar la evaluación RA2 CDT1 de Química.

No depende del framework web: puede probarse con unittest en cualquier entorno.
Contiene la clave de respuestas (extraída del examen original de RA2 CDT1 —
Aniones, Cationes e Iones), la lógica de calificación y el cálculo de
penalizaciones anti-trampa.

Cada una de las 20 preguntas vale 5 puntos (total académico: 100).
Respuesta: -5 puntos por cada incidente anti-trampa; nota final con piso en 0.
"""

import re

# ---------------------------------------------------------------------------
# Clave de respuestas (20 preguntas × 5 puntos = 100 puntos)
# ---------------------------------------------------------------------------
# Cada elemento es la lista de respuestas aceptables para esa pregunta.
# Ambas partes (respuesta del estudiante y alternativas) se normalizan
# antes de comparar, tolerando mayúsculas, acentos y espacios.

CLAVE = [
    #  1. Símbolo del catión sodio.
    ["Na+", "na+"],
    #  2. Símbolo del anión cloruro.
    ["Cl-", "cl-"],
    #  3. ¿Cuántos protones tiene el oxígeno?
    ["8"],
    #  4. ¿Cuántos electrones tiene el carbono neutro?
    ["6"],
    #  5. Mg2+ es:
    ["catión", "cation"],
    #  6. Cl- es:
    ["anión", "anion"],
    #  7. ¿Cuántos protones tiene el sodio?
    ["11"],
    #  8. ¿Cuántos electrones tiene Na+?
    ["10"],
    #  9. ¿Cuántos electrones tiene Cl-?
    ["18"],
    # 10. Nombre del ion Na+.
    ["catión sodio", "cation sodio"],
    # 11. Nombre del ion Cl-.
    ["anión cloruro", "anion cloruro"],
    # 12. ¿Cuántos protones tiene el calcio?
    ["20"],
    # 13. ¿Cuántos electrones tiene Ca2+?
    ["18"],
    # 14. O2- es:
    ["anión", "anion"],
    # 15. Al3+ es:
    ["catión", "cation"],
    # 16. ¿Cuántos protones tiene el aluminio?
    ["13"],
    # 17. ¿Cuántos electrones tiene Al3+?
    ["10"],
    # 18. ¿Un catión gana o pierde electrones?
    ["pierde", "pierde electrones"],
    # 19. ¿Un anión gana o pierde electrones?
    ["gana", "gana electrones"],
    # 20. ¿La carga de un anión es positiva o negativa?
    ["negativa"],
]

PUNTOS_POR_PREGUNTA = 5
TOTAL_PREGUNTAS = 20
TOTAL_ACADEMICO = PUNTOS_POR_PREGUNTA * TOTAL_PREGUNTAS  # 100
DESCUENTO_POR_INCIDENTE = 5

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


def normalizar(valor):
    """Normaliza una respuesta: minúsculas, sin acentos, sin espacios.

    Implementación equivalente al ``normalizar()`` JavaScript del RA2 original,
    garantizando paridad entre cliente y servidor.
    """
    if not isinstance(valor, str):
        return ""
    texto = re.sub(r'\s+', '', valor)
    texto = texto.translate(_MAP_ACENTOS).lower()
    return texto.strip()


def _es_respuesta_correcta(indice, valor):
    """ Compara la respuesta del estudiante contra la clave (normalizada). """
    valor_norm = normalizar(valor)
    aceptadas = CLAVE[indice]
    return any(normalizar(r) == valor_norm for r in aceptadas)


def calificar_examen(respuestas, n_incidentes):
    """Califica las 20 preguntas y aplica penalizaciones anti-trampa.

    ``respuestas`` : dict {"0": "Na+", "1": "Cl-", …} con las respuestas.
    ``n_incidentes`` : número de incidentes de trampa registrados en el servidor.

    Devuelve dict con desglose por pregunta, incidentes, descuento y nota final.
    """
    detalle = []
    puntos = 0

    for indice in range(TOTAL_PREGUNTAS):
        texto = str(respuestas.get(str(indice), "") or "")
        correcta = _es_respuesta_correcta(indice, texto)
        puntos_pregunta = PUNTOS_POR_PREGUNTA if correcta else 0
        puntos += puntos_pregunta
        detalle.append({
            "indice": indice,
            "respuesta": texto,
            "correcta": correcta,
            "puntos": puntos_pregunta,
        })

    n_incidentes = max(0, int(n_incidentes))
    descuento_trampa = DESCUENTO_POR_INCIDENTE * n_incidentes
    nota_final = max(0, puntos - descuento_trampa)

    return {
        "academico": puntos,
        "total_academico": TOTAL_ACADEMICO,
        "puntos_por_pregunta": PUNTOS_POR_PREGUNTA,
        "detalle": detalle,
        "incidentes_trampa": n_incidentes,
        "descuento_trampa": descuento_trampa,
        "nota_final": nota_final,
    }
