# -*- coding: utf-8 -*-
# ------------------------------------------------------------------------------
#
#   Copyright 2018-2019 Fetch.AI Limited
#
#   Licensed under the Apache License, Version 2.0 (the "License");
#   you may not use this file except in compliance with the License.
#   You may obtain a copy of the License at
#
#       http://www.apache.org/licenses/LICENSE-2.0
#
#   Unless required by applicable law or agreed to in writing, software
#   distributed under the License is distributed on an "AS IS" BASIS,
#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
#   See the License for the specific language governing permissions and
#   limitations under the License.
#
# ------------------------------------------------------------------------------

"""This module contains the tests for the TCP base module."""

import asyncio
import unittest.mock
from asyncio import CancelledError

import pytest

from aea.configurations.base import PublicId
from aea.mail.base import Envelope
from aea.protocols.default.message import DefaultMessage

import packages
from packages.fetchai.connections.tcp.connection import (
    TCPClientConnection,
    TCPServerConnection,
)

from ....conftest import get_unused_tcp_port


@pytest.mark.asyncio
async def test_connect_twice():
    """Test that connecting twice the tcp connection works correctly."""
    port = get_unused_tcp_port()
    tcp_connection = TCPServerConnection(
        "address", "127.0.0.1", port, connection_id=PublicId("fetchai", "tcp", "0.1.0")
    )

    loop = asyncio.get_event_loop()
    tcp_connection.loop = loop

    await tcp_connection.connect()
    await asyncio.sleep(0.1)
    with unittest.mock.patch.object(
        packages.fetchai.connections.tcp.base.logger, "warning"
    ) as mock_logger_warning:
        await tcp_connection.connect()
        mock_logger_warning.assert_called_with("Connection already set up.")

    await tcp_connection.disconnect()


@pytest.mark.asyncio
async def test_connect_raises_exception():
    """Test the case that a connection attempt raises an exception."""
    port = get_unused_tcp_port()
    tcp_connection = TCPServerConnection(
        "address", "127.0.0.1", port, connection_id=PublicId("fetchai", "tcp", "0.1.0")
    )

    loop = asyncio.get_event_loop()
    tcp_connection.loop = loop

    with unittest.mock.patch.object(
        packages.fetchai.connections.tcp.base.logger, "error"
    ) as mock_logger_error:
        with unittest.mock.patch.object(
            tcp_connection, "setup", side_effect=Exception("error during setup")
        ):
            await tcp_connection.connect()
            mock_logger_error.assert_called_with("error during setup")


@pytest.mark.asyncio
async def test_disconnect_when_already_disconnected():
    """Test that disconnecting a connection already disconnected works correctly."""
    port = get_unused_tcp_port()
    tcp_connection = TCPServerConnection(
        "address", "127.0.0.1", port, connection_id=PublicId("fetchai", "tcp", "0.1.0")
    )

    with unittest.mock.patch.object(
        packages.fetchai.connections.tcp.base.logger, "warning"
    ) as mock_logger_warning:
        await tcp_connection.disconnect()
        mock_logger_warning.assert_called_with("Connection already disconnected.")


@pytest.mark.asyncio
async def test_send_to_unknown_destination():
    """Test that a message to an unknown destination logs an error."""
    address = "address"
    port = get_unused_tcp_port()
    tcp_connection = TCPServerConnection(
        address, "127.0.0.1", port, connection_id=PublicId("fetchai", "tcp", "0.1.0")
    )
    envelope = Envelope(
        to="non_existing_destination",
        sender="address",
        protocol_id=DefaultMessage.protocol_id,
        message=b"",
    )
    with unittest.mock.patch.object(
        packages.fetchai.connections.tcp.base.logger, "error"
    ) as mock_logger_error:
        await tcp_connection.send(envelope)
        mock_logger_error.assert_called_with(
            "[{}]: Cannot send envelope {}".format(address, envelope)
        )


@pytest.mark.asyncio
async def test_send_cancelled():
    """Test that cancelling a send works correctly."""
    port = get_unused_tcp_port()
    tcp_server = TCPServerConnection(
        "address_server",
        "127.0.0.1",
        port,
        connection_id=PublicId("fetchai", "tcp", "0.1.0"),
    )
    tcp_client = TCPClientConnection(
        "address_client",
        "127.0.0.1",
        port,
        connection_id=PublicId("fetchai", "tcp", "0.1.0"),
    )

    await tcp_server.connect()
    await tcp_client.connect()

    with unittest.mock.patch.object(
        tcp_client._writer, "drain", side_effect=CancelledError
    ):
        envelope = Envelope(
            to="address_client",
            sender="address_server",
            protocol_id=DefaultMessage.protocol_id,
            message=b"",
        )
        await tcp_client.send(envelope)

    await tcp_client.disconnect()
    await tcp_server.disconnect()
