# std imports
import asyncio

# local
from telnetlib3.telopt import IAC, WILL, WONT, TTYPE, BINARY
from telnetlib3.tests.accessories import create_server, asyncio_connection


async def test_server_wait_for_client(bind_host, unused_tcp_port):
    """Test Server.wait_for_client() returns protocol after negotiation."""
    async with create_server(host=bind_host, port=unused_tcp_port, connect_maxwait=0.5) as server:
        async with asyncio_connection(bind_host, unused_tcp_port) as (reader, writer):
            writer.write(IAC + WONT + TTYPE)
            client = await asyncio.wait_for(server.wait_for_client(), 0.5)
            assert client is not None
            assert hasattr(client, "writer")
            assert hasattr(client, "reader")


async def test_server_clients_list(bind_host, unused_tcp_port):
    """Test Server.clients property returns list of connected protocols."""
    async with create_server(host=bind_host, port=unused_tcp_port, connect_maxwait=0.5) as server:
        assert server.clients == []

        async with asyncio_connection(bind_host, unused_tcp_port) as (reader, writer):
            writer.write(IAC + WONT + TTYPE)
            client = await asyncio.wait_for(server.wait_for_client(), 0.5)
            assert len(server.clients) == 1
            # client is a weakref proxy, clients[0] is actual protocol
            assert server.clients[0] == client


async def test_server_client_disconnect_cleanup(bind_host, unused_tcp_port):
    """Test that clients are removed from list on disconnect."""
    async with create_server(host=bind_host, port=unused_tcp_port, connect_maxwait=0.5) as server:
        async with asyncio_connection(bind_host, unused_tcp_port) as (reader, writer):
            writer.write(IAC + WONT + TTYPE)
            client = await asyncio.wait_for(server.wait_for_client(), 0.5)
            assert len(server.clients) == 1

        # Connection closed, wait a moment for cleanup
        await asyncio.sleep(0.05)
        assert len(server.clients) == 0


async def test_server_is_serving(bind_host, unused_tcp_port):
    """Test Server.is_serving() delegates to underlying server."""
    async with create_server(host=bind_host, port=unused_tcp_port) as server:
        assert server.is_serving() is True

    # After context exit, server is closed
    assert server.is_serving() is False


async def test_server_sockets(bind_host, unused_tcp_port):
    """Test Server.sockets property returns socket list."""
    async with create_server(host=bind_host, port=unused_tcp_port) as server:
        assert server.sockets is not None
        assert len(server.sockets) > 0


async def test_server_with_wait_for(bind_host, unused_tcp_port):
    """Test integration of Server.wait_for_client() with writer.wait_for()."""
    async with create_server(host=bind_host, port=unused_tcp_port, connect_maxwait=2.0) as server:
        async with asyncio_connection(bind_host, unused_tcp_port) as (reader, writer):
            # Send WILL BINARY and WONT TTYPE
            writer.write(IAC + WILL + BINARY)
            writer.write(IAC + WONT + TTYPE)

            client = await asyncio.wait_for(server.wait_for_client(), 5.0)

            # Use wait_for to check specific negotiation state
            await asyncio.wait_for(client.writer.wait_for(remote={"BINARY": True}), 5.0)
            assert client.writer.remote_option[BINARY] is True


async def test_server_multiple_sequential_clients(bind_host, unused_tcp_port):
    """Test wait_for_client() works for multiple sequential connections."""
    async with create_server(host=bind_host, port=unused_tcp_port, connect_maxwait=0.5) as server:
        # First client
        async with asyncio_connection(bind_host, unused_tcp_port) as (reader1, writer1):
            writer1.write(IAC + WONT + TTYPE)
            client1 = await asyncio.wait_for(server.wait_for_client(), 0.5)
            assert client1 is not None

        # Wait for first client to disconnect
        await asyncio.sleep(0.05)

        # Second client
        async with asyncio_connection(bind_host, unused_tcp_port) as (reader2, writer2):
            writer2.write(IAC + WONT + TTYPE)
            client2 = await asyncio.wait_for(server.wait_for_client(), 0.5)
            assert client2 is not None
            assert client2 is not client1


async def test_create_server_line_mode_param(bind_host, unused_tcp_port):
    """create_server() accepts line_mode=True without error."""
    async with create_server(
        host=bind_host, port=unused_tcp_port, line_mode=True, connect_maxwait=0.5
    ) as server:
        async with asyncio_connection(bind_host, unused_tcp_port) as (reader, writer):
            writer.write(IAC + WONT + TTYPE)
            client = await asyncio.wait_for(server.wait_for_client(), 0.5)
            assert client is not None
            assert client.line_mode is True


async def test_create_server_line_mode_default_false(bind_host, unused_tcp_port):
    """create_server() defaults to line_mode=False."""
    async with create_server(host=bind_host, port=unused_tcp_port, connect_maxwait=0.5) as server:
        async with asyncio_connection(bind_host, unused_tcp_port) as (reader, writer):
            writer.write(IAC + WONT + TTYPE)
            client = await asyncio.wait_for(server.wait_for_client(), 0.5)
            assert client.line_mode is False


async def test_enqueue_client_queue_full():
    """_enqueue_client discards oldest entry when bounded queue is full."""
    from telnetlib3.server import Server, _enqueue_client
    from telnetlib3.server_base import BaseServer

    class _TestProto(BaseServer):
        def connection_made(self, transport):
            pass

    server = Server(None)
    server._new_client = asyncio.Queue(maxsize=1)

    p1 = _TestProto(shell=lambda _: None, _waiter_connected=asyncio.Future())
    p1._waiter_connected.set_result(None)
    _enqueue_client(server, p1)
    assert server._new_client.qsize() == 1

    p2 = _TestProto(shell=lambda _: None, _waiter_connected=asyncio.Future())
    p2._waiter_connected.set_result(None)
    _enqueue_client(server, p2)
    assert server._new_client.qsize() == 1


async def test_register_protocol_prunes_dead():
    """_register_protocol removes closed protocols before appending."""
    from telnetlib3.server import Server

    class _FakeProto:
        _closing = True
        _waiter_connected = None

    class _LiveProto:
        _closing = False
        _waiter_connected = asyncio.Future()

    server = Server(None)
    server._protocols = [_FakeProto()]
    server._register_protocol(_LiveProto())
    assert len(server._protocols) == 1
    assert isinstance(server._protocols[0], _LiveProto)
