"""Pruebas de integración del blueprint RA2 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 ra2_cdt1 as modulo
from test_ra2_grader import respuestas_perfectas


class BaseApi(unittest.TestCase):
    def setUp(self):
        self.tmp = tempfile.TemporaryDirectory()
        self.addCleanup(self.tmp.cleanup)
        modulo.CSV_RESULTADOS = Path(self.tmp.name) / "resultados_ra2_cdt1.csv"
        modulo.CSV_INCIDENCIAS = Path(self.tmp.name) / "incidencias_ra2_cdt1.csv"
        with modulo._bloqueo:
            modulo._sesiones.clear()
        from flask import Flask
        app = Flask(__name__, template_folder=str(
            Path(modulo.__file__).parent / "templates"))
        app.register_blueprint(modulo.bp)
        self.cliente = app.test_client()

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


class TestIniciar(BaseApi):
    def test_identificacion_valida_entrega_token(self):
        datos = self.cliente.post("/iniciar-ra2", json={
            "apellidos": "Lopez", "nombres": "Juan",
            "carrera": "Electrónica", "clave": "12"}).get_json()
        self.assertTrue(datos["ok"])
        self.assertEqual(len(datos["token"]), 32)

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

    def test_campos_vacios_rechazan(self):
        codigo = self.cliente.post("/iniciar-ra2", json={
            "apellidos": "", "nombres": "X",
            "carrera": "Mecánica", "clave": "1"}).status_code
        self.assertEqual(codigo, 400)

    def test_clave_ausente_rechaza(self):
        codigo = self.cliente.post("/iniciar-ra2", json={
            "apellidos": "Lopez", "nombres": "Juan",
            "carrera": "Mecánica"}).status_code
        self.assertEqual(codigo, 400)


class TestIncidente(BaseApi):
    def test_incidente_valido_acepta(self):
        token = self._iniciar()
        datos = self.cliente.post("/incidente-ra2", json={
            "token": token, "tipo": "pestana_oculta"}).get_json()
        self.assertTrue(datos["ok"])
        self.assertEqual(datos["incidentes"], 1)
        self.assertEqual(datos["descuento"], 5)

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

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


class TestGuardar(BaseApi):
    def _enviar(self, token, respuestas=None):
        return self.cliente.post("/guardar-ra2", json={
            "token": token, "respuestas": respuestas or respuestas_perfectas()})

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

    def test_un_incidente_resta_cinco(self):
        token = self._iniciar()
        self.cliente.post("/incidente-ra2", json={
            "token": token, "tipo": "pantalla_completa"})
        r = self._enviar(token).get_json()["resultado"]
        self.assertEqual(r["nota_final"], 95)

    def test_csv_resultados_formato(self):
        token1 = self._iniciar()
        self._enviar(token1)
        token2 = self._iniciar(apellidos="Ramírez", nombres="Luis", clave="15")
        self.cliente.post("/incidente-ra2", json={
            "token": token2, "tipo": "pantalla_completa"})
        self._enviar(token2)
        with open(modulo.CSV_RESULTADOS, encoding="utf-8-sig") as f:
            filas = list(csv.reader(f))
        self.assertEqual(filas[0], ["No", "Clave", "Apellidos", "Nombres",
                                    "Carrera", "Nota", "Incidentes", "Descuento"])
        self.assertEqual(filas[1][:5], ["1", "9", "Pérez García", "Ana María", "Mecánica"])
        self.assertEqual(filas[2], ["2", "15", "Ramírez", "Luis", "Mecánica", "95", "1", "5"])

    def test_envio_duplicado_bloqueado(self):
        token = self._iniciar()
        self._enviar(token)
        self.assertEqual(self._enviar(token).status_code, 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):
        token = self._iniciar()
        trampas = {"0": "<script>", "99": "hack", "3": "x" * 500}
        r = self._enviar(token, trampas).get_json()["resultado"]
        self.assertEqual(r["academico"], 0)

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


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