# -*- coding: utf-8 -*-
import asyncio
from unittest.mock import patch

import pytest

import cabbagok
from tests.conftest import (
    RANDOM_QUEUE,
    TEST_DESTINATION,
    RESPONSE_CORR_ID,
    MockEnvelope,
    MockProperties,
    DELIVERY_TAG,
)

pytestmark = pytest.mark.asyncio


class TestSendRpc:
    """AsyncAmqpRpc.send_rpc"""

    @pytest.mark.parametrize(
        "data, expected_payload",
        [
            ("", b""),
            (b"", b""),
            (
                "Test message body. Тест",
                b"Test message body. \xd0\xa2\xd0\xb5\xd1\x81\xd1\x82",
            ),
            (b"Raw message \xff", b"Raw message \xff"),
        ],
    )
    async def test_no_response(self, connection, data, expected_payload):
        """Check that data is correctly encoded (if needed) and sent."""
        rpc = cabbagok.AsyncAmqpRpc(connection=connection)
        await rpc.connect()
        await rpc.send_rpc(
            destination=TEST_DESTINATION, data=data, await_response=False
        )
        rpc.channel.basic_publish.assert_called_once_with(
            exchange_name="",
            routing_key=TEST_DESTINATION,
            properties={},
            payload=expected_payload,
        )

    @pytest.mark.parametrize(
        "data, sent_payload, received_payload, expected_result",
        [
            ("request data", b"request data", b"", ""),
            (b"raw request", b"raw request", b"", b""),
            (
                "request data",
                b"request data",
                b"Test. \xd0\xa2\xd0\xb5\xd1\x81\xd1\x82",
                "Test. Тест",
            ),
            (b"raw request", b"raw request", b"Raw message \xff", b"Raw message \xff"),
        ],
    )
    async def test_await_response(
        self, connection, data, sent_payload, received_payload, expected_result
    ):
        """Check that data returned by await_response is parsed and returned correctly."""
        rpc = cabbagok.AsyncAmqpRpc(connection=connection)
        await rpc.connect()
        with patch(
            "cabbagok.amqp.AsyncAmqpRpc._await_response",
            return_value=received_payload,
            autospec=True,
        ), patch("cabbagok.amqp.uuid.uuid4", return_value=RESPONSE_CORR_ID):
            result = await rpc.send_rpc(
                destination=TEST_DESTINATION, data=data, await_response=True
            )
        assert result == expected_result
        rpc.channel.basic_publish.assert_called_once_with(
            exchange_name="",
            routing_key=TEST_DESTINATION,
            payload=sent_payload,
            properties={"reply_to": RANDOM_QUEUE, "correlation_id": RESPONSE_CORR_ID},
        )

    @pytest.mark.parametrize(
        "correlation_id, expected",
        [
            ("my-correlation-id", "my-correlation-id"),
            (None, RESPONSE_CORR_ID),  # generated by the library
        ],
    )
    async def test_correlation_id(self, connection, correlation_id, expected):
        """Check that correlation id either matches the custom id or is generated from uuid."""
        rpc = cabbagok.AsyncAmqpRpc(connection=connection)
        await rpc.connect()
        with patch(
            "cabbagok.amqp.AsyncAmqpRpc._await_response",
            return_value=b"response",
            autospec=True,
        ), patch("cabbagok.amqp.uuid.uuid4", return_value=RESPONSE_CORR_ID):
            await rpc.send_rpc(
                destination=TEST_DESTINATION,
                data="payload",
                await_response=True,
                correlation_id=correlation_id,
            )
        rpc.channel.basic_publish.assert_called_once_with(
            exchange_name="",
            routing_key=TEST_DESTINATION,
            payload=b"payload",
            properties={"reply_to": RANDOM_QUEUE, "correlation_id": expected},
        )


class TestAwaitResponse:
    """AsyncAmqpRpc._await_response"""

    async def test_ok(self, rpc):
        loop = asyncio.get_running_loop()
        # schedule awaiting response in another Task
        task = asyncio.ensure_future(
            rpc._await_response(correlation_id=RESPONSE_CORR_ID, timeout=10.0)
        )
        # but it's not executing yet
        assert set(rpc._responses.keys()) == set()
        # let it run for a bit
        await asyncio.sleep(0)
        # check that it created a Future
        assert set(rpc._responses.keys()) == {RESPONSE_CORR_ID}
        # set Future result
        rpc._responses[RESPONSE_CORR_ID].set_result("task result")
        # let the Task run to completion
        await task
        assert task.done()
        assert task.result() == "task result"
        # check that it cleaned up rpc._responses
        assert set(rpc._responses.keys()) == set()

    async def test_timeout(self, rpc):
        with pytest.raises(cabbagok.ServiceUnavailableError):
            await rpc._await_response(correlation_id=RESPONSE_CORR_ID, timeout=0)
        assert set(rpc._responses.keys()) == set()


class TestOnResponse:
    """AsyncAmqpRpc._on_response: aioamqp handler for messages in callback queue."""

    @pytest.mark.parametrize(
        "body", [b"", b"Test message body. \xd0\xa2\xd0\xb5\xd1\x81\xd1\x82"]
    )
    async def test_ok(self, rpc, body):
        rpc._responses[RESPONSE_CORR_ID] = asyncio.get_running_loop().create_future()
        await rpc._on_response(
            channel=rpc.channel,
            body=body,
            envelope=MockEnvelope(),
            properties=MockProperties(),
        )
        assert rpc._responses[RESPONSE_CORR_ID].done()
        assert rpc._responses[RESPONSE_CORR_ID].result() == body
        rpc.channel.basic_client_ack.assert_called_once_with(delivery_tag=DELIVERY_TAG)
        rpc.channel.basic_client_nack.assert_not_called()

    async def test_unexpected_tag(self, rpc):
        await rpc._on_response(
            channel=rpc.channel,
            body=b"resp",
            envelope=MockEnvelope(),
            properties=MockProperties(),
        )
        rpc.channel.basic_client_ack.assert_called_once_with(delivery_tag=DELIVERY_TAG)
