"""Pulsa una tecla nombrada sobre el elemento enfocado de una pestana de Chrome via CDP.

Primitiva de input CDP: localiza un target (pestana) por substring de su URL, abre el
WebSocket de depuracion y emite el par de eventos `Input.dispatchKeyEvent`
(rawKeyDown + keyUp) de una tecla con nombre canonico (Enter, Escape, Backspace, Tab,
ArrowDown, ...) con modificadores opcionales. Base reutilizable para enviar mensajes
(Enter en el composer de WhatsApp), cerrar overlays (Escape), navegar resultados
(ArrowDown), borrar (Backspace) o combos (Ctrl+A con modifiers).
"""

import json
import urllib.request

import websocket

# Mapa de teclas soportadas -> {key, code, windowsVirtualKeyCode}.
# vk = windowsVirtualKeyCode == nativeVirtualKeyCode (codigos VK de Windows).
_KEY_MAP = {
    "Enter": {"key": "Enter", "code": "Enter", "vk": 13},
    "Escape": {"key": "Escape", "code": "Escape", "vk": 27},
    "Backspace": {"key": "Backspace", "code": "Backspace", "vk": 8},
    "Tab": {"key": "Tab", "code": "Tab", "vk": 9},
    "Delete": {"key": "Delete", "code": "Delete", "vk": 46},
    "ArrowDown": {"key": "ArrowDown", "code": "ArrowDown", "vk": 40},
    "ArrowUp": {"key": "ArrowUp", "code": "ArrowUp", "vk": 38},
    "ArrowLeft": {"key": "ArrowLeft", "code": "ArrowLeft", "vk": 37},
    "ArrowRight": {"key": "ArrowRight", "code": "ArrowRight", "vk": 39},
    "Home": {"key": "Home", "code": "Home", "vk": 36},
    "End": {"key": "End", "code": "End", "vk": 35},
}


def cdp_press_key(
    key: str,
    *,
    port: int = 9222,
    target_url_substr: str = "",
    modifiers: int = 0,
    timeout_s: float = 10.0,
) -> dict:
    """Pulsa una tecla nombrada sobre el elemento enfocado de una pestana de Chrome.

    Args:
        key: Nombre canonico de la tecla. Soportadas: Enter, Escape, Backspace,
            Tab, Delete, ArrowDown, ArrowUp, ArrowLeft, ArrowRight, Home, End.
        port: Puerto de remote debugging de Chrome. Default 9222.
        target_url_substr: Substring que debe contener la URL del target (pestana).
            Si "", usa el primer target de tipo "page".
        modifiers: Bitmask de modificadores CDP (Alt=1, Ctrl=2, Meta/Cmd=4,
            Shift=8; combinables con OR). Default 0 (sin modificadores).
        timeout_s: Timeout (segundos) para la conexion WebSocket. Default 10.0.

    Returns:
        dict con claves:
            ok: bool — True si los eventos de tecla se emitieron sin error.
            error: str — mensaje de error (vacio si ok).

        Nunca lanza: errores de red/conexion/transport se devuelven en "error"
        con ok=False.
    """
    # 1. Validar la tecla contra el mapa interno (antes de tocar la red).
    entry = _KEY_MAP.get(key)
    if entry is None:
        return {"ok": False, "error": f"unsupported key: {key}"}

    k = entry["key"]
    c = entry["code"]
    vk = entry["vk"]

    # 2. Listar targets via HTTP.
    try:
        with urllib.request.urlopen(
            f"http://127.0.0.1:{port}/json", timeout=5
        ) as resp:
            targets = json.loads(resp.read().decode())
    except Exception as e:  # noqa: BLE001 — red/HTTP/JSON, no relanzar
        return {"ok": False, "error": str(e)}

    # 3. Elegir el primer target type=="page" cuya url contenga el substring.
    chosen = None
    for t in targets:
        if t.get("type") != "page":
            continue
        url = t.get("url", "")
        if target_url_substr == "" or target_url_substr in url:
            chosen = t
            break

    if chosen is None:
        return {"ok": False, "error": f"no target matching {target_url_substr}"}

    ws_url = chosen.get("webSocketDebuggerUrl", "")

    # 4. Abrir WS y emitir rawKeyDown + keyUp.
    try:
        ws = websocket.create_connection(ws_url, timeout=timeout_s)
    except Exception as e:  # noqa: BLE001 — conexion WS
        return {"ok": False, "error": str(e)}

    try:
        for msg_id, ev_type in ((1, "rawKeyDown"), (2, "keyUp")):
            ws.send(json.dumps({
                "id": msg_id,
                "method": "Input.dispatchKeyEvent",
                "params": {
                    "type": ev_type,
                    "key": k,
                    "code": c,
                    "windowsVirtualKeyCode": vk,
                    "nativeVirtualKeyCode": vk,
                    "modifiers": modifiers,
                },
            }))

        # Drenar respuestas hasta ver el id==2 (o agotar el stream). Ignoramos
        # eventos del server y frames no-JSON.
        while True:
            raw = ws.recv()
            if not raw:
                break
            try:
                parsed = json.loads(raw)
            except Exception:  # noqa: BLE001 — frame no-JSON, ignorar
                continue
            if parsed.get("id") == 2:
                break
    except Exception as e:  # noqa: BLE001 — fallo de transport durante send/recv
        return {"ok": False, "error": str(e)}
    finally:
        try:
            ws.close()
        except Exception:  # noqa: BLE001 — cierre best-effort
            pass

    return {"ok": True, "error": ""}


if __name__ == "__main__":
    import sys

    key_arg = sys.argv[1] if len(sys.argv) > 1 else "Enter"
    substr = sys.argv[2] if len(sys.argv) > 2 else ""
    out = cdp_press_key(key_arg, port=9222, target_url_substr=substr)
    print(json.dumps(out, ensure_ascii=False, indent=2))
