"""Pruebas de integracion del blueprint Flask estequiometria_cdt.

Cubren: contrato de API, incidentes con descuento, guardado de 1 fila por estudiante,
bloqueo de reenvios (409) y saneamiento/seguridad de entradas.
"""

import csv
import os
import tempfile
import unittest

from flask import Flask

import estequiometria_cdt
from estequiometria_cdt import bp as estequiometria_bp


def respuestas_perfectas():
    return {
        "respuestas": {
            "4.1.5": {"0": "B", "1": "A", "2": "C", "3": "D", "4": "B"},
            "4.1.6": {
                "0": "1,3,2", "1": "4,3,2", "2": "1,5,3,4", "3": "2,2,2,1",
                "4": "1,2,1,1", "5": "4,3,2", "6": "1,1,1", "7": "2,7,4,6",
                "8": "1,2,1,1", "9": "2,13,8,10",
            },
            "4.1.7": {
                "0": "4", "1": "4", "2": "117", "3": "324", "4": "5",
                "5": "10.8", "6": "34.6", "7": "0.5", "8": "302.2", "9": "7.45",
            },
            "ACT": {"0": "C", "1": "A", "2": "B", "3": "D", "4": "C"},
        }
    }


def leer_csv(ruta):
    if not os.path.exists(ruta):
        return []
    with open(ruta, newline="", encoding="utf-8-sig") as f:
        return list(csv.reader(f))


class BaseTest(unittest.TestCase):

    def setUp(self):
        self.tmp = tempfile.TemporaryDirectory()
        self.orig_res = estequiometria_cdt.CSV_RESULTADOS
        self.orig_inc = estequiometria_cdt.CSV_INCIDENCIAS
        self.res_path = os.path.join(self.tmp.name, "estequiometria_resultados.csv")
        self.inc_path = os.path.join(self.tmp.name, "estequiometria_incidencias.csv")
        estequiometria_cdt.CSV_RESULTADOS = self.res_path
        estequiometria_cdt.CSV_INCIDENCIAS = self.inc_path
        estequiometria_cdt._sesiones.clear()

        app = Flask(__name__)
        app.register_blueprint(estequiometria_bp)
        app.config["TESTING"] = True
        self.client = app.test_client()

    def tearDown(self):
        estequiometria_cdt.CSV_RESULTADOS = self.orig_res
        estequiometria_cdt.CSV_INCIDENCIAS = self.orig_inc
        estequiometria_cdt._sesiones.clear()
        self.tmp.cleanup()

    def _iniciar(self, **extra):
        datos = {"apellidos": "Perez", "nombres": "Luis",
                 "carrera": "Mecanica"}
        datos.update(extra)
        return self.client.post("/iniciar-estequiometria", json=datos)


class TestRutasBasicas(BaseTest):

    def test_pagina_200(self):
        r = self.client.get("/estequiometria")
        self.assertEqual(r.status_code, 200)

    def test_pagina_no_expone_clave(self):
        contenido = self.client.get("/estequiometria")\
            .get_data(as_text=True)
        self.assertNotIn("CLAVE", contenido)


class TestIniciar(BaseTest):

    def test_iniciar_ok_token_32hex(self):
        r = self._iniciar()
        self.assertEqual(r.status_code, 200)
        token = r.get_json()["token"]
        self.assertIsInstance(token, str)
        self.assertEqual(len(token), 32)
        self.assertTrue(all(c in "0123456789abcdef" for c in token))

    def test_iniciar_carrera_no_permitida_400(self):
        r = self._iniciar(carrera="Medicina")
        self.assertEqual(r.status_code, 400)

    def test_iniciar_falta_nombres_400(self):
        r = self._iniciar(nombres="")
        self.assertEqual(r.status_code, 400)


class TestIncidente(BaseTest):

    def test_incidente_valido_descuenta(self):
        token = self._iniciar().get_json()["token"]
        r = self.client.post("/incidente-estequiometria",
                             json={"token": token, "tipo": "pegar"})
        self.assertEqual(r.status_code, 200)
        self.assertEqual(r.get_json()["descuento"], 5)

    def test_incidente_tipo_invalido_400(self):
        token = self._iniciar().get_json()["token"]
        r = self.client.post("/incidente-estequiometria",
                             json={"token": token, "tipo": "rm -rf /"})
        self.assertEqual(r.status_code, 400)
        self.assertEqual(leer_csv(self.inc_path), [])

    def test_incidente_token_desconocido_400(self):
        r = self.client.post("/incidente-estequiometria",
                             json={"token": "x" * 32, "tipo": "pegar"})
        self.assertEqual(r.status_code, 400)

    def test_incidentes_se_acumulan(self):
        token = self._iniciar().get_json()["token"]
        for _ in range(3):
            self.client.post("/incidente-estequiometria",
                             json={"token": token, "tipo": "copiar"})
        filas = leer_csv(self.inc_path)
        self.assertEqual(len(filas), 4)  # encabezado + 3 incidentes


class TestGuardar(BaseTest):

    def test_guardar_perfecto_nota_100(self):
        token = self._iniciar().get_json()["token"]
        r = self.client.post("/guardar-estequiometria",
                             json={"token": token, **respuestas_perfectas()})
        self.assertEqual(r.status_code, 200)
        resultado = r.get_json()["resultado"]
        self.assertEqual(resultado["nota_final"], 100)
        filas = leer_csv(self.res_path)
        self.assertEqual(len(filas), 2)  # encabezado + 1 fila

    def test_guardar_con_incidentes_descuenta(self):
        token = self._iniciar().get_json()["token"]
        self.client.post("/incidente-estequiometria",
                         json={"token": token, "tipo": "pegar"})
        r = self.client.post("/guardar-estequiometria",
                             json={"token": token, **respuestas_perfectas()})
        resultado = r.get_json()["resultado"]
        self.assertEqual(resultado["nota_final"], 95)

    def test_reenvio_duplicado_409(self):
        token = self._iniciar().get_json()["token"]
        payload = {"token": token, **respuestas_perfectas()}
        self.assertEqual(self.client.post("/guardar-estequiometria",
                                          json=payload).status_code, 200)
        r = self.client.post("/guardar-estequiometria", json=payload)
        self.assertEqual(r.status_code, 409)
        filas = leer_csv(self.res_path)
        self.assertEqual(len(filas), 2)  # no se duplico

    def test_token_desconocido_400(self):
        r = self.client.post("/guardar-estequiometria",
                             json={"token": "y" * 32, **respuestas_perfectas()})
        self.assertEqual(r.status_code, 400)

    def test_respuesta_corta_se_acorta(self):
        token = self._iniciar().get_json()["token"]
        payload = {"token": token,
                   "respuestas": {"4.1.6": {"0": "E" * 200}}}
        r = self.client.post("/guardar-estequiometria", json=payload)
        self.assertEqual(r.status_code, 200)

    def test_inyeccion_csv_escapada(self):
        token = self._iniciar(apellidos="=HIPERVINCULO($A$1)").get_json()["token"]
        r = self.client.post("/guardar-estequiometria",
                             json={"token": token, **respuestas_perfectas()})
        self.assertEqual(r.status_code, 200)
        filas = leer_csv(self.res_path)
        self.assertTrue(filas[1][1].startswith("'"))  # celda escapada

    def test_correlativo_por_estudiante(self):
        t1 = self._iniciar(apellidos="Uno", nombres="Primer").get_json()["token"]
        self.client.post("/guardar-estequiometria", json={"token": t1, **respuestas_perfectas()})
        filas = leer_csv(self.res_path)
        self.assertEqual(filas[1][0], "1")
        t2 = self._iniciar(apellidos="Dos", nombres="Segundo").get_json()["token"]
        self.client.post("/guardar-estequiometria", json={"token": t2, **respuestas_perfectas()})
        filas = leer_csv(self.res_path)
        self.assertEqual(filas[2][0], "2")

    def test_json_malformado_sin_500(self):
        r = self.client.post("/iniciar-estequiometria", data=b"{roto",
                             content_type="application/json")
        self.assertEqual(r.status_code, 400)


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