import asyncio
from unittest.mock import MagicMock

import pytest
import pytest_asyncio
from trame.app import asynchronous, get_client, get_server


@pytest_asyncio.fixture
async def server():
    server = get_server("test_client_connection")
    server.start(exec_mode="task", port=0)
    assert await server.ready
    assert server.running
    try:
        yield server
    finally:
        await asyncio.sleep(0.5)
        await server.stop()


@pytest_asyncio.fixture
async def client(server):
    url = f"ws://localhost:{server.port}/ws"
    client = get_client(url)
    asynchronous.create_task(client.connect(secret="wslink-secret"))

    for _ in range(10):
        if client.connected == 2:
            break
        await asyncio.sleep(0.1)

    # should be a noop
    await client.connect()
    assert client.connected == 2

    try:
        yield client
    finally:
        await asyncio.sleep(0.1)
        await client.disconnect()


@pytest.mark.asyncio
async def test_client_connection(server, client):
    @client.change("a")
    def on_change(a, **_):
        assert a == 2

    @server.trigger("add")
    def server_method(*args):
        result = 0
        for v in args:
            result += v
        return result

    with server.state as state:
        state.a = 2

    await server.network_completion
    await asyncio.sleep(0.1)  # wait for client network

    assert server.state.a == client.state.a

    with client.state as state:
        state.b = {"a": 1, "b": 2, "_filter": ["b"]}

    await asyncio.sleep(0.1)  # wait for client network
    assert server.state.b == {"a": 1, "_filter": ["b"]}

    assert await client.call_trigger("add", [1, 2, 3]) == 6
    assert await client.call_trigger("add") == 0


@pytest.mark.asyncio
async def test_given_suppress_listeners_sends_updates_to_client_only(server, client):
    state_key = "my_state_key"

    on_call = MagicMock()
    server.state.change(state_key)(on_call)
    with server.state.suppress_change_listeners(state_key):
        server.state[state_key] = 42
        server.state.flush()

    await server.network_completion
    await asyncio.sleep(0.1)

    assert client.state[state_key] == 42
    on_call.assert_not_called()
