"""
Test file for asynchronous requests.
"""
import time

from osbrain import Agent
from osbrain import run_agent
from osbrain import run_logger
from osbrain.helper import logger_received
from osbrain.helper import sync_agent_logger
from osbrain.helper import wait_agent_attr

from .common import append_received


def on_error(agent):
    agent.error_count += 1


class Server(Agent):
    def on_init(self):
        self.received = []
        self.blocked = False


class Client(Agent):
    def on_init(self):
        self.received = []


def blocked_reply(agent, request):
    agent.blocked = True
    agent.received.append(request)
    while agent.blocked:
        time.sleep(0.01)

    return 'x' + request


def async_client_server(server_handler=blocked_reply):
    server = run_agent('server', base=Server)
    client = run_agent('client', base=Client)

    addr = server.bind('ASYNC_REP', alias='replier', handler=server_handler)
    client.connect(addr, alias='async', handler=append_received)

    return (client, server)


def async_client_server_pair(server_handler=blocked_reply):
    client, server1 = async_client_server(server_handler)

    server2 = run_agent('server2', base=Server)
    addr2 = server2.bind('ASYNC_REP', alias='replier2', handler=server_handler)
    client.connect(addr2, alias='async2', handler=append_received)

    return (client, server1, server2)


def test_return(nsproxy):
    """
    Asynchronous request-reply pattern with a reply handler that returns.
    """
    client, server1, server2 = async_client_server_pair()
    client.send('async', 'foo')
    client.send('async', 'bar')
    client.send('async2', 'qux')

    assert wait_agent_attr(server1, value=['foo'], timeout=0.1)
    assert wait_agent_attr(server2, value=['qux'], timeout=0.1)
    assert not wait_agent_attr(client, length=1, timeout=0.1)

    server2.unsafe.set_attr(blocked=False)
    assert wait_agent_attr(client, value=['xqux'], timeout=0.1)

    server1.unsafe.set_attr(blocked=False)
    assert wait_agent_attr(client, value=['xqux', 'xfoo'], timeout=0.1)
    server1.unsafe.set_attr(blocked=False)
    assert wait_agent_attr(client, value=['xqux', 'xfoo', 'xbar'], timeout=0.1)


def test_yield(nsproxy):
    """
    Asynchronous request-reply pattern with a reply handler that yields.
    """

    def blocked_reply_yield(agent, request):
        agent.received.append(request)
        agent.blocked = True
        while agent.blocked:
            time.sleep(0.01)

        yield 'x' + request
        agent.received.append('y' + request)

    client, server1, server2 = async_client_server_pair(blocked_reply_yield)
    client.send('async', 'foo')
    client.send('async2', 'bar')

    assert wait_agent_attr(server1, value=['foo'], timeout=0.1)
    assert wait_agent_attr(server2, value=['bar'], timeout=0.1)
    assert not wait_agent_attr(client, length=1, timeout=0.1)

    server2.unsafe.set_attr(blocked=False)
    assert wait_agent_attr(client, value=['xbar'], timeout=0.1)
    assert wait_agent_attr(server2, value=['bar', 'ybar'], timeout=0.1)

    server1.unsafe.set_attr(blocked=False)
    assert wait_agent_attr(client, value=['xbar', 'xfoo'], timeout=0.1)
    assert wait_agent_attr(server1, value=['foo', 'yfoo'], timeout=0.1)


def test_unknown(nsproxy):
    """
    When receiving a response for an unknown request (or an already processed
    request), a warning should be logged and handler should not be executed.
    """
    client, server = async_client_server()

    logger = run_logger('logger')
    client.set_logger(logger)
    sync_agent_logger(client, logger)

    client.send('async', 'foo')
    assert wait_agent_attr(server, value=['foo'], timeout=0.1)

    # Manually remove the pending request before it is received
    client.set_attr(_pending_requests={})
    server.unsafe.set_attr(blocked=False)
    assert logger_received(
        logger,
        log_name='log_history_warning',
        message='Received response for an unknown request!',
    )
    assert len(client.get_attr('received')) == 0


def test_wait_in_time(nsproxy):
    """
    Asynchronous request-reply pattern maximum wait.
    """
    client, server = async_client_server()

    logger = run_logger('logger')
    client.set_logger(logger)
    sync_agent_logger(client, logger)

    client.send('async', 'foo', wait=1)
    assert wait_agent_attr(server, value=['foo'])
    server.unsafe.set_attr(blocked=False)
    assert wait_agent_attr(client, value=['xfoo'])


def test_wait_timeout(nsproxy):
    """
    Asynchronous request-reply pattern when exceeding the maximum wait.
    """
    client, server = async_client_server()
    logger = run_logger('logger')
    client.set_logger(logger)
    sync_agent_logger(client, logger)

    client.send('async', 'foo', wait=0.5)
    assert len(client.get_attr('_pending_requests')) == 1
    assert logger_received(
        logger,
        log_name='log_history_warning',
        message='not receive req',
        timeout=1,
    )
    assert len(client.get_attr('_pending_requests')) == 0

    server.unsafe.set_attr(blocked=False)
    assert logger_received(
        logger,
        log_name='log_history_warning',
        message='Received response for an unknown request!',
    )
    assert server.get_attr('received') == ['foo']
    assert client.get_attr('received') == []


def test_wait_on_error(nsproxy):
    """
    Asynchronous request-reply pattern maximum wait with an error handler.
    """
    client, server = async_client_server()

    client.set_attr(error_count=0)
    client.send('async', 'foo', wait=0.5, on_error=on_error)
    assert wait_agent_attr(client, 'error_count', value=1, timeout=1)
    server.unsafe.set_attr(blocked=False)
