"""Minimal DDP (Distributed Data Protocol) client for Rocket Chat subscriptions."""

from __future__ import annotations

import asyncio
import contextlib
import json
from collections.abc import AsyncIterator
from dataclasses import dataclass, field
from typing import Any

import websockets
from websockets import ClientConnection


@dataclass
class DDPClient:
    """Minimal DDP client for real-time subscriptions."""

    url: str
    _ws: ClientConnection | None = field(default=None, repr=False)
    _session: str | None = field(default=None, repr=False)
    _msg_id: int = field(default=0, repr=False)
    _pending: dict[str, asyncio.Future[dict[str, Any]]] = field(
        default_factory=dict, repr=False
    )
    _events: asyncio.Queue[dict[str, Any]] = field(
        default_factory=asyncio.Queue, repr=False
    )
    _receiver_task: asyncio.Task[None] | None = field(default=None, repr=False)

    def _next_id(self) -> str:
        self._msg_id += 1
        return str(self._msg_id)

    async def __aenter__(self) -> DDPClient:
        """Connect to DDP server and establish session."""
        self._ws = await websockets.connect(self.url, open_timeout=30)
        self._receiver_task = asyncio.create_task(self._receiver())

        await self._send({"msg": "connect", "version": "1", "support": ["1"]})

        while self._session is None:
            msg = await self._events.get()
            if msg.get("msg") == "connected":
                self._session = msg.get("session")
            elif msg.get("msg") == "failed":
                raise ConnectionError(f"DDP connection failed: {msg}")

        return self

    async def _send(self, data: dict[str, Any]) -> None:
        """Send a message to the server."""
        if self._ws is None:
            raise RuntimeError("Not connected")
        await self._ws.send(json.dumps(data))

    async def _receiver(self) -> None:
        """Background task to receive and dispatch messages."""
        if self._ws is None:
            return

        try:
            async for raw in self._ws:
                msg = json.loads(raw)
                msg_type = msg.get("msg")

                if msg_type == "ping":
                    await self._send({"msg": "pong"})
                elif msg_type == "result":
                    msg_id = msg.get("id")
                    if msg_id in self._pending:
                        self._pending[msg_id].set_result(msg)
                        del self._pending[msg_id]
                else:
                    await self._events.put(msg)
        except websockets.ConnectionClosed:
            pass
        finally:
            await self._events.put({"msg": "disconnected"})

    async def call(self, method: str, params: list[Any]) -> dict[str, Any]:
        """Call a DDP method and wait for result."""
        msg_id = self._next_id()
        future: asyncio.Future[dict[str, Any]] = (
            asyncio.get_event_loop().create_future()
        )
        self._pending[msg_id] = future

        await self._send(
            {"msg": "method", "method": method, "params": params, "id": msg_id}
        )
        return await future

    async def subscribe(self, name: str, params: list[Any]) -> str:
        """Subscribe to a publication. Returns subscription ID."""
        sub_id = self._next_id()
        await self._send({"msg": "sub", "id": sub_id, "name": name, "params": params})
        return sub_id

    async def unsubscribe(self, sub_id: str) -> None:
        """Unsubscribe from a publication."""
        await self._send({"msg": "unsub", "id": sub_id})

    async def events(self) -> AsyncIterator[dict[str, Any]]:
        """Yield subscription events as they arrive."""
        while True:
            msg = await self._events.get()
            if msg.get("msg") == "disconnected":
                break
            yield msg

    async def __aexit__(self, *exc: object) -> None:
        """Close the connection."""
        if self._receiver_task:
            self._receiver_task.cancel()
            with contextlib.suppress(asyncio.CancelledError):
                await self._receiver_task
        if self._ws:
            await self._ws.close()
