"""
UDP <-> WebSocket Bridge for Minecraft Bedrock Edition (Geyser)

Runs a WebSocket server. Each incoming WebSocket connection gets its own
UDP socket to communicate with Geyser. Packets are relayed bidirectionally.

Flow:
  Bedrock Client -> [Client Bridge] -> WebSocket -> [This Bridge] -> UDP -> Geyser
"""

import asyncio
import logging
import argparse

try:
    import websockets
except ImportError:
    raise SystemExit("Install websockets: pip install websockets")

logger = logging.getLogger("udp-ws-bridge")


class GeyserRelay(asyncio.DatagramProtocol):
    """Relays UDP responses from Geyser back through the WebSocket."""

    def __init__(self, websocket):
        self.websocket = websocket
        self.transport = None

    def connection_made(self, transport):
        self.transport = transport

    def datagram_received(self, data, addr):
        asyncio.ensure_future(self._forward(data))

    async def _forward(self, data):
        try:
            await self.websocket.send(data)
        except websockets.ConnectionClosed:
            pass

    def error_received(self, exc):
        logger.warning("UDP error: %s", exc)


async def handle_connection(websocket, geyser_host: str, geyser_port: int):
    """Bridge one WebSocket client to Geyser over UDP."""
    peer = websocket.remote_address
    logger.info("New WebSocket client: %s", peer)

    loop = asyncio.get_event_loop()
    transport, protocol = await loop.create_datagram_endpoint(
        lambda: GeyserRelay(websocket),
        remote_addr=(geyser_host, geyser_port),
    )

    try:
        async for message in websocket:
            if isinstance(message, str):
                message = message.encode("utf-8")
            if isinstance(message, bytes):
                transport.sendto(message)
    except websockets.ConnectionClosed:
        logger.info("Client %s disconnected", peer)
    except Exception as exc:
        logger.error("Bridge error for %s: %s", peer, exc)
    finally:
        transport.close()
        logger.info("Relay closed for %s", peer)


async def run_bridge(ws_host: str, ws_port: int, geyser_host: str, geyser_port: int):
    """Main bridge coroutine."""
    logger.info(
        "UDP<->WS Bridge: ws://%s:%s <-> udp://%s:%s",
        ws_host, ws_port, geyser_host, geyser_port,
    )

    async with websockets.serve(
        lambda ws: handle_connection(ws, geyser_host, geyser_port),
        ws_host,
        ws_port,
        max_size=None,
        ping_interval=20,
        ping_timeout=30,
    ):
        logger.info("Bridge ready – waiting for connections…")
        await asyncio.Future()  # run forever


def main():
    """CLI entry point."""
    logging.basicConfig(
        level=logging.INFO,
        format="%(asctime)s [%(levelname)s] %(message)s",
    )
    
    parser = argparse.ArgumentParser(description="UDP<->WS Bridge for Geyser")
    parser.add_argument("--ws-host", default="127.0.0.1")
    parser.add_argument("--ws-port", type=int, default=8765)
    parser.add_argument("--geyser-host", default="127.0.0.1")
    parser.add_argument("--geyser-port", type=int, default=19132)
    args = parser.parse_args()

    try:
        asyncio.run(run_bridge(args.ws_host, args.ws_port, args.geyser_host, args.geyser_port))
    except KeyboardInterrupt:
        logger.info("Shutting down…")


if __name__ == "__main__":
    main()
