"""Login headless contra un Hoppscotch self-hosted via magic link.

Reproduce el flujo de magic link de Hoppscotch sin navegador, leyendo el
correo de verificacion desde una instancia Mailpit de pruebas:

  1. POST /v1/auth/signin       -> deviceIdentifier
  2. GET  mailpit messages      -> ultimo correo "Sign in" para ese email
  3. GET  mailpit message/{id}  -> extrae el token (?token=...) del cuerpo
  4. POST /v1/auth/verify       -> Set-Cookie access_token + refresh_token

Devuelve los JWT de sesion (access_token / refresh_token). El access_token es
el que las mutations GraphQL protegidas por GqlAuthGuard esperan en la cookie
`access_token` (no en el header Authorization).
"""

import re

import requests

# El correo de Hoppscotch incluye un enlace con ?token=<jwt>. El token es un
# JWT (3 segmentos base64url separados por puntos), asi que aceptamos letras,
# digitos, guion, guion bajo y punto.
_TOKEN_RE = re.compile(r"token=([A-Za-z0-9_\-.]+)")


def hoppscotch_login(
    email: str,
    *,
    backend_url: str = "http://localhost:3170",
    mailpit_url: str = "http://localhost:8025",
    timeout_s: float = 15.0,
) -> dict:
    """Obtiene un JWT de sesion de Hoppscotch via magic link (headless).

    Args:
        email: correo del usuario que inicia sesion. Debe poder recibir el
            correo de verificacion en la instancia Mailpit indicada.
        backend_url: base del backend Hoppscotch (sin barra final). El endpoint
            REST de auth cuelga de ``{backend_url}/v1/auth/...``.
        mailpit_url: base de la API de Mailpit donde aterriza el correo de
            verificacion (sin barra final).
        timeout_s: timeout por request HTTP en segundos.

    Returns:
        Dict. En exito:
        ``{"status": "ok", "access_token": str, "refresh_token": str,
        "email": str}``. En error (signin no 201, no llega correo, token no
        encontrado, verify no 200, o fallo de transporte):
        ``{"status": "error", "error": str}``.
    """
    session = requests.Session()

    try:
        # 1) Signin: pide el magic link. Respuesta 201 con deviceIdentifier.
        signin = session.post(
            f"{backend_url}/v1/auth/signin",
            json={"email": email},
            timeout=timeout_s,
        )
        if signin.status_code != 201:
            return {
                "status": "error",
                "error": (
                    f"signin returned {signin.status_code} "
                    f"(expected 201): {signin.text[:200]}"
                ),
            }
        try:
            device_identifier = signin.json().get("deviceIdentifier")
        except ValueError:
            device_identifier = None
        if not device_identifier:
            return {
                "status": "error",
                "error": "signin response missing deviceIdentifier",
            }

        # 2) Localiza el correo de verificacion mas reciente para este email.
        messages = session.get(
            f"{mailpit_url}/api/v1/messages",
            params={"limit": 5},
            timeout=timeout_s,
        )
        if messages.status_code != 200:
            return {
                "status": "error",
                "error": (
                    f"mailpit messages returned {messages.status_code} "
                    "(expected 200)"
                ),
            }
        try:
            inbox = messages.json().get("messages") or []
        except ValueError:
            return {
                "status": "error",
                "error": "mailpit messages response is not valid JSON",
            }

        message_id = None
        for msg in inbox:
            recipients = msg.get("To") or []
            to_match = any(
                (addr.get("Address") or "").lower() == email.lower()
                for addr in recipients
            )
            subject = msg.get("Subject") or ""
            if to_match and "Sign in" in subject:
                message_id = msg.get("ID")
                break
        if not message_id:
            return {
                "status": "error",
                "error": f"no 'Sign in' email found for {email} in mailpit",
            }

        # 3) Descarga el cuerpo del correo y extrae el token.
        message = session.get(
            f"{mailpit_url}/api/v1/message/{message_id}",
            timeout=timeout_s,
        )
        if message.status_code != 200:
            return {
                "status": "error",
                "error": (
                    f"mailpit message returned {message.status_code} "
                    "(expected 200)"
                ),
            }
        try:
            body = message.json()
        except ValueError:
            return {
                "status": "error",
                "error": "mailpit message response is not valid JSON",
            }
        haystack = f"{body.get('Text') or ''}\n{body.get('HTML') or ''}"
        token_match = _TOKEN_RE.search(haystack)
        if not token_match:
            return {
                "status": "error",
                "error": "magic-link token not found in verification email",
            }
        token = token_match.group(1)

        # 4) Verify: canjea el token + deviceIdentifier por las cookies de
        #    sesion (access_token / refresh_token).
        verify = session.post(
            f"{backend_url}/v1/auth/verify",
            json={"token": token, "deviceIdentifier": device_identifier},
            timeout=timeout_s,
        )
        if verify.status_code != 200:
            return {
                "status": "error",
                "error": (
                    f"verify returned {verify.status_code} "
                    f"(expected 200): {verify.text[:200]}"
                ),
            }

        access_token = session.cookies.get("access_token")
        refresh_token = session.cookies.get("refresh_token")
        if not access_token:
            return {
                "status": "error",
                "error": "verify succeeded but no access_token cookie was set",
            }

        return {
            "status": "ok",
            "access_token": access_token,
            "refresh_token": refresh_token,
            "email": email,
        }
    except requests.RequestException as exc:
        return {"status": "error", "error": f"transport error: {exc}"}
    finally:
        session.close()
