from __future__ import annotations

import inspect
import ssl
from collections.abc import Callable
from typing import Any

from google.protobuf.message import Message

try:
    from websockets.asyncio.server import serve as ws_serve

    _NEW_WEBSOCKETS_API = True
except ImportError:  # websockets 12.x legacy API
    from websockets import serve as ws_serve

    _NEW_WEBSOCKETS_API = False

from proto_socket.communicator import ParserMap
from proto_socket.tcp_client import MAX_PACKET_SIZE
from proto_socket.ws_client import WsClient


class WsServer:
    def __init__(
        self,
        host: str,
        port: int,
        path: str,
        new_client: Callable[[Any], WsClient] | None = None,
        interval_sec: int = 0,
        wait_sec: int = 0,
        parser_map: ParserMap | None = None,
        ssl_context: ssl.SSLContext | None = None,
    ) -> None:
        self._host = host
        self._port = port
        self._path = path
        self._new_client = new_client
        self._interval_sec = interval_sec
        self._wait_sec = wait_sec
        self._parser_map = parser_map
        self._ssl_context = ssl_context
        self._clients: list[WsClient] = []
        self._server: Any | None = None
        self.on_client_connected: Callable[[WsClient], object] = lambda _: None

    @property
    def port(self) -> int:
        if self._server is None or not self._server.sockets:
            return self._port
        return int(self._server.sockets[0].getsockname()[1])

    def clients(self) -> list[WsClient]:
        return list(self._clients)

    async def start(self) -> None:
        handler = self._handle_websocket if _NEW_WEBSOCKETS_API else self._handle_legacy_websocket
        self._server = await ws_serve(
            handler,
            self._host,
            self._port,
            ssl=self._ssl_context,
            max_size=MAX_PACKET_SIZE,
        )

    async def stop(self) -> None:
        if self._server is not None:
            self._server.close()
            await self._server.wait_closed()
            self._server = None
        clients = list(self._clients)
        self._clients.clear()
        for client in clients:
            await client.close()

    async def broadcast(self, message: Message) -> None:
        for client in self.clients():
            await client.communicator.send(message)

    async def _handle_websocket(self, ws: Any) -> None:
        if self._new_client is not None:
            client = self._new_client(ws)
        else:
            if self._parser_map is None:
                raise ValueError("parser_map is required when new_client is not provided")
            client = WsClient(ws, self._interval_sec, self._wait_sec, self._parser_map)

        self._clients.append(client)
        client.add_disconnect_listener(lambda c: self._remove_client(c))
        result = self.on_client_connected(client)
        if inspect.isawaitable(result):
            await result
        await client.wait_closed()

    def _remove_client(self, client: WsClient) -> None:
        if client in self._clients:
            self._clients.remove(client)

    async def _handle_legacy_websocket(self, ws: Any, _path: str) -> None:
        await self._handle_websocket(ws)
