"""QA SAQI contra produccion: funcional (L3/L4) + adversarial (ATK-SEC) del modulo 4.1.5-4.1.7.

Uso: python3 qa_funnel_estequiometria.py [URL_BASE]
"""

import json
import sys
import urllib.error
import urllib.request

BASE = sys.argv[1] if len(sys.argv) > 1 else "https://adoniz-acer.tail60dd6a.ts.net"
RESULTADOS = []


def pedir(ruta, datos=None, cabeceras=None, bruto=None):
    peticion = urllib.request.Request(f"{BASE}{ruta}", headers=cabeceras or {})
    cuerpo = bruto
    if datos is not None:
        cuerpo = json.dumps(datos).encode()
        peticion.add_header("Content-Type", "application/json")
    try:
        with urllib.request.urlopen(peticion, data=cuerpo, timeout=15) as r:
            return r.status, r.read()
    except urllib.error.HTTPError as e:
        return e.code, e.read()


def verificar(nombre, condicion, detalle=""):
    RESULTADOS.append((nombre, condicion, detalle))
    print(f"{'PASS' if condicion else 'FAIL'}  {nombre}" +
          (f"  [{detalle}]" if detalle and not condicion else ""))


def es_json(cuerpo):
    try:
        json.loads(cuerpo)
        return True
    except (ValueError, TypeError):
        return False


# L4 funcional
estado, html = pedir("/quimica/")
verificar("L4 menu responde 200", estado == 200, str(estado))
verificar("L4 menu muestra tarjeta estequiometria",
          b"estequiometria" in html)

# flujo completo 4.1.5-4.1.7
estado, cuerpo = pedir("/quimica/estequiometria")
verificar("L4 pagina modulo responde 200", estado == 200, str(estado))

verificar("L4 notificacion visible por incidencia",
          estado == 200 and
          b"notificacionIncidente" in (cuerpo or b"") and
          b"mostrarAviso" in (cuerpo or b"") and
          b"actualizarContador" in (cuerpo or b"") and
          b"n-incidentes" in (cuerpo or b"") and
          b"contador-trampas" in (cuerpo or b"") and
          b"async function reportarIncidente" in (cuerpo or b""),
          "faltan marcadores de notificacion")

estado, cuerpo = pedir("/quimica/iniciar-estequiometria", {
    "apellidos": "QA", "nombres": "Automatizado", "carrera": "Mecanica"})
token = json.loads(cuerpo)["token"] if estado == 200 else ""
verificar("L3 iniciar entrega token de 32 hex", estado == 200 and len(token) == 32)

respuestas = {
    "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"},
    }
}

# incidente
estado, _ = pedir("/quimica/incidente-estequiometria",
                  {"token": token, "tipo": "pegar"})
verificar("L3 incidente valido acepta", estado == 200)

estado, cuerpo = pedir("/quimica/guardar-estequiometria", {"token": token,
                                                            "respuestas": {}})
resultado = json.loads(cuerpo).get("resultado", {}) if estado == 200 else {}
verificar("L3 envio sin respuestas califica 0 sin caer",
          estado == 200 and resultado.get("nota_final", -1) == 0)

estado, cuerpo = pedir("/quimica/guardar-estequiometria", {"token": token,
                                                            "respuestas": {}})
verificar("L3 reenvio con mismo token bloqueado (409)", estado == 409, str(estado))

# sesion perfecta
estado, cuerpo = pedir("/quimica/iniciar-estequiometria", {
    "apellidos": "QA2", "nombres": "Perfecto", "carrera": "Electronica"})
token2 = json.loads(cuerpo)["token"] if estado == 200 else ""
estado, cuerpo = pedir("/quimica/guardar-estequiometria", {"token": token2, **respuestas})
r2 = json.loads(cuerpo).get("resultado", {}) if estado == 200 else {}
verificar("L3 envio perfecto => nota 100",
          estado == 200 and r2.get("nota_final") == 100)

# ATK-SEC
casos = [
    ("JSON malformado", "/quimica/iniciar-estequiometria", None,
     {"Content-Type": "application/json"}, b"{roto"),
    ("Content-Type incorrecto", "/quimica/iniciar-estequiometria", None,
     {"Content-Type": "text/plain"}, b"texto"),
    ("Payload gigante (1MB)", "/quimica/iniciar-estequiometria", None,
     {"Content-Type": "application/json"},
     b'{"apellidos":"' + b"A" * 1_000_000 +
     '","nombres":"x","carrera":"Mecanica"}'.encode()),
]
for nombre, ruta, _d, cabeceras, cuerpo in casos:
    codigo, _ = pedir(ruta, bruto=cuerpo, cabeceras=cabeceras)
    verificar(f"SEC {nombre} (status={codigo}, no 500)", codigo not in (500, 502, 503),
              f"{codigo}")

ataques = [
    {"apellidos": "=HIPERVINCULO($A$1)", "nombres": "+cmd", "carrera": "Mecanica"},
    {"apellidos": "<script>alert(1)</script>", "nombres": "x", "carrera": "Mecanica"},
    {"apellidos": "Lopez\nSegunda linea", "nombres": "x", "carrera": "Mecanica"},
]
for i, ataque in enumerate(ataques, 1):
    codigo, _ = pedir("/quimica/iniciar-estequiometria", ataque)
    verificar(f"SEC ataque entrada {i} no provoca 500", codigo in (200, 400), str(codigo))

estado, _ = pedir("/quimica/incidente-estequiometria", {"token": "x" * 32, "tipo": "pegar"})
verificar("SEC token desconocido rechazado", estado == 400, str(estado))
estado, _ = pedir("/quimica/incidente-estequiometria", {"token": token, "tipo": "; rm -rf /"})
verificar("SEC tipo de incidente fuera de allow-list rechazado", estado == 400, str(estado))

estado, cuerpo = pedir("/quimica/estequiometria")
verificar("SEC la pagina no expone la clave de respuestas",
          b"CLAVE" not in (cuerpo or b""))
verificar("SEC la pagina NO fuerza pantalla completa",
          b"requestFullscreen" not in (cuerpo or b""))

fallos = [r for r in RESULTADOS if not r[1]]
print(f"\n=== QA SAQI: {len(RESULTADOS) - len(fallos)}/{len(RESULTADOS)} PASS, "
      f"{len(fallos)} FAIL ===")
sys.exit(1 if fallos else 0)