"""Pruebas del dominio puro de calificación RA3 CDT1 (unittest estándar)."""

import sys
import unittest

import ra3_grader as g


def respuestas_perfectas():
    return {
        "serie1": {"0": "V", "1": "V", "2": "V", "3": "F", "4": "V"},
        "serie2": {"0": "B", "1": "C", "2": "C", "3": "B", "4": "A"},
        "serie3": {
            "0": "Un mol es la cantidad de sustancia que contiene 6,022 x 10^23 "
                 "partículas como átomos o moléculas.",
            "1": "El número de Avogadro indica cuántas partículas hay por mol.",
            "2": "Indica la proporción más simple en que se combinan los átomos.",
            "3": "La empírica muestra la proporción simple y la molecular el número "
                 "real y exacto de cada elemento.",
            "4": "Es el porcentaje en masa de cada elemento del compuesto, dividido "
                 "entre la masa total multiplicado por 100.",
        },
        "serie4": {
            "0": "100", "1": "5", "2": "132",
            "3": "12.044x10^23", "porc_h": "11.1", "porc_o": "88.9",
            "5": "SO2", "6": "C3H6",
        },
        "serie5": {"v1": "18", "v2": "4", "v3": "24.088x10^23",
                   "pasos": list(g.PASOS_CORRECTOS)},
    }


METAS_NORMALES = {
    f"s3_{i}": {"tiempo_s": "30", "teclas": "150", "correcciones": "2"}
    for i in range(5)
}


class TestSeriesObjetivas(unittest.TestCase):
    def test_serie1_perfecta(self):
        puntos, _, total = g.calificar_serie1(respuestas_perfectas()["serie1"])
        self.assertEqual((puntos, total), (10, 10))

    def test_serie1_invalido_cuenta_cero(self):
        puntos, _, _ = g.calificar_serie1({"0": "X"})
        self.assertEqual(puntos, 0)

    def test_serie2_letras_desordenadas(self):
        r = {"0": "B", "1": "C", "2": "C", "3": "B", "4": "A"}
        self.assertEqual(g.calificar_serie2(r)[0], 10)

    def test_totales_constantes(self):
        self.assertEqual(sum(v for v in g.PUNTOS.values()), 100)


class TestSerieTres(unittest.TestCase):
    def test_variantes_coma_tilde(self):
        texto = {"0": "Cantidad de sustancia con 6,022×10²³ partículas"}
        puntos, detalle, _ = g.calificar_serie3(texto)
        self.assertEqual(detalle[0]["grupos_detectados"], 3)

    def test_texto_vacio_puntua_cero(self):
        puntos, _, _ = g.calificar_serie3({"0": ""})
        self.assertEqual(puntos, 0)

    def test_techo_20(self):
        def primera_literal(grupo):
            return next(a for a in grupo if not a.startswith("re:"))
        todos = {str(i): " ".join(primera_literal(a) for a in grupos)
                 for i, grupos in enumerate(g.SERIE3_GRUPOS)}
        puntos, _, total = g.calificar_serie3(todos)
        self.assertEqual((puntos, total), (20, 20))


class TestNumeros(unittest.TestCase):
    def test_notacion_cientifica_acepta(self):
        self.assertTrue(g._valor_dentro("1.2044e24", g.VALOR_MOLECULAS_2MOL))
        self.assertTrue(g._valor_dentro("12,044 x 10^23", g.VALOR_MOLECULAS_2MOL))
        self.assertFalse(g._valor_dentro("6.022e23", g.VALOR_MOLECULAS_2MOL))

    def test_rechaza_nan_inf(self):
        self.assertIsNone(g._a_flotante_seguro("nan"))
        self.assertIsNone(g._a_flotante_seguro("inf"))
        self.assertIsNone(g._a_flotante_seguro("abc"))

    def test_formula_normalizada(self):
        self.assertEqual(g.normalizar_formula(" so₂ ".replace("₂", "2")), "SO2")
        self.assertEqual(g.normalizar_formula("c3h6"), "C3H6")
        self.assertNotEqual(g.normalizar_formula("CO2"), "SO2")


class TestSerieCinco(unittest.TestCase):
    def test_pasos_correctos(self):
        r = {"v1": "18", "v2": "4", "v3": "2.4088e24",
             "pasos": list(g.PASOS_CORRECTOS)}
        puntos, _, total = g.calificar_serie5(r)
        self.assertEqual((puntos, total), (20, 20))

    def test_pasos_invertidos_parcial(self):
        pasos = list(reversed(g.PASOS_CORRECTOS))
        r = {"v1": "", "v2": "", "v3": "", "pasos": pasos}
        puntos, detalle, _ = g.calificar_serie5(r)
        self.assertEqual(detalle[-1]["correctos"], 0)  # inversión total: ninguno coincide

    def test_valores_fuera_tolerancia(self):
        r = {"v1": "19", "v2": "4.5", "v3": "6.022e23",
             "pasos": list(g.PASOS_CORRECTOS)}
        puntos, _, _ = g.calificar_serie5(r)
        self.assertEqual(puntos, 8)


class TestIndicadoresIA(unittest.TestCase):
    def test_umbral_bloques_de_cuatro(self):
        self.assertEqual(g.alertas_ia(0), 0)
        self.assertEqual(g.alertas_ia(3), 0)
        self.assertEqual(g.alertas_ia(4), 1)
        self.assertEqual(g.alertas_ia(9), 2)

    def test_meta_ausente_cuenta_indicador(self):
        texto = {"0": "un mol contiene particulas"}
        indicadores = g.evaluar_indicadores_ia(texto, {})
        self.assertIn("s3_0:sin_meta", indicadores)

    def test_tiempo_imposible(self):
        texto = {"0": "un mol es la cantidad de sustancia con numero de avogadro y particulas"}
        metas = {"s3_0": {"tiempo_s": "0.5", "teclas": "90", "correcciones": "1"}}
        indicadores = g.evaluar_indicadores_ia(texto, metas)
        self.assertTrue(any("tiempo_imposible" in i for i in indicadores))

    def test_redaccion_copiada_del_modelo(self):
        largo = ("la formula empirica es la formula que indica la proporcion mas simple "
                 "(minima) en que se combinan los atomos de los elementos de un "
                 "compuesto. ademas es importante destacar este concepto.")
        texto = {"2": largo}
        metas = {"s3_2": {"tiempo_s": "60", "teclas": "300", "correcciones": "0"}}
        indicadores = g.evaluar_indicadores_ia(texto, metas)
        self.assertTrue(any("redaccion_ia" in i for i in indicadores))

    def test_respuesta_humana_normal_no_alerta(self):
        texto = {
            "0": "Es la cantidad de sustancia que tiene particulas de avogadro",
            "1": "cuantas particulas caben en un mol de algo",
            "2": "la proporcion simple de atomos en un compuesto",
            "3": "una dice proporcion y la otra el numero real de cada elemento",
            "4": "el porcentaje de masa de cada elemento del compuesto",
        }
        indicadores = g.evaluar_indicadores_ia(texto, METAS_NORMALES)
        self.assertEqual(indicadores, [])


class TestCalificacionTotal(unittest.TestCase):
    def test_examen_perfecto_sin_penalizacion(self):
        resultado = g.calificar_examen(respuestas_perfectas(), METAS_NORMALES, 0)
        self.assertEqual(resultado["academico"], 100)
        self.assertEqual(resultado["nota_final"], 100)

    def test_dos_incidentes_restan_diez(self):
        resultado = g.calificar_examen(
            respuestas_perfectas(), METAS_NORMALES, 2)
        self.assertEqual(resultado["nota_final"], 90)

    def test_suelo_en_cero(self):
        vacias = {"serie1": {}, "serie2": {}, "serie3": {}, "serie4": {},
                  "serie5": {}}
        resultado = g.calificar_examen(vacias, {}, 99)
        self.assertEqual(resultado["nota_final"], 0)

    def test_alertas_ia_descuentan_desde_ultraperfecto(self):
        perfecto = respuestas_perfectas()
        largo_modelo = g.MODELO_SERIE3[0]
        perfecto["serie3"] = {str(i): (largo_modelo * 2 if i == 0 else
                                       " ".join(a[0] for a in grupos))
                              for i, grupos in enumerate(g.SERIE3_GRUPOS)}
        metas = {f"s3_{i}": {"tiempo_s": "-5", "teclas": "1", "correcciones": "0"}
                 for i in range(5)}
        resultado = g.calificar_examen(perfecto, metas, 0)
        self.assertGreater(len(resultado["indicadores_ia"]), 0)
        self.assertEqual(resultado["descuento_ia"],
                         5 * resultado["alertas_ia"])


if __name__ == "__main__":
    unittest.main()
