"""QA SAQI contra producción: funcional (L3/L4) + adversarial (ATK-SEC).

Uso: python3 qa_funnel.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 RA3", b"ra3-cdt1" in html)
enlaces = [l.decode() for l in
           [x.split(b'"')[0] for x in html.split(b'href="')[1:]]]
modulos = [e for e in enlaces if e.startswith("/quimica/")]
for enlace in modulos:
    codigo, _ = pedir(enlace)
    verificar(f"L4 enlace {enlace} -> 200", codigo == 200, str(codigo))

# ------------------------------------------------------- flujo completo RA3
estado, cuerpo = pedir("/quimica/iniciar-ra3", {
    "apellidos": "QA", "nombres": "Automatizado", "carrera": "Mecánica"})
token = json.loads(cuerpo)["token"] if estado == 200 else ""
verificar("L3 iniciar entrega token de 32 hex", estado == 200 and len(token) == 32)

estado, _ = pedir("/quimica/incidente-ra3",
                  {"token": token, "tipo": "pantalla_completa"})
verificar("L3 incidente válido acepta", estado == 200)

estado, cuerpo = pedir("/quimica/guardar-ra3", {
    "token": token, "respuestas": {}, "metas": {}})
resultado = json.loads(cuerpo).get("resultado", {}) if estado == 200 else {}
verificar("L3 envío sin respuestas califica 0 sin caer",
          estado == 200 and resultado.get("academico") == 0)
estado, _ = pedir("/quimica/guardar-ra3", {"token": token, "respuestas": {}, "metas": {}})
verificar("L3 reenvío con mismo token bloqueado (409)", estado == 409, str(estado))

# ------------------------------------------------------------- ATK-SEC
casos = [
    ("JSON malformado rechazado sin 500",
     "/quimica/iniciar-ra3", None, {"Content-Type": "application/json"}, b"{roto"),
    ("Content-Type incorrecto no cae",
     "/quimica/iniciar-ra3", None, {"Content-Type": "text/plain"}, b"texto"),
    ("Payload gigante (1MB) no cae",
     "/quimica/iniciar-ra3", None, {"Content-Type": "application/json"},
     b'{"apellidos":"' + b"A" * 1_000_000 + b'","nombres":"x","carrera":"Mec\xaanica"}'),
]
for nombre, ruta, _datos, cabeceras, cuerpo in casos:
    codigo, respuesta = 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": "Mecánica"},
    {"apellidos": "<script>alert(1)</script>", "nombres": "x", "carrera": "Mecánica"},
    {"apellidos": "López\nSegunda línea", "nombres": "x", "carrera": "Mecánica"},
]
for i, ataque in enumerate(ataques, 1):
    codigo, _ = pedir("/quimica/iniciar-ra3", ataque)
    verificar(f"SEC ataque entrada {i} no provoca 500", codigo in (200, 400), str(codigo))

estado, _ = pedir("/quimica/incidente-ra3", {"token": "x" * 32, "tipo": "pegar"})
verificar("SEC token desconocido rechazado", estado == 400, str(estado))
estado, _ = pedir("/quimica/incidente-ra3", {"token": token, "tipo": "; rm -rf /"})
verificar("SEC tipo de incidente fuera de allow-list rechazado", estado == 400, str(estado))
estado, cuerpo = pedir("/quimica/ra3-cdt1")
verificar("SEC la página no expone la clave de respuestas",
          b"CLAVE_VF" not in cuerpo and b"C3H6\"" not in cuerpo)

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)
