"""Smoke test end-to-end F13 (auth minima).

Requiere el servidor corriendo en localhost:8080 y seed_usuarios_demo.py aplicado.

Uso:
    python smoke_test_f13.py

Tests (8):
  T1  login admin OK + cookie Set-Cookie HttpOnly
  T2  login password invalida -> 401 generico
  T3  /api/auth/me con cookie -> usuario con rol
  T4  /api/obras sin cookie -> 401
  T5  /api/obras como admin -> ve TDG1 + VITR1 (ambas)
  T6  /api/obras como cliente (vitrium) -> solo VITR1
  T7  /api/obras/TDG1 como cliente -> 403
  T8  logout -> cookie cleared + /api/auth/me 401
"""
from __future__ import annotations

import http.cookiejar
import json
import sys
import urllib.error
import urllib.request
from urllib.parse import urlparse

BASE_URL = "http://localhost:8080"
ADMIN = {"email": "admin@demo.local", "password": "demo1234"}
CLIENTE = {"email": "vitrium@demo.local", "password": "vitrium1234"}


def _build_opener():
    cj = http.cookiejar.CookieJar()
    return urllib.request.build_opener(
        urllib.request.HTTPCookieProcessor(cj)
    ), cj


def _request(opener, method, path, body=None, headers=None):
    url = BASE_URL + path
    data = None
    hdrs = {"Accept": "application/json"}
    if headers:
        hdrs.update(headers)
    if body is not None:
        data = json.dumps(body).encode("utf-8")
        hdrs["Content-Type"] = "application/json"
    req = urllib.request.Request(url, data=data, headers=hdrs, method=method)
    try:
        resp = opener.open(req, timeout=10)
        return resp.getcode(), resp.headers, resp.read()
    except urllib.error.HTTPError as e:
        return e.code, e.headers, e.read()


def _json(body):
    try:
        return json.loads(body.decode("utf-8"))
    except Exception:
        return None


PASS, FAIL = [], []


def check(name, cond, detail=""):
    marker = "OK " if cond else "FAIL"
    print(f"  [{marker}] {name}" + (f"  -- {detail}" if detail else ""))
    (PASS if cond else FAIL).append(name)


def main():
    # ========================================================================
    # T1: Login admin OK + cookie
    # ========================================================================
    print("T1: login admin correcto")
    opener_admin, cj_admin = _build_opener()
    code, headers, body = _request(opener_admin, "POST", "/api/auth/login", ADMIN)
    data = _json(body) or {}
    set_cookie = headers.get("Set-Cookie", "")
    check("T1.1 status 200", code == 200, f"code={code} body={body[:80]!r}")
    check("T1.2 body ok=True", data.get("ok") is True)
    check("T1.3 usuario.rol=admin", (data.get("usuario") or {}).get("rol") == "admin")
    check("T1.4 Set-Cookie ergon_session presente", "ergon_session=" in set_cookie, set_cookie[:80])
    check("T1.5 cookie HttpOnly", "HttpOnly" in set_cookie)
    check("T1.6 cookie SameSite=Lax", "SameSite=Lax" in set_cookie)

    # ========================================================================
    # T2: login password invalida -> 401 + mensaje generico
    # ========================================================================
    print("T2: login password invalida")
    opener_bad, _ = _build_opener()
    code, _, body = _request(opener_bad, "POST", "/api/auth/login",
                              {"email": ADMIN["email"], "password": "wrong"})
    data = _json(body) or {}
    check("T2.1 status 401", code == 401)
    msg = (data.get("error") or "").lower()
    check("T2.2 mensaje generico sin filtrar campo",
          "invalid" in msg or "invalidos" in msg, f"msg={msg!r}")

    # ========================================================================
    # T3: /api/auth/me con cookie
    # ========================================================================
    print("T3: /api/auth/me con cookie admin")
    code, _, body = _request(opener_admin, "GET", "/api/auth/me")
    data = _json(body) or {}
    check("T3.1 status 200", code == 200)
    check("T3.2 usuario.email coincide",
          (data.get("usuario") or {}).get("email") == ADMIN["email"])
    check("T3.3 rol admin", (data.get("usuario") or {}).get("rol") == "admin")

    # ========================================================================
    # T4: /api/obras sin cookie -> 401
    # ========================================================================
    print("T4: /api/obras sin cookie")
    opener_none, _ = _build_opener()
    code, _, body = _request(opener_none, "GET", "/api/obras")
    check("T4.1 status 401", code == 401, f"code={code}")

    # ========================================================================
    # T5: /api/obras como admin -> ambas obras
    # ========================================================================
    print("T5: /api/obras como admin")
    code, _, body = _request(opener_admin, "GET", "/api/obras")
    obras = _json(body) or []
    check("T5.1 status 200", code == 200)
    codigos = {o.get("codigo") for o in obras if isinstance(o, dict)}
    check("T5.2 admin ve TDG1 y VITR1",
          "TDG1" in codigos and "VITR1" in codigos,
          f"codigos={sorted(codigos)}")

    # ========================================================================
    # T6: /api/obras como cliente -> solo VITR1
    # ========================================================================
    print("T6: /api/obras como cliente vitrium")
    opener_cli, _ = _build_opener()
    code_login, _, _ = _request(opener_cli, "POST", "/api/auth/login", CLIENTE)
    check("T6.0 login cliente OK", code_login == 200)
    code, _, body = _request(opener_cli, "GET", "/api/obras")
    obras = _json(body) or []
    codigos = {o.get("codigo") for o in obras if isinstance(o, dict)}
    check("T6.1 status 200", code == 200)
    check("T6.2 cliente ve SOLO VITR1",
          codigos == {"VITR1"}, f"codigos={sorted(codigos)}")

    # ========================================================================
    # T7: cliente intenta acceder a TDG1 -> 403
    # ========================================================================
    print("T7: cliente accede a TDG1 (no asignada)")
    code, _, body = _request(opener_cli, "GET", "/api/obras/TDG1")
    check("T7.1 status 403", code == 403, f"code={code}")

    code, _, body = _request(opener_cli, "GET", "/api/obras/TDG1/avance")
    check("T7.2 recurso anidado 403", code == 403, f"code={code}")

    # Cliente debe poder ver su propia obra VITR1
    code, _, body = _request(opener_cli, "GET", "/api/obras/VITR1")
    check("T7.3 cliente accede a VITR1 OK", code == 200, f"code={code}")

    # ========================================================================
    # T8: logout -> cookie cleared + me -> 401
    # ========================================================================
    print("T8: logout admin")
    code, headers, _ = _request(opener_admin, "POST", "/api/auth/logout")
    set_cookie = headers.get("Set-Cookie", "")
    check("T8.1 logout 200", code == 200)
    check("T8.2 Set-Cookie con Max-Age=0",
          "Max-Age=0" in set_cookie or "ergon_session=;" in set_cookie,
          set_cookie[:80])
    # El CookieJar borra la cookie cuando Max-Age=0, asi que el siguiente
    # request no enviara ergon_session.
    code, _, body = _request(opener_admin, "GET", "/api/auth/me")
    check("T8.3 /api/auth/me post-logout = 401", code == 401, f"code={code}")

    # ========================================================================
    # T9 (bonus): POST /api/obras como cliente -> 403
    # ========================================================================
    print("T9: cliente intenta crear obra (rol admin requerido)")
    # Re-login cliente porque no hicimos logout de ese opener
    code, _, body = _request(opener_cli, "POST", "/api/obras", {
        "codigo": "NUEVX",
        "nombre": "x",
        "cliente": "x",
        "presupuesto_total": 1,
        "fecha_inicio": "2026-01-01",
        "fecha_fin_plan": "2026-12-31",
        "fecha_corte_actual": "2026-06-01",
        "umbral_desvio_pct": 10,
        "umbral_critico_pct": 20,
        "dg_nivel_servicio": 2,
        "rubros": [{"nombre": "x", "peso_pct": 100}],
    })
    check("T9.1 POST /api/obras como cliente = 403", code == 403, f"code={code}")

    # ========================================================================
    # T10 (regression): token invalido debe devolver 401 bien-formado + limpiar cookie
    # Historia: bug detectado en auto-revision scar_002. send_header antes de
    # send_response dejaba Set-Cookie antes del status-line -> BadStatusLine en
    # el cliente. Fix via _clear_session_cookie_via_json.
    # ========================================================================
    print("T10: token invalido (regression)")
    opener_bad_tok, _ = _build_opener()
    req_bad = urllib.request.Request(
        BASE_URL + "/api/obras",
        headers={"Cookie": "ergon_session=INVALID_XYZ_NOT_IN_DB_01234567890",
                 "Accept": "application/json"},
    )
    try:
        resp = urllib.request.urlopen(req_bad, timeout=5)
        code = resp.getcode()
        hdrs = resp.headers
    except urllib.error.HTTPError as e:
        code = e.code
        hdrs = e.headers
    except Exception as e:
        check("T10.0 respuesta HTTP bien-formada (no BadStatusLine)", False, str(e))
        code = None
        hdrs = None
    if code is not None:
        check("T10.0 respuesta HTTP bien-formada (no BadStatusLine)", True)
        check("T10.1 status 401", code == 401, f"code={code}")
        sc = (hdrs.get("Set-Cookie") or "") if hdrs else ""
        check("T10.2 cookie cleared con Max-Age=0",
              "Max-Age=0" in sc and "ergon_session=;" in sc, f"sc={sc[:80]}")

    # ========================================================================
    # Resumen
    # ========================================================================
    print()
    print("=" * 60)
    print(f"PASS: {len(PASS)}    FAIL: {len(FAIL)}")
    if FAIL:
        print("Fallados:")
        for n in FAIL:
            print(f"  - {n}")
        sys.exit(1)
    print("F13 smoke test: TODOS OK")


if __name__ == "__main__":
    main()
