"""Klien Chrome DevTools Protocol minimal (asyncio + websockets)."""
from __future__ import annotations

import asyncio
import json
import logging
import urllib.request
from typing import Awaitable, Callable

import websockets

log = logging.getLogger("agent.cdp")


class CDPError(Exception):
    pass


class CDP:
    def __init__(self, ws_url: str, on_event: Callable[[str, dict, str | None], Awaitable[None]]):
        self.ws_url, self.on_event = ws_url, on_event
        self.ws = None
        self._id = 0
        self._pending: dict[int, asyncio.Future] = {}
        self._reader: asyncio.Task | None = None
        self.closed = asyncio.Event()

    async def connect(self) -> None:
        self.ws = await websockets.connect(self.ws_url, max_size=None, ping_interval=None, open_timeout=5)
        self._reader = asyncio.create_task(self._read())

    async def _read(self) -> None:
        try:
            async for raw in self.ws:
                m = json.loads(raw)
                if "id" in m:
                    fut = self._pending.pop(m["id"], None)
                    if fut and not fut.done():
                        if "error" in m:
                            fut.set_exception(CDPError(m["error"].get("message", str(m["error"]))))
                        else:
                            fut.set_result(m.get("result", {}))
                elif "method" in m:
                    asyncio.create_task(self._dispatch(m["method"], m.get("params", {}), m.get("sessionId")))
        except Exception as e:
            log.debug("cdp reader selesai: %s", e)
        finally:
            for f in self._pending.values():
                if not f.done():
                    f.set_exception(CDPError("koneksi CDP terputus"))
            self._pending.clear()
            self.closed.set()

    async def _dispatch(self, method, params, session_id):
        try:
            await self.on_event(method, params, session_id)
        except Exception:
            log.exception("handler event %s gagal", method)

    async def send(self, method: str, params: dict | None = None, session_id: str | None = None, timeout: float = 6.0) -> dict:
        if self.ws is None or self.closed.is_set():
            raise CDPError("tidak terhubung")
        self._id += 1
        mid = self._id
        fut = asyncio.get_running_loop().create_future()
        self._pending[mid] = fut
        msg = {"id": mid, "method": method, "params": params or {}}
        if session_id:
            msg["sessionId"] = session_id
        await self.ws.send(json.dumps(msg))
        return await asyncio.wait_for(fut, timeout)

    async def close(self) -> None:
        if self.ws:
            await self.ws.close()


def fetch_ws_url(port: int) -> str | None:
    """Ambil URL websocket browser dari endpoint HTTP lokal (blocking; panggil via to_thread)."""
    try:
        with urllib.request.urlopen(f"http://127.0.0.1:{port}/json/version", timeout=1.5) as r:
            return json.loads(r.read().decode()).get("webSocketDebuggerUrl")
    except Exception:
        return None
