"""Pruebas de integración del blueprint RA3 CDT1 con Flask test client.

Usa archivos CSV temporales para no ensuciar el directorio del módulo.
"""

import csv
import sys
import tempfile
import unittest
from pathlib import Path

sys.path.insert(0, str(Path(__file__).resolve().parent))

import ra3_cdt1 as modulo
import ra3_grader as g


class BaseApi(unittest.TestCase):
    def setUp(self):
        self.tmp = tempfile.TemporaryDirectory()
        self.addCleanup(self.tmp.cleanup)
        modulo.CSV_RESULTADOS = Path(self.tmp.name) / "resultados_ra3_cdt1.csv"
        modulo.CSV_INCIDENCIAS = Path(self.tmp.name) / "incidencias_ra3_cdt1.csv"
        with _bloqueo_temporal(modulo):
            modulo._sesiones.clear()
        self.cliente = self._crear_cliente()

    @staticmethod
    def _crear_cliente():
        from flask import Flask
        app = Flask(__name__, template_folder=str(
            Path(modulo.__file__).parent / "templates"))
        app.register_blueprint(modulo.bp)
        return app.test_client()

    def _iniciar(self, apellidos="Pérez García", nombres="Ana María",
                 carrera="Mecánica"):
        respuesta = self.cliente.post("/iniciar-ra3", json={
            "apellidos": apellidos, "nombres": nombres, "carrera": carrera})
        return respuesta.get_json()["token"]


def _bloqueo_temporal(_modulo):
    return _modulo._bloqueo


RESPUESTAS = {
    "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": "1.2044e24",
               "porc_h": "11.11", "porc_o": "88.89", "5": "SO2", "6": "C3H6"},
    "serie5": {"v1": "18", "v2": "4", "v3": "2.4088e24",
               "pasos": list(g.PASOS_CORRECTOS)},
}
METAS = {f"s3_{i}": {"tiempo_s": "30", "teclas": "150", "correcciones": "2"}
         for i in range(5)}


class TestIniciar(BaseApi):
    def test_identificacion_valida_entrega_token(self):
        datos = self._cliente_iniciar()
        self.assertTrue(datos["ok"])
        self.assertEqual(len(datos["token"]), 32)

    def _cliente_iniciar(self):
        return self.cliente.post("/iniciar-ra3", json={
            "apellidos": "Lopez", "nombres": "Juan", "carrera": "Electrónica"
        }).get_json()

    def test_carrera_no_permitida_rechaza(self):
        codigo = self.cliente.post("/iniciar-ra3", json={
            "apellidos": "Lopez", "nombres": "Juan", "carrera": "Admin"}).status_code
        self.assertEqual(codigo, 400)

    def test_campos_vacios_o_largos_rechazan(self):
        vacio = self.cliente.post("/iniciar-ra3", json={
            "apellidos": "", "nombres": "X", "carrera": "Mecánica"})
        largo = self.cliente.post("/iniciar-ra3", json={
            "apellidos": "A" * 150, "nombres": "X", "carrera": "Mecánica"})
        for caso in (vacio, largo):
            self.assertEqual(caso.status_code, 400)


class TestIncidentes(BaseApi):
    def test_incidente_valido_incrementa(self):
        token = self._iniciar()
        primera = self.cliente.post("/incidente-ra3",
                                    json={"token": token,
                                          "tipo": "pantalla_completa"}).get_json()
        segunda = self.cliente.post("/incidente-ra3",
                                    json={"token": token,
                                          "tipo": "pegar"}).get_json()
        self.assertEqual((primera["incidentes"], segunda["incidentes"]), (1, 2))

    def test_tipo_invalido_rechaza(self):
        token = self._iniciar()
        codigo = self.cliente.post(
            "/incidente-ra3", json={"token": token, "tipo": "hackeo"}).status_code
        self.assertEqual(codigo, 400)

    def test_token_invalido_rechaza(self):
        codigo = self.cliente.post(
            "/incidente-ra3", json={"token": "no-existe",
                                    "tipo": "pegar"}).status_code
        self.assertEqual(codigo, 400)


class TestGuardar(BaseApi):
    def _enviar(self, token, respuestas=RESPUESTAS, metas=METAS):
        return self.cliente.post("/guardar-ra3", json={
            "token": token, "respuestas": respuestas, "metas": metas})

    def test_examen_perfecto_sin_incidentes_nota_100(self):
        token = self._iniciar()
        datos = self._enviar(token).get_json()
        r = datos["resultado"]
        self.assertEqual(r["academico"], 100)
        self.assertEqual((r["nota_final"], r["incidentes_trampa"], r["alertas_ia"]),
                         (100, 0, 0))

    def test_csv_resultados_formato_y_correlativo(self):
        token1 = self._iniciar()
        self._enviar(token1)
        token2 = self._iniciar(apellidos="Ramírez", nombres="Luis")
        self.cliente.post("/incidente-ra3", json={"token": token2,
                                                  "tipo": "pantalla_completa"})
        respuesta = self._enviar(token2).get_json()
        self.assertEqual(respuesta["resultado"]["nota_final"], 95)  # 100 - 5
        with open(modulo.CSV_RESULTADOS, encoding="utf-8-sig") as f:
            filas = list(csv.reader(f))
        self.assertEqual(filas[0], ["No", "Apellidos", "Nombres", "Carrera", "Nota"])
        self.assertEqual(filas[1][:4], ["1", "Pérez García", "Ana María", "Mecánica"])
        self.assertEqual(filas[2][:5], ["2", "Ramírez", "Luis", "Mecánica", "95"])

    def test_envio_duplicado_bloqueado(self):
        token = self._iniciar()
        self._enviar(token)
        codigo = self._enviar(token).status_code
        self.assertEqual(codigo, 409)

    def test_inyeccion_csv_neutralizada(self):
        token = self._iniciar(apellidos='=HIPERVINCULO("malo")')
        self._enviar(token)
        contenido = Path(modulo.CSV_RESULTADOS).read_text(encoding="utf-8-sig")
        self.assertIn("'=HIPERVINCULO", contenido)

    def test_respuestas_tamperadas_ignoradas(self):
        """Claves desconocidas y valores fuera de allow-list no puntúan."""
        token = self._iniciar()
        trampas = {
            "serie1": {"0": "VVV", "9": "V"},
            "serie2": {"0": "Z"},
            "serie3": {"hack": "<script>"},
            "serie4": {"0": "inf"},
            "serie5": {"v1": "nan", "pasos": ["volcar_bd", "borrar_huella"]},
        }
        datos = self._enviar(token, trampas, {}).get_json()["resultado"]
        self.assertEqual(datos["academico"], 0)

    def test_metas_ausentes_generan_indicadores(self):
        token = self._iniciar()
        datos = self._enviar(token, RESPUESTAS, {}).get_json()["resultado"]
        self.assertGreaterEqual(len(datos["indicadores_ia"]), 5)

    def test_pagina_sirve_html(self):
        pagina = self.cliente.get("/ra3-cdt1")
        self.assertEqual(pagina.status_code, 200)
        cuerpo = pagina.get_data(as_text=True)
        for esperado in ("btn-iniciar", "btn-enviar", "lista-pasos",
                         "fullscreenchange", "iniciar-ra3"):
            self.assertIn(esperado, cuerpo)


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