"""Client-side discovery helpers."""

from __future__ import annotations

import asyncio
from dataclasses import dataclass

from cliproom.protocol import (
    DEFAULT_DISCOVERY_PORT,
    DEFAULT_DISCOVERY_TIMEOUT_SECONDS,
    DiscoverMessage,
    DiscoverReplyMessage,
    decode_message_line,
    encode_message,
)


@dataclass(slots=True, frozen=True)
class DiscoveredSession:
    session: str
    host: str
    port: int
    requires_passphrase: bool


class _DiscoveryClientProtocol(asyncio.DatagramProtocol):
    def __init__(self) -> None:
        self.responses: list[DiscoveredSession] = []
        self.transport: asyncio.DatagramTransport | None = None

    def connection_made(self, transport: asyncio.BaseTransport) -> None:
        self.transport = transport  # type: ignore[assignment]

    def datagram_received(self, data: bytes, addr: tuple[str, int]) -> None:
        del addr
        try:
            message = decode_message_line(data)
        except Exception:
            return
        if not isinstance(message, DiscoverReplyMessage):
            return
        self.responses.append(
            DiscoveredSession(
                session=message.session,
                host=message.host,
                port=message.port,
                requires_passphrase=message.requires_passphrase,
            )
        )


async def discover_sessions(
    *,
    broadcast_host: str = "255.255.255.255",
    discovery_port: int = DEFAULT_DISCOVERY_PORT,
    timeout_seconds: float = DEFAULT_DISCOVERY_TIMEOUT_SECONDS,
) -> list[DiscoveredSession]:
    """Broadcast discover and collect replies until timeout."""
    loop = asyncio.get_running_loop()
    protocol = _DiscoveryClientProtocol()
    transport, _ = await loop.create_datagram_endpoint(
        lambda: protocol,
        local_addr=("0.0.0.0", 0),
        allow_broadcast=True,
    )
    try:
        payload = encode_message(DiscoverMessage())
        transport.sendto(payload, (broadcast_host, discovery_port))
        await asyncio.sleep(timeout_seconds)
    finally:
        transport.close()

    unique = {
        (item.session, item.host, item.port, item.requires_passphrase): item
        for item in protocol.responses
    }
    return sorted(unique.values(), key=lambda x: (x.session, x.host, x.port))

