# -*- 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 dialogue/base.py module."""

import sys
from typing import FrozenSet, Tuple, Type, cast
from unittest import mock

import pytest

import aea
from aea.common import Address
from aea.exceptions import AEAEnforceError
from aea.protocols.base import Message
from aea.protocols.default.message import DefaultMessage
from aea.protocols.dialogue.base import Dialogue as BaseDialogue
from aea.protocols.dialogue.base import DialogueLabel, DialogueMessage, DialogueStats
from aea.protocols.dialogue.base import Dialogues as BaseDialogues
from aea.protocols.dialogue.base import InvalidDialogueMessage
from aea.protocols.state_update.message import StateUpdateMessage


class Dialogue(BaseDialogue):
    """This concrete class defines a dialogue."""

    INITIAL_PERFORMATIVES = frozenset({DefaultMessage.Performative.BYTES})
    TERMINAL_PERFORMATIVES = frozenset({DefaultMessage.Performative.ERROR})
    VALID_REPLIES = {
        DefaultMessage.Performative.BYTES: frozenset(
            {DefaultMessage.Performative.BYTES, DefaultMessage.Performative.ERROR}
        ),
        DefaultMessage.Performative.ERROR: frozenset(),
    }

    class Role(BaseDialogue.Role):
        """This class defines the agent's role in this dialogue."""

        ROLE1 = "role1"
        ROLE2 = "role2"

    class EndState(BaseDialogue.EndState):
        """This class defines the end states of this dialogue."""

        SUCCESSFUL = 0
        FAILED = 1

    def __init__(
        self,
        dialogue_label: DialogueLabel,
        message_class: Type[Message] = DefaultMessage,
        self_address: Address = "agent 1",
        role: BaseDialogue.Role = Role.ROLE1,
    ) -> None:
        """
        Initialize a dialogue.

        :param dialogue_label: the identifier of the dialogue
        :param self_address: the address of the entity for whom this dialogue is maintained
        :param role: the role of the agent this dialogue is maintained for
        :return: None
        """
        BaseDialogue.__init__(
            self,
            dialogue_label=dialogue_label,
            self_address=self_address,
            role=role,
            message_class=message_class,
        )


class Dialogues(BaseDialogues):
    """This class gives a concrete definition of dialogues."""

    END_STATES = frozenset(
        {Dialogue.EndState.SUCCESSFUL, Dialogue.EndState.FAILED}
    )  # type: FrozenSet[BaseDialogue.EndState]

    def __init__(
        self,
        self_address: Address,
        message_class=DefaultMessage,
        dialogue_class=Dialogue,
    ) -> None:
        """
        Initialize dialogues.

        :param self_address: the address of the entity for whom dialogues are maintained
        :return: None
        """

        def role_from_first_message(  # pylint: disable=unused-argument
            message: Message, receiver_address: Address
        ) -> BaseDialogue.Role:
            """Infer the role of the agent from an incoming/outgoing first message

            :param message: an incoming/outgoing first message
            :param receiver_address: the address of the receiving agent
            :return: The role of the agent
            """
            return Dialogue.Role.ROLE1

        BaseDialogues.__init__(
            self,
            self_address=self_address,
            end_states=cast(FrozenSet[BaseDialogue.EndState], self.END_STATES),
            message_class=message_class,
            dialogue_class=dialogue_class,
            role_from_first_message=role_from_first_message,
        )


@pytest.mark.skipif(
    sys.version_info < (3, 7),
    reason="This part of code is only defined for python version >= 3.7",
)
def test_dialogue_message_python_3_7():
    """Test DiallogueMessage if python is 3.7"""
    dialogue_message = DialogueMessage(DefaultMessage.Performative.BYTES)
    assert isinstance(dialogue_message.performative, Message.Performative)

    assert dialogue_message.performative == DefaultMessage.Performative.BYTES
    assert dialogue_message.contents == {}
    assert dialogue_message.is_incoming is None
    assert dialogue_message.target is None


@pytest.mark.skipif(
    sys.version_info >= (3, 7),
    reason="This part of code is only defined for python version < 3.7",
)
def test_dialogue_message_python_3_6():
    """Test DiallogueMessage if python is 3.6"""
    with mock.patch.object(
        aea.protocols.dialogue.base.sys, "version_info", return_value=(3, 6)
    ):
        dialogue_message = DialogueMessage(DefaultMessage.Performative.BYTES)
        assert isinstance(dialogue_message.performative, Message.Performative)

        assert dialogue_message.performative == DefaultMessage.Performative.BYTES
        assert dialogue_message.contents == {}
        assert dialogue_message.is_incoming is None
        assert dialogue_message.target is None


class TestDialogueLabel:
    """Test for DialogueLabel."""

    @classmethod
    def setup(cls):
        """Initialise the environment to test DialogueLabel."""
        cls.agent_address = "agent 1"
        cls.opponent_address = "agent 2"
        cls.dialogue_label = DialogueLabel(
            dialogue_reference=(str(1), ""),
            dialogue_opponent_addr=cls.opponent_address,
            dialogue_starter_addr=cls.agent_address,
        )

    def test_all_methods(self):
        """Test the DialogueLabel."""
        assert self.dialogue_label.dialogue_reference == (str(1), "")
        assert self.dialogue_label.dialogue_starter_reference == str(1)
        assert self.dialogue_label.dialogue_responder_reference == ""
        assert self.dialogue_label.dialogue_opponent_addr == self.opponent_address
        assert self.dialogue_label.dialogue_starter_addr == self.agent_address
        assert str(self.dialogue_label) == "{}_{}_{}_{}".format(
            self.dialogue_label.dialogue_starter_reference,
            self.dialogue_label.dialogue_responder_reference,
            self.dialogue_label.dialogue_opponent_addr,
            self.dialogue_label.dialogue_starter_addr,
        )

        dialogue_label_eq = DialogueLabel(
            dialogue_reference=(str(1), ""),
            dialogue_opponent_addr=self.opponent_address,
            dialogue_starter_addr=self.agent_address,
        )

        assert dialogue_label_eq == self.dialogue_label

        dialogue_label_not_eq = "This is a test"

        assert not dialogue_label_not_eq == self.dialogue_label

        assert hash(dialogue_label_eq) == hash(self.dialogue_label)

        assert self.dialogue_label.json == dict(
            dialogue_starter_reference=str(1),
            dialogue_responder_reference="",
            dialogue_opponent_addr=self.opponent_address,
            dialogue_starter_addr=self.agent_address,
        )
        assert DialogueLabel.from_json(self.dialogue_label.json) == self.dialogue_label
        assert DialogueLabel.from_str(str(self.dialogue_label)) == self.dialogue_label


class TestDialogueBase:
    """Test for Dialogue."""

    @classmethod
    def setup(cls):
        """Initialise the environment to test Dialogue."""
        cls.incomplete_reference = (str(1), "")
        cls.complete_reference = (str(1), str(1))
        cls.opponent_address = "agent 2"
        cls.agent_address = "agent 1"

        cls.dialogue_label = DialogueLabel(
            dialogue_reference=cls.incomplete_reference,
            dialogue_opponent_addr=cls.opponent_address,
            dialogue_starter_addr=cls.agent_address,
        )
        cls.dialogue = Dialogue(dialogue_label=cls.dialogue_label)

        cls.dialogue_label_opponent_started = DialogueLabel(
            dialogue_reference=cls.complete_reference,
            dialogue_opponent_addr=cls.opponent_address,
            dialogue_starter_addr=cls.opponent_address,
        )
        cls.dialogue_opponent_started = Dialogue(
            dialogue_label=cls.dialogue_label_opponent_started
        )

        # convenient messages to reuse across tests
        cls.valid_message_1_by_self = DefaultMessage(
            dialogue_reference=(str(1), ""),
            performative=DefaultMessage.Performative.BYTES,
            content=b"Hello",
        )
        cls.valid_message_1_by_self.sender = cls.agent_address
        cls.valid_message_1_by_self.to = cls.opponent_address

        cls.valid_message_2_by_other = DefaultMessage(
            dialogue_reference=(str(1), str(1)),
            message_id=2,
            target=1,
            performative=DefaultMessage.Performative.BYTES,
            content=b"Hello back",
        )
        cls.valid_message_2_by_other.sender = cls.opponent_address
        cls.valid_message_2_by_other.to = cls.agent_address

        cls.valid_message_3_by_self = DefaultMessage(
            dialogue_reference=(str(1), str(1)),
            message_id=3,
            target=2,
            performative=DefaultMessage.Performative.BYTES,
            content=b"Hello back 2",
        )
        cls.valid_message_3_by_self.sender = cls.agent_address
        cls.valid_message_3_by_self.to = cls.opponent_address

    def test_inner_classes(self):
        """Test the inner classes: Role and EndStates."""
        assert str(Dialogue.Role.ROLE1) == "role1"
        assert str(Dialogue.Role.ROLE2) == "role2"
        assert str(Dialogue.EndState.SUCCESSFUL) == "0"
        assert str(Dialogue.EndState.FAILED) == "1"

    def test_dialogue_properties(self):
        """Test dialogue properties."""
        assert self.dialogue.dialogue_label == self.dialogue_label
        assert self.dialogue.incomplete_dialogue_label == self.dialogue_label
        assert self.dialogue.dialogue_labels == {self.dialogue_label}
        assert self.dialogue.self_address == self.agent_address

        assert self.dialogue.role == Dialogue.Role.ROLE1
        assert str(self.dialogue.role) == "role1"

        assert self.dialogue.rules.initial_performatives == frozenset(
            {DefaultMessage.Performative.BYTES}
        )
        assert self.dialogue.rules.terminal_performatives == frozenset(
            {DefaultMessage.Performative.ERROR}
        )
        assert self.dialogue.rules.valid_replies == {
            DefaultMessage.Performative.BYTES: frozenset(
                {DefaultMessage.Performative.BYTES, DefaultMessage.Performative.ERROR}
            ),
            DefaultMessage.Performative.ERROR: frozenset(),
        }
        assert self.dialogue.rules.get_valid_replies(
            DefaultMessage.Performative.BYTES
        ) == frozenset(
            {DefaultMessage.Performative.BYTES, DefaultMessage.Performative.ERROR}
        )
        assert self.dialogue.rules.get_valid_replies(
            DefaultMessage.Performative.ERROR
        ) == frozenset({})
        assert self.dialogue.message_class == DefaultMessage

        assert self.dialogue.is_self_initiated

        assert self.dialogue.last_incoming_message is None
        assert self.dialogue.last_outgoing_message is None
        assert self.dialogue.last_message is None

        assert self.dialogue.is_empty

    def test_counterparty_from_message(self):
        """Test the 'counterparty_from_message' method."""
        assert (
            self.dialogue._counterparty_from_message(self.valid_message_1_by_self)
            == self.opponent_address
        )
        assert (
            self.dialogue._counterparty_from_message(self.valid_message_2_by_other)
            == self.opponent_address
        )

    def test_is_message_by_self(self):
        """Test the 'is_message_by_self' method."""
        assert self.dialogue._is_message_by_self(self.valid_message_1_by_self)
        assert not self.dialogue._is_message_by_self(self.valid_message_2_by_other)

    def test_is_message_by_other(self):
        """Test the 'is_message_by_other' method."""
        assert not self.dialogue._is_message_by_other(self.valid_message_1_by_self)
        assert self.dialogue._is_message_by_other(self.valid_message_2_by_other)

    def test_try_get_message(self):
        """Test the 'try_get_message' method."""
        assert (
            self.dialogue._try_get_message(self.valid_message_1_by_self.message_id)
            is None
        )
        self.dialogue._update(self.valid_message_1_by_self)
        assert (
            self.dialogue._try_get_message(self.valid_message_1_by_self.message_id)
            == self.valid_message_1_by_self
        )

        assert (
            self.dialogue._try_get_message(self.valid_message_2_by_other.message_id)
            is None
        )
        self.dialogue._update(self.valid_message_2_by_other)
        assert (
            self.dialogue._try_get_message(self.valid_message_2_by_other.message_id)
            == self.valid_message_2_by_other
        )

    def test_get_message(self):
        """Test the 'get_message' method."""
        with pytest.raises(ValueError) as cm:
            self.dialogue._get_message(self.valid_message_1_by_self.message_id)
        assert str(cm.value) == "Message not present."

        self.dialogue._update(self.valid_message_1_by_self)
        assert (
            self.dialogue._get_message(self.valid_message_1_by_self.message_id)
            == self.valid_message_1_by_self
        )

        with pytest.raises(ValueError) as cm:
            self.dialogue._get_message(self.valid_message_2_by_other.message_id)
        assert str(cm.value) == "Message not present."

        self.dialogue._update(self.valid_message_2_by_other)
        assert (
            self.dialogue._get_message(self.valid_message_2_by_other.message_id)
            == self.valid_message_2_by_other
        )

    def test_has_message(self):
        """Test the 'has_message' method."""
        assert self.dialogue._has_message(self.valid_message_1_by_self) is False

        self.dialogue._update(self.valid_message_1_by_self)
        assert self.dialogue._has_message(self.valid_message_1_by_self) is True

        assert self.dialogue._has_message(self.valid_message_2_by_other) is False

    def test_has_message_id(self):
        """Test the 'has_message_id' method."""
        assert self.dialogue._has_message_id(1) is False

        self.dialogue._update(self.valid_message_1_by_self)
        assert self.dialogue._has_message_id(1) is True

        assert self.dialogue._has_message_id(2) is False

    def test_update_positive(self):
        """Positive test for the 'update' method."""
        self.dialogue._update(self.valid_message_1_by_self)
        assert self.dialogue.last_outgoing_message == self.valid_message_1_by_self

    def test_update_positive_multiple_messages_by_self(self):
        """Positive test for the 'update' method: multiple messages by self is sent to the dialogue."""
        self.dialogue._update(self.valid_message_1_by_self)

        valid_message_2_by_self = DefaultMessage(
            dialogue_reference=(str(1), ""),
            message_id=2,
            target=1,
            performative=DefaultMessage.Performative.BYTES,
            content=b"Hello back",
        )
        valid_message_2_by_self.sender = self.agent_address
        valid_message_2_by_self.to = self.opponent_address

        self.dialogue._update(valid_message_2_by_self)

        assert self.dialogue.last_message.message_id == 2

    def test_update_negative_is_valid_next_message_fails(self):
        """Negative test for the 'update' method: input message is invalid with respect to the dialogue."""
        invalid_message_1_by_self = DefaultMessage(
            dialogue_reference=(str(1), ""),
            message_id=2,
            target=1,
            performative=DefaultMessage.Performative.BYTES,
            content=b"Hello",
        )
        invalid_message_1_by_self.sender = self.agent_address
        invalid_message_1_by_self.to = self.opponent_address

        with pytest.raises(InvalidDialogueMessage) as cm:
            self.dialogue._update(invalid_message_1_by_self)
        assert (
            str(cm.value)
            == "Message 2 is invalid with respect to this dialogue. Error: "
            + "Invalid message_id. Expected 1. Found 2."
        )

        assert self.dialogue.last_outgoing_message is None

    def test_update_dialogue_negative_message_does_not_belong_to_dialogue(self):
        """Negative test for the 'update' method in dialogue with wrong message not belonging to dialogue."""
        invalid_message_1_by_self = DefaultMessage(
            dialogue_reference=(str(2), ""),
            performative=DefaultMessage.Performative.BYTES,
            content=b"Hello",
        )
        invalid_message_1_by_self.sender = self.agent_address
        invalid_message_1_by_self.to = self.opponent_address

        with pytest.raises(InvalidDialogueMessage) as cm:
            self.dialogue._update(invalid_message_1_by_self)
        assert str(cm.value) == (
            "The message 1 does not belong to this dialogue."
            "The dialogue reference of the message is {}, while the dialogue reference of the dialogue is {}".format(
                invalid_message_1_by_self.dialogue_reference,
                self.dialogue.dialogue_label.dialogue_reference,
            )
        )
        assert self.dialogue.is_empty

    def test_is_belonging_to_dialogue(self):
        """Test for the '_is_belonging_to_dialogue' method"""
        valid_message_2_by_self = DefaultMessage(
            dialogue_reference=(str(2), ""),
            performative=DefaultMessage.Performative.BYTES,
            content=b"Hello",
        )
        valid_message_2_by_self.sender = self.agent_address
        valid_message_2_by_self.to = self.opponent_address

        assert self.dialogue._is_belonging_to_dialogue(self.valid_message_1_by_self)
        assert not self.dialogue._is_belonging_to_dialogue(valid_message_2_by_self)

    def test_reply_positive(self):
        """Positive test for the 'reply' method."""
        self.dialogue._update(self.valid_message_1_by_self)

        self.dialogue.reply(
            target_message=self.valid_message_1_by_self,
            performative=DefaultMessage.Performative.BYTES,
            content=b"Hello Back",
        )

        assert self.dialogue.last_message.message_id == 2

    def test_reply_negative_empty_dialogue(self):
        """Negative test for the 'reply' method: target message is not in the dialogue."""
        with pytest.raises(ValueError) as cm:
            self.dialogue.reply(
                target_message=self.valid_message_1_by_self,
                performative=DefaultMessage.Performative.BYTES,
                content=b"Hello Back",
            )
        assert str(cm.value) == "Cannot reply in an empty dialogue!"
        assert self.dialogue.is_empty

    def test_reply_negative_target_message_target_mismatch(self):
        """Negative test for the 'reply' method: target message and target provided but do not match."""
        self.dialogue._update(self.valid_message_1_by_self)
        assert self.dialogue.last_message.message_id == 1

        with pytest.raises(AEAEnforceError) as cm:
            self.dialogue.reply(
                target_message=self.valid_message_1_by_self,
                target=2,
                performative=DefaultMessage.Performative.BYTES,
                content=b"Hello Back",
            )
        assert str(cm.value) == "The provided target and target_message do not match."
        assert self.dialogue.last_message.message_id == 1

    def test_reply_negative_invalid_target(self):
        """Negative test for the 'reply' method: target message is not in the dialogue."""
        self.dialogue._update(self.valid_message_1_by_self)
        assert self.dialogue.last_message.message_id == 1

        invalid_message_1_by_self = DefaultMessage(
            dialogue_reference=(str(1), ""),
            message_id=2,
            target=1,
            performative=DefaultMessage.Performative.BYTES,
            content=b"Hello There",
        )
        invalid_message_1_by_self.sender = self.agent_address
        invalid_message_1_by_self.to = self.opponent_address

        with pytest.raises(AEAEnforceError) as cm:
            self.dialogue.reply(
                target_message=invalid_message_1_by_self,
                performative=DefaultMessage.Performative.BYTES,
                content=b"Hello Back",
            )
        assert str(cm.value) == "The target message does not exist in this dialogue."
        assert self.dialogue.last_message.message_id == 1

    def test_is_valid_next_message_positive(self):
        """Positive test for the 'validate_next_message' method"""
        self.dialogue._update(self.valid_message_1_by_self)
        self.dialogue._update(self.valid_message_2_by_other)

        result, msg = self.dialogue._validate_next_message(self.valid_message_3_by_self)
        assert result is True
        assert msg == "Message is valid with respect to this dialogue."

    def test_is_valid_next_message_negative_basic_validation_fails(self):
        """Negative test for the 'validate_next_message' method: basic_validation method fails"""
        invalid_message_1_by_self = DefaultMessage(
            dialogue_reference=(str(1), ""),
            message_id=2,
            target=0,
            performative=DefaultMessage.Performative.BYTES,
            content=b"Hello",
        )
        invalid_message_1_by_self.sender = self.agent_address
        invalid_message_1_by_self.to = self.opponent_address

        result, msg = self.dialogue._validate_next_message(invalid_message_1_by_self)
        assert result is False
        assert msg == "Invalid message_id. Expected 1. Found 2."

    def test_is_valid_next_message_negative_additional_validation_fails(self):
        """Negative test for the 'validate_next_message' method: additional_validation method fails"""
        self.dialogue._update(self.valid_message_1_by_self)
        self.dialogue._update(self.valid_message_2_by_other)

        invalid_message_3_by_self = DefaultMessage(
            dialogue_reference=(str(1), str(1)),
            message_id=3,
            target=1,
            performative=DefaultMessage.Performative.BYTES,
            content=b"Hello back 2",
        )
        invalid_message_3_by_self.sender = self.agent_address
        invalid_message_3_by_self.to = self.opponent_address

        result, msg = self.dialogue._validate_next_message(invalid_message_3_by_self)
        assert result is False
        assert msg == "Invalid target. Expected 2. Found 1."

    def test_is_valid_next_message_negative_is_valid_fails(self):
        """Negative test for the 'validate_next_message' method: is_valid method fails"""

        def failing_custom_validation(message: Message) -> Tuple[bool, str]:
            return False, "some reason"

        self.dialogue._custom_validation = failing_custom_validation

        result, msg = self.dialogue._validate_next_message(self.valid_message_1_by_self)
        assert result is False
        assert msg == "some reason"

    def test_basic_validation_positive(self):
        """Positive test for the '_basic_validation' method."""
        result, msg = self.dialogue._basic_validation(self.valid_message_1_by_self)
        assert result is True
        assert msg == "The initial message passes basic validation."

        self.dialogue._update(self.valid_message_1_by_self)

        result, msg = self.dialogue._basic_validation(self.valid_message_2_by_other)
        assert result is True
        assert msg == "The non-initial message passes basic validation."

    def test_basic_validation_negative_initial_message_invalid(self):
        """Negative test for the '_basic_validation' method: initial message is invalid."""
        invalid_message_1_by_self = DefaultMessage(
            dialogue_reference=(str(1), ""),
            message_id=2,
            target=1,
            performative=DefaultMessage.Performative.BYTES,
            content=b"Hello",
        )
        invalid_message_1_by_self.sender = self.agent_address
        invalid_message_1_by_self.to = self.opponent_address

        result, msg = self.dialogue._basic_validation(invalid_message_1_by_self)
        assert result is False
        assert msg == "Invalid message_id. Expected 1. Found 2."

    def test_basic_validation_negative_non_initial_message_invalid(self):
        """Negative test for the '_basic_validation' method: non-initial message is invalid."""
        self.dialogue._update(self.valid_message_1_by_self)

        invalid_message_2_by_other = DefaultMessage(
            dialogue_reference=(str(1), str(1)),
            message_id=2,
            target=0,
            performative=DefaultMessage.Performative.BYTES,
            content=b"Hello back",
        )
        invalid_message_2_by_other.sender = self.opponent_address
        invalid_message_2_by_other.to = self.agent_address

        result, msg = self.dialogue._basic_validation(invalid_message_2_by_other)
        assert result is False
        assert (
            msg
            == "Invalid target. Expected a value greater than or equal to 1. Found 0."
        )

    def test_basic_validation_initial_message_positive(self):
        """Positive test for the '_basic_validation_initial_message' method."""
        result, msg = self.dialogue._basic_validation_initial_message(
            self.valid_message_1_by_self
        )
        assert result is True
        assert msg == "The initial message passes basic validation."

    def test_basic_validation_initial_message_negative_invalid_dialogue_reference(self):
        """Negative test for the '_basic_validation' method: input message has invalid dialogue reference."""
        invalid_message_1_by_self = DefaultMessage(
            dialogue_reference=(str(2), ""),
            performative=DefaultMessage.Performative.BYTES,
            content=b"Hello",
        )
        invalid_message_1_by_self.sender = self.agent_address
        invalid_message_1_by_self.to = self.opponent_address

        result, msg = self.dialogue._basic_validation_initial_message(
            invalid_message_1_by_self
        )
        assert result is False
        assert msg == "Invalid dialogue_reference[0]. Expected 1. Found 2."

    def test_basic_validation_initial_message_negative_invalid_message_id(self):
        """Negative test for the '_basic_validation' method: input message has invalid message id."""
        invalid_message_1_by_self = DefaultMessage(
            dialogue_reference=(str(1), ""),
            message_id=2,
            target=1,
            performative=DefaultMessage.Performative.BYTES,
            content=b"Hello",
        )
        invalid_message_1_by_self.sender = self.agent_address
        invalid_message_1_by_self.to = self.opponent_address

        result, msg = self.dialogue._basic_validation_initial_message(
            invalid_message_1_by_self
        )
        assert result is False
        assert msg == "Invalid message_id. Expected 1. Found 2."

    def test_basic_validation_initial_message_negative_invalid_target(self):
        """Negative test for the '_basic_validation_initial_message' method: input message has invalid target."""
        invalid_message_1_by_self = DefaultMessage(
            dialogue_reference=(str(1), ""),
            message_id=1,
            target=1,
            performative=DefaultMessage.Performative.BYTES,
            content=b"Hello",
        )
        invalid_message_1_by_self.sender = self.agent_address
        invalid_message_1_by_self.to = self.opponent_address

        result, msg = self.dialogue._basic_validation_initial_message(
            invalid_message_1_by_self
        )
        assert result is False
        assert msg == "Invalid target. Expected 0. Found 1."

    def test_basic_validation_initial_message_negative_invalid_performative(self):
        """Negative test for the '_basic_validation_initial_message' method: input message has invalid performative."""
        invalid_initial_msg = DefaultMessage(
            dialogue_reference=(str(1), ""),
            performative=DefaultMessage.Performative.ERROR,
            error_code=DefaultMessage.ErrorCode.INVALID_MESSAGE,
            error_msg="some_error_message",
            error_data={"some_data": b"some_bytes"},
        )
        invalid_initial_msg.sender = self.agent_address
        invalid_initial_msg.to = self.opponent_address

        result, msg = self.dialogue._basic_validation_initial_message(
            invalid_initial_msg
        )
        assert result is False
        assert (
            msg
            == "Invalid initial performative. Expected one of {}. Found error.".format(
                self.dialogue.rules.initial_performatives
            )
        )

    def test_basic_validation_non_initial_message_positive(self):
        """Positive test for the '_basic_validation_non_initial_message' method."""
        self.dialogue._update(self.valid_message_1_by_self)

        result, msg = self.dialogue._basic_validation_non_initial_message(
            self.valid_message_2_by_other
        )
        assert result is True
        assert msg == "The non-initial message passes basic validation."

    def test_basic_validation_non_initial_message_negative_invalid_dialogue_reference(
        self,
    ):
        """Negative test for the '_basic_validation_non_initial_message' method: input message has invalid dialogue reference."""
        self.dialogue._update(self.valid_message_1_by_self)

        invalid_message_2_by_other = DefaultMessage(
            dialogue_reference=(str(2), str(1)),
            message_id=2,
            target=1,
            performative=DefaultMessage.Performative.BYTES,
            content=b"Hello back",
        )
        invalid_message_2_by_other.sender = self.opponent_address
        invalid_message_2_by_other.to = self.agent_address

        result, msg = self.dialogue._basic_validation_non_initial_message(
            invalid_message_2_by_other
        )
        assert result is False
        assert msg == "Invalid dialogue_reference[0]. Expected 1. Found 2."

    def test_basic_validation_non_initial_message_negative_invalid_message_id(self):
        """Negative test for the '_basic_validation_non_initial_message' method: input message has invalid message id."""
        self.dialogue._update(self.valid_message_1_by_self)

        invalid_message_2_by_other = DefaultMessage(
            dialogue_reference=(str(1), str(1)),
            message_id=3,
            target=1,
            performative=DefaultMessage.Performative.BYTES,
            content=b"Hello back",
        )
        invalid_message_2_by_other.sender = self.opponent_address
        invalid_message_2_by_other.to = self.agent_address

        result, msg = self.dialogue._basic_validation_non_initial_message(
            invalid_message_2_by_other
        )
        assert result is False
        assert msg == "Invalid message_id. Expected 2. Found 3."

    def test_basic_validation_non_initial_message_negative_invalid_target_1(self):
        """Negative test for the '_basic_validation_non_initial_message' method: input message has target less than 1."""
        self.dialogue._update(self.valid_message_1_by_self)

        invalid_message_2_by_other = DefaultMessage(
            dialogue_reference=(str(1), str(1)),
            message_id=2,
            target=0,
            performative=DefaultMessage.Performative.BYTES,
            content=b"Hello back",
        )
        invalid_message_2_by_other.sender = self.opponent_address
        invalid_message_2_by_other.to = self.agent_address

        result, msg = self.dialogue._basic_validation_non_initial_message(
            invalid_message_2_by_other
        )
        assert result is False
        assert (
            msg
            == "Invalid target. Expected a value greater than or equal to 1. Found 0."
        )

    def test_basic_validation_non_initial_message_negative_invalid_target_2(self):
        """Negative test for the '_basic_validation_non_initial_message' method: input message has target greater than the id of the last existing message."""
        self.dialogue._update(self.valid_message_1_by_self)

        invalid_message_2_by_other = DefaultMessage(
            dialogue_reference=(str(1), str(1)),
            message_id=2,
            target=2,
            performative=DefaultMessage.Performative.BYTES,
            content=b"Hello back",
        )
        invalid_message_2_by_other.sender = self.opponent_address
        invalid_message_2_by_other.to = self.agent_address

        result, msg = self.dialogue._basic_validation_non_initial_message(
            invalid_message_2_by_other
        )
        assert result is False
        assert (
            msg == "Invalid target. Expected a value less than or equal to 1. Found 2."
        )

    def test_basic_validation_non_initial_message_negative_invalid_performative(self):
        """Negative test for the '_basic_validation_non_initial_message' method: input message has invalid performative."""
        self.dialogue._update(self.valid_message_1_by_self)

        invalid_message_2_by_other = StateUpdateMessage(
            dialogue_reference=(str(1), str(1)),
            message_id=2,
            target=1,
            performative=StateUpdateMessage.Performative.APPLY,
            amount_by_currency_id={},
            quantities_by_good_id={},
        )
        invalid_message_2_by_other.sender = self.opponent_address
        invalid_message_2_by_other.to = self.agent_address

        result, msg = self.dialogue._basic_validation_non_initial_message(
            invalid_message_2_by_other
        )
        assert result is False
        assert msg == "Invalid performative. Expected one of {}. Found {}.".format(
            self.dialogue.rules.get_valid_replies(
                self.valid_message_1_by_self.performative
            ),
            invalid_message_2_by_other.performative,
        )

    def test_additional_validation_positive(self):
        """Positive test for the '_additional_rules' method."""
        result, msg = self.dialogue._additional_validation(self.valid_message_1_by_self)
        assert result is True
        assert msg == "The message passes additional validation."

        self.dialogue._update(self.valid_message_1_by_self)
        self.dialogue._update(self.valid_message_2_by_other)

        result, msg = self.dialogue._additional_validation(self.valid_message_3_by_self)
        assert result is True
        assert msg == "The message passes additional validation."

    def test_additional_validation_negative_invalid_target(self):
        """Negative test for the '_additional_rules' method: input message has invalid target (its target is not the last message of the dialogue)."""
        self.dialogue._update(self.valid_message_1_by_self)
        self.dialogue._update(self.valid_message_2_by_other)

        invalid_message_3_by_self = DefaultMessage(
            dialogue_reference=(str(1), str(1)),
            message_id=3,
            target=1,
            performative=DefaultMessage.Performative.BYTES,
            content=b"Hello back 2",
        )
        invalid_message_3_by_self.sender = self.agent_address
        invalid_message_3_by_self.to = self.opponent_address

        result, msg = self.dialogue._additional_validation(invalid_message_3_by_self)
        assert result is False
        assert msg == "Invalid target. Expected 2. Found 1."

    def test_update_dialogue_label_positive(self):
        """Positive test for the 'update_dialogue_label' method."""
        self.dialogue._update(self.valid_message_1_by_self)

        new_label = DialogueLabel(
            (str(1), str(1)), self.valid_message_1_by_self.to, self.agent_address
        )
        self.dialogue._update_dialogue_label(new_label)

        assert self.dialogue.dialogue_label == new_label

    def test_update_dialogue_label_negative_invalid_existing_label(self):
        """Negative test for the 'update_dialogue_label' method: existing dialogue reference is invalid."""
        self.dialogue._update(self.valid_message_1_by_self)
        self.dialogue._update(self.valid_message_2_by_other)

        new_label = DialogueLabel(
            (str(1), str(1)), self.valid_message_1_by_self.to, self.agent_address
        )
        self.dialogue._update_dialogue_label(new_label)
        assert self.dialogue.dialogue_label == new_label

        new_label = DialogueLabel(
            (str(1), str(2)), self.valid_message_1_by_self.to, self.agent_address
        )
        with pytest.raises(AEAEnforceError) as cm:
            self.dialogue._update_dialogue_label(new_label)
        assert str(cm.value) == "Dialogue label cannot be updated."

        assert self.dialogue.dialogue_label != new_label

    def test_update_dialogue_label_negative_invalid_input_label(self):
        """Negative test for the 'update_dialogue_label' method: input dialogue label's dialogue reference is invalid."""
        self.dialogue._update(self.valid_message_1_by_self)

        new_label = DialogueLabel(
            (str(2), ""), self.valid_message_1_by_self.to, self.agent_address
        )
        with pytest.raises(AEAEnforceError) as cm:
            self.dialogue._update_dialogue_label(new_label)
        assert str(cm.value) == "Dialogue label cannot be updated."
        assert self.dialogue.dialogue_label != new_label

    def test_interleave(self):
        """Test the '_interleave' method."""
        list_1 = [1, 3, 5, 7]
        list_2 = [2, 4, 6, 8]
        assert Dialogue._interleave(list_1, list_2) == [1, 2, 3, 4, 5, 6, 7, 8]

        list_3 = [1, 3, 4]
        list_4 = [2]
        assert Dialogue._interleave(list_3, list_4) == [1, 2, 3, 4]

    def test___str__1(self):
        """Test the '__str__' method: dialogue is self initiated"""
        self.dialogue._update(self.valid_message_1_by_self)
        self.dialogue._update(self.valid_message_2_by_other)

        self.dialogue._update(self.valid_message_3_by_self)

        dialogue_str = (
            "Dialogue Label: 1__agent 2_agent 1\nbytes( )\nbytes( )\nbytes( )"
        )

        assert str(self.dialogue) == dialogue_str

    def test___str__2(self):
        """Test the '__str__' method: dialogue is other initiated"""
        valid_message_1_by_other = DefaultMessage(
            dialogue_reference=(str(1), ""),
            performative=DefaultMessage.Performative.BYTES,
            content=b"Hello",
        )
        valid_message_1_by_other.sender = self.opponent_address
        valid_message_1_by_other.to = self.agent_address

        self.dialogue_opponent_started._update(valid_message_1_by_other)

        valid_message_2_by_self = DefaultMessage(
            dialogue_reference=(str(1), str(1)),
            message_id=2,
            target=1,
            performative=DefaultMessage.Performative.BYTES,
            content=b"Hello back",
        )
        valid_message_2_by_self.sender = self.agent_address
        valid_message_2_by_self.to = self.opponent_address

        self.dialogue_opponent_started._update(valid_message_2_by_self)

        valid_message_3_by_other = DefaultMessage(
            dialogue_reference=(str(1), str(1)),
            message_id=3,
            target=2,
            performative=DefaultMessage.Performative.BYTES,
            content=b"Hello back 2",
        )
        valid_message_3_by_other.sender = self.opponent_address
        valid_message_3_by_other.to = self.agent_address

        self.dialogue_opponent_started._update(valid_message_3_by_other)

        dialogue_str = (
            "Dialogue Label: 1_1_agent 2_agent 2\nbytes( )\nbytes( )\nbytes( )"
        )

        assert str(self.dialogue_opponent_started) == dialogue_str


class TestDialogueStats:
    """Test for DialogueStats."""

    @classmethod
    def setup(cls):
        """Initialise the environment to test DialogueStats."""
        cls.agent_address = "agent 1"
        cls.opponent_address = "agent 2"
        cls.dialogue_label = DialogueLabel(
            dialogue_reference=(str(1), ""),
            dialogue_opponent_addr=cls.opponent_address,
            dialogue_starter_addr=cls.agent_address,
        )
        cls.dialogue = Dialogue(dialogue_label=cls.dialogue_label)
        end_states = frozenset(
            {Dialogue.EndState.SUCCESSFUL, Dialogue.EndState.FAILED}
        )  # type: FrozenSet[BaseDialogue.EndState]
        cls.dialogue_stats = DialogueStats(end_states)

    def test_properties(self):
        """Test dialogue properties."""
        assert isinstance(self.dialogue_stats.self_initiated, dict)
        assert self.dialogue_stats.self_initiated == {
            Dialogue.EndState.SUCCESSFUL: 0,
            Dialogue.EndState.FAILED: 0,
        }
        assert self.dialogue_stats.other_initiated == {
            Dialogue.EndState.SUCCESSFUL: 0,
            Dialogue.EndState.FAILED: 0,
        }

    def test_add_dialogue_endstate(self):
        """Test for the 'add_dialogue_endstate' method."""
        assert self.dialogue_stats.self_initiated == {
            Dialogue.EndState.SUCCESSFUL: 0,
            Dialogue.EndState.FAILED: 0,
        }
        assert self.dialogue_stats.other_initiated == {
            Dialogue.EndState.SUCCESSFUL: 0,
            Dialogue.EndState.FAILED: 0,
        }

        self.dialogue_stats.add_dialogue_endstate(Dialogue.EndState.SUCCESSFUL, True)
        assert self.dialogue_stats.self_initiated == {
            Dialogue.EndState.SUCCESSFUL: 1,
            Dialogue.EndState.FAILED: 0,
        }
        assert self.dialogue_stats.other_initiated == {
            Dialogue.EndState.SUCCESSFUL: 0,
            Dialogue.EndState.FAILED: 0,
        }

        self.dialogue_stats.add_dialogue_endstate(Dialogue.EndState.FAILED, False)
        assert self.dialogue_stats.self_initiated == {
            Dialogue.EndState.SUCCESSFUL: 1,
            Dialogue.EndState.FAILED: 0,
        }
        assert self.dialogue_stats.other_initiated == {
            Dialogue.EndState.SUCCESSFUL: 0,
            Dialogue.EndState.FAILED: 1,
        }


class TestDialoguesBase:
    """Test for Dialogues."""

    @classmethod
    def setup(cls):
        """Initialise the environment to test Dialogue."""
        cls.agent_address = "agent 1"
        cls.opponent_address = "agent 2"
        cls.dialogue_label = DialogueLabel(
            dialogue_reference=(str(1), ""),
            dialogue_opponent_addr=cls.opponent_address,
            dialogue_starter_addr=cls.agent_address,
        )
        cls.dialogue = Dialogue(dialogue_label=cls.dialogue_label)
        cls.own_dialogues = Dialogues(cls.agent_address)
        cls.opponent_dialogues = Dialogues(cls.opponent_address)

        # convenient messages to reuse across tests
        cls.valid_message_1_by_self = DefaultMessage(
            dialogue_reference=(str(1), ""),
            performative=DefaultMessage.Performative.BYTES,
            content=b"Hello",
        )
        cls.valid_message_1_by_self.sender = cls.agent_address
        cls.valid_message_1_by_self.to = cls.opponent_address

        cls.valid_message_2_by_other = DefaultMessage(
            dialogue_reference=(str(1), str(1)),
            message_id=2,
            target=1,
            performative=DefaultMessage.Performative.BYTES,
            content=b"Hello back",
        )
        cls.valid_message_2_by_other.sender = cls.opponent_address
        cls.valid_message_2_by_other.to = cls.agent_address

    def test_dialogues_properties(self):
        """Test dialogue properties."""
        assert self.own_dialogues.dialogues == dict()
        assert self.own_dialogues.self_address == self.agent_address
        assert self.own_dialogues.dialogue_stats.other_initiated == {
            Dialogue.EndState.SUCCESSFUL: 0,
            Dialogue.EndState.FAILED: 0,
        }
        assert self.own_dialogues.dialogue_stats.self_initiated == {
            Dialogue.EndState.SUCCESSFUL: 0,
            Dialogue.EndState.FAILED: 0,
        }
        assert self.own_dialogues.message_class == DefaultMessage
        assert self.own_dialogues.dialogue_class == Dialogue

    def test_counterparty_from_message(self):
        """Test the 'counterparty_from_message' method."""
        assert (
            self.own_dialogues._counterparty_from_message(self.valid_message_1_by_self)
            == self.opponent_address
        )
        assert (
            self.own_dialogues._counterparty_from_message(self.valid_message_2_by_other)
            == self.opponent_address
        )

    def test_is_message_by_self(self):
        """Test the 'is_message_by_self' method."""
        assert self.own_dialogues._is_message_by_self(self.valid_message_1_by_self)
        assert not self.own_dialogues._is_message_by_self(self.valid_message_2_by_other)

    def test_is_message_by_other(self):
        """Test the 'is_message_by_other' method."""
        assert not self.own_dialogues._is_message_by_other(self.valid_message_1_by_self)
        assert self.own_dialogues._is_message_by_other(self.valid_message_2_by_other)

    def test_new_self_initiated_dialogue_reference(self):
        """Test the 'new_self_initiated_dialogue_reference' method."""
        self_initiated_ref = self.own_dialogues.new_self_initiated_dialogue_reference()
        assert (
            isinstance(self_initiated_ref[0], str)
            and self_initiated_ref[0] != ""
            and len(self_initiated_ref[0]) == DialogueLabel.NONCE_BYTES_NB * 2
        )
        assert self_initiated_ref[1] == ""
        self_initiated_ref_2 = (
            self.own_dialogues.new_self_initiated_dialogue_reference()
        )
        assert self_initiated_ref_2 != self_initiated_ref

    def test_create_positive(self):
        """Positive test for the 'create' method."""
        assert len(self.own_dialogues.dialogues) == 0
        self.own_dialogues.create(
            self.opponent_address, DefaultMessage.Performative.BYTES, content=b"Hello"
        )
        assert len(self.own_dialogues.dialogues) == 1

    def test_create_negative_incorrect_performative_content_combination(self):
        """Negative test for the 'create' method: invalid performative and content combination (i.e. invalid message)."""
        assert len(self.own_dialogues.dialogues) == 0
        with pytest.raises(SyntaxError) as cm:
            self.own_dialogues.create(
                self.opponent_address,
                DefaultMessage.Performative.ERROR,
                content=b"Hello",
            )
        assert (
            str(cm.value)
            == "Cannot create a dialogue with the specified performative and contents."
        )
        assert len(self.own_dialogues.dialogues) == 0

    def test_update_positive_new_dialogue_by_other(self):
        """Positive test for the 'update' method: the input message is for a new dialogue dialogue by other."""
        valid_message_1_by_other = DefaultMessage(
            dialogue_reference=(str(1), ""),
            performative=DefaultMessage.Performative.BYTES,
            content=b"Hello",
        )
        valid_message_1_by_other.sender = self.opponent_address
        valid_message_1_by_other.to = self.agent_address

        assert len(self.own_dialogues.dialogues) == 0

        dialogue = self.own_dialogues.update(valid_message_1_by_other)

        assert len(self.own_dialogues.dialogues) == 1
        assert dialogue is not None
        assert dialogue.last_message.dialogue_reference == (str(1), "")
        assert dialogue.last_message.message_id == 1
        assert dialogue.last_message.target == 0
        assert dialogue.last_message.performative == DefaultMessage.Performative.BYTES
        assert dialogue.last_message.content == b"Hello"

    def test_update_positive_existing_dialogue(self):
        """Positive test for the 'update' method: the input message is for an existing dialogue."""
        msg, dialogue = self.own_dialogues.create(
            self.opponent_address, DefaultMessage.Performative.BYTES, content=b"Hello"
        )

        dialogue_reference = (
            msg.dialogue_reference[0],
            self.opponent_dialogues._generate_dialogue_nonce(),
        )
        valid_message_2_by_other = DefaultMessage(
            dialogue_reference=dialogue_reference,
            message_id=msg.message_id + 1,
            target=msg.message_id,
            performative=DefaultMessage.Performative.BYTES,
            content=b"Hello back",
        )
        valid_message_2_by_other.sender = self.opponent_address
        valid_message_2_by_other.to = self.agent_address

        assert len(self.own_dialogues.dialogues) == 1

        dialogue = self.own_dialogues.update(valid_message_2_by_other)

        assert len(self.own_dialogues.dialogues) == 1
        assert dialogue is not None
        assert dialogue.last_message.dialogue_reference == dialogue_reference
        assert dialogue.last_message.message_id == valid_message_2_by_other.message_id
        assert dialogue.last_message.target == valid_message_2_by_other.target
        assert dialogue.last_message.performative == DefaultMessage.Performative.BYTES
        assert dialogue.last_message.content == b"Hello back"

    def test_update_positive_existing_dialogue_2(self):
        """Positive test for the 'update' method: the input message is for an existing dialogue from the original sender."""
        msg_1, dialogue = self.own_dialogues.create(
            self.opponent_address, DefaultMessage.Performative.BYTES, content=b"Hello"
        )

        opponent_dialogue_1 = self.opponent_dialogues.update(msg_1)

        msg_2 = dialogue.reply(
            performative=DefaultMessage.Performative.BYTES, content=b"Hello again",
        )

        opponent_dialogue_2 = self.opponent_dialogues.update(msg_2)

        assert opponent_dialogue_1 == opponent_dialogue_2

    def test_update_negative_invalid_label(self):
        """Negative test for the 'update' method: dialogue is not extendable with the input message."""
        invalid_message_1_by_other = DefaultMessage(
            dialogue_reference=("", ""),
            message_id=0,
            target=0,
            performative=DefaultMessage.Performative.BYTES,
            content=b"Hello",
        )
        invalid_message_1_by_other.sender = self.opponent_address
        invalid_message_1_by_other.to = self.agent_address

        assert not self.own_dialogues.update(invalid_message_1_by_other)

    def test_update_negative_new_dialogue_by_self(self):
        """Negative test for the 'update' method: the message is not by the counterparty."""
        assert len(self.own_dialogues.dialogues) == 0

        with pytest.raises(AEAEnforceError) as cm:
            self.own_dialogues.update(self.valid_message_1_by_self)
        assert (
            str(cm.value)
            == "Invalid 'update' usage. Update must only be used with a message by another agent."
        )

        assert len(self.own_dialogues.dialogues) == 0

    def test_update_negative_no_to(self):
        """Negative test for the 'update' method: the 'to' field of the input message is not set."""
        invalid_message_1_by_other = DefaultMessage(
            dialogue_reference=(str(1), ""),
            performative=DefaultMessage.Performative.BYTES,
            content=b"Hello",
        )
        invalid_message_1_by_other.sender = self.opponent_address

        assert len(self.own_dialogues.dialogues) == 0

        with pytest.raises(AEAEnforceError) as cm:
            self.own_dialogues.update(invalid_message_1_by_other)
        assert str(cm.value) == "The message's 'to' field is not set {}".format(
            invalid_message_1_by_other
        )

        assert len(self.own_dialogues.dialogues) == 0

    def test_update_negative_no_sender(self):
        """Negative test for the 'update' method: the 'sender' field of the input message is not set."""
        invalid_message_1_by_other = DefaultMessage(
            dialogue_reference=(str(1), ""),
            performative=DefaultMessage.Performative.BYTES,
            content=b"Hello",
        )
        invalid_message_1_by_other.to = self.agent_address

        assert len(self.own_dialogues.dialogues) == 0

        with pytest.raises(AEAEnforceError) as cm:
            self.own_dialogues.update(invalid_message_1_by_other)
        assert (
            str(cm.value)
            == "Invalid 'update' usage. Update must only be used with a message by another agent."
        )

        assert len(self.own_dialogues.dialogues) == 0

    def test_update_negative_no_matching_to(self):
        """Negative test for the 'update' method: the 'to' field of the input message does not match self address."""
        invalid_message_1_by_other = DefaultMessage(
            dialogue_reference=(str(1), ""),
            performative=DefaultMessage.Performative.BYTES,
            content=b"Hello",
        )
        invalid_message_1_by_other.to = self.agent_address + "wrong_stuff"
        invalid_message_1_by_other.sender = self.opponent_address

        assert len(self.own_dialogues.dialogues) == 0

        with pytest.raises(AEAEnforceError) as cm:
            self.own_dialogues.update(invalid_message_1_by_other)
        assert (
            str(cm.value)
            == "Message to and dialogue self address do not match. Got 'to=agent 1wrong_stuff' expected 'to=agent 1'."
        )

        assert len(self.own_dialogues.dialogues) == 0

    def test_update_negative_invalid_message(self):
        """Negative test for the 'update' method: the message is invalid."""
        invalid_message_1_by_other = DefaultMessage(
            dialogue_reference=(str(1), ""),
            performative=DefaultMessage.Performative.ERROR,
            error_code=DefaultMessage.ErrorCode.INVALID_MESSAGE,
            error_msg="some_error_message",
            error_data={"some_data": b"some_bytes"},
        )
        invalid_message_1_by_other.sender = self.opponent_address
        invalid_message_1_by_other.to = self.agent_address

        assert len(self.own_dialogues.dialogues) == 0

        dialogue = self.own_dialogues.update(invalid_message_1_by_other)

        assert dialogue is None

        assert len(self.own_dialogues.dialogues) == 0

    def test_update_negative_existing_dialogue_non_nonexistent(self):
        """Negative test for the 'update' method: the dialogue referred by the input message does not exist."""
        _, dialogue = self.own_dialogues.create(
            self.opponent_address, DefaultMessage.Performative.BYTES, content=b"Hello"
        )

        invalid_message_2_by_other = DefaultMessage(
            dialogue_reference=(str(2), str(1)),
            message_id=2,
            target=1,
            performative=DefaultMessage.Performative.BYTES,
            content=b"Hello back",
        )
        invalid_message_2_by_other.sender = self.opponent_address
        invalid_message_2_by_other.to = self.agent_address

        updated_dialogue = self.own_dialogues.update(invalid_message_2_by_other)

        assert updated_dialogue is None
        last_message = self.own_dialogues.dialogues[
            dialogue.dialogue_label
        ].last_message
        assert (
            last_message.dialogue_reference[0] != ""
            and last_message.dialogue_reference[1] == ""
        )
        assert (
            self.own_dialogues.dialogues[
                dialogue.dialogue_label
            ].last_message.message_id
            == 1
        )
        assert (
            self.own_dialogues.dialogues[dialogue.dialogue_label].last_message.target
            == 0
        )
        assert (
            self.own_dialogues.dialogues[
                dialogue.dialogue_label
            ].last_message.performative
            == DefaultMessage.Performative.BYTES
        )
        assert (
            self.own_dialogues.dialogues[dialogue.dialogue_label].last_message.content
            == b"Hello"
        )

    def test_complete_dialogue_reference_positive(self,):
        """Positive test for the '_complete_dialogue_reference' method."""
        msg, dialogue = self.own_dialogues.create(
            self.opponent_address, DefaultMessage.Performative.BYTES, content=b"Hello"
        )

        valid_message_2_by_other = DefaultMessage(
            dialogue_reference=(
                msg.dialogue_reference[0],
                self.opponent_dialogues._generate_dialogue_nonce(),
            ),
            message_id=2,
            target=1,
            performative=DefaultMessage.Performative.BYTES,
            content=b"Hello back",
        )
        valid_message_2_by_other.sender = self.opponent_address
        valid_message_2_by_other.to = self.agent_address

        self.own_dialogues._complete_dialogue_reference(valid_message_2_by_other)

        assert (
            self.own_dialogues.dialogues[
                dialogue.dialogue_label
            ].dialogue_label.dialogue_reference
            == valid_message_2_by_other.dialogue_reference
        )

    def test_complete_dialogue_reference_negative_incorrect_reference(self,):
        """Negative test for the '_complete_dialogue_reference' method: the input message has invalid dialogue reference."""
        msg, dialogue = self.own_dialogues.create(
            self.opponent_address, DefaultMessage.Performative.BYTES, content=b"Hello"
        )

        wrong_own_ref = (
            "wrong reference"  # if correct, would be  msg.dialogue_reference[0]
        )
        valid_message_2_by_other = DefaultMessage(
            dialogue_reference=(
                wrong_own_ref,
                self.opponent_dialogues._generate_dialogue_nonce(),
            ),
            message_id=msg.message_id + 1,
            target=msg.message_id,
            performative=DefaultMessage.Performative.BYTES,
            content=b"Hello back",
        )
        valid_message_2_by_other.sender = self.opponent_address
        valid_message_2_by_other.to = self.agent_address

        self.own_dialogues._complete_dialogue_reference(valid_message_2_by_other)
        assert (
            self.own_dialogues.dialogues[
                dialogue.dialogue_label
            ].dialogue_label.dialogue_reference
            == msg.dialogue_reference
        )

    def test_get_dialogue_positive_1(self):
        """Positive test for the 'get_dialogue' method: the dialogue is self initiated and the second message is by the other agent."""
        msg, dialogue = self.own_dialogues.create(
            self.opponent_address, DefaultMessage.Performative.BYTES, content=b"Hello"
        )

        valid_message_2_by_other = DefaultMessage(
            dialogue_reference=(
                msg.dialogue_reference[0],
                self.opponent_dialogues._generate_dialogue_nonce(),
            ),
            message_id=msg.message_id + 1,
            target=msg.message_id,
            performative=DefaultMessage.Performative.BYTES,
            content=b"Hello back",
        )
        valid_message_2_by_other.sender = self.opponent_address
        valid_message_2_by_other.to = self.agent_address

        self.own_dialogues._complete_dialogue_reference(valid_message_2_by_other)

        assert (
            self.own_dialogues.dialogues[
                dialogue.dialogue_label
            ].dialogue_label.dialogue_reference
            == valid_message_2_by_other.dialogue_reference
        )

        retrieved_dialogue = self.own_dialogues.get_dialogue(valid_message_2_by_other)

        assert retrieved_dialogue.dialogue_label == dialogue.dialogue_label

    def test_get_dialogue_positive_2(self):
        """Positive test for the 'get_dialogue' method: the dialogue is other initiated and the second message is by this agent."""
        valid_message_1_by_other = DefaultMessage(
            dialogue_reference=(str(1), ""),
            performative=DefaultMessage.Performative.BYTES,
            content=b"Hello",
        )
        valid_message_1_by_other.sender = self.opponent_address
        valid_message_1_by_other.to = self.agent_address

        dialogue = self.own_dialogues.update(valid_message_1_by_other)

        valid_message_2_by_other = DefaultMessage(
            dialogue_reference=dialogue.dialogue_label.dialogue_reference,
            message_id=valid_message_1_by_other.message_id + 1,
            target=valid_message_1_by_other.message_id,
            performative=DefaultMessage.Performative.BYTES,
            content=b"Hello back",
        )
        valid_message_2_by_other.sender = self.agent_address
        valid_message_2_by_other.to = self.opponent_address

        retrieved_dialogue = self.own_dialogues.get_dialogue(valid_message_2_by_other)

        assert retrieved_dialogue is not None
        assert retrieved_dialogue.dialogue_label == dialogue.dialogue_label

    def test_get_dialogue_negative_invalid_reference(self):
        """Negative test for the 'get_dialogue' method: the input message has invalid dialogue reference."""
        msg, dialogue = self.own_dialogues.create(
            self.opponent_address, DefaultMessage.Performative.BYTES, content=b"Hello"
        )

        valid_message_2_by_other = DefaultMessage(
            dialogue_reference=(
                msg.dialogue_reference[0],
                self.opponent_dialogues._generate_dialogue_nonce(),
            ),
            message_id=msg.message_id + 1,
            target=msg.message_id,
            performative=DefaultMessage.Performative.BYTES,
            content=b"Hello back",
        )
        valid_message_2_by_other.sender = self.opponent_address
        valid_message_2_by_other.to = self.agent_address

        dialogue = self.own_dialogues.update(valid_message_2_by_other)
        assert dialogue is not None

        invalid_message_3_by_self = DefaultMessage(
            dialogue_reference=(str(2), str(1)),
            message_id=2,
            target=1,
            performative=DefaultMessage.Performative.BYTES,
            content=b"Hello back",
        )
        invalid_message_3_by_self.sender = self.agent_address
        invalid_message_3_by_self.to = self.opponent_address

        retrieved_dialogue = self.own_dialogues.get_dialogue(invalid_message_3_by_self)

        assert retrieved_dialogue is None

    def test_get_latest_label(self):
        """Positive test for the 'get_latest_label' method."""
        pass

    def test_get_dialogue_from_label_positive(self):
        """Positive test for the 'get_dialogue_from_label' method."""
        _, dialogue = self.own_dialogues.create(
            self.opponent_address, DefaultMessage.Performative.BYTES, content=b"Hello"
        )

        retrieved_dialogue = self.own_dialogues._get_dialogue_from_label(
            dialogue.dialogue_label
        )
        assert retrieved_dialogue.dialogue_label == dialogue.dialogue_label

    def test_get_dialogue_from_label_negative_incorrect_input_label(self):
        """Negative test for the 'get_dialogue_from_label' method: the input dialogue label does not exist."""
        _, dialogue = self.own_dialogues.create(
            self.opponent_address, DefaultMessage.Performative.BYTES, content=b"Hello"
        )

        incorrect_label = DialogueLabel(
            (str(1), "error"), self.opponent_address, self.agent_address
        )

        retrieved_dialogue = self.own_dialogues._get_dialogue_from_label(
            incorrect_label
        )
        assert retrieved_dialogue is None

    def test_create_self_initiated_positive(self):
        """Positive test for the '_create_self_initiated' method."""
        assert len(self.own_dialogues.dialogues) == 0

        self.own_dialogues._create_self_initiated(
            self.opponent_address, (str(1), ""), Dialogue.Role.ROLE1
        )
        assert len(self.own_dialogues.dialogues) == 1

    def test_create_self_initiated_negative_invalid_dialogue_reference(self):
        """Negative test for the '_create_self_initiated' method: invalid dialogue reference"""
        pass

    def test_create_opponent_initiated_positive(self):
        """Positive test for the '_create_opponent_initiated' method."""
        assert len(self.own_dialogues.dialogues) == 0

        self.own_dialogues._create_opponent_initiated(
            self.opponent_address, (str(1), ""), Dialogue.Role.ROLE2
        )
        assert len(self.own_dialogues.dialogues) == 1

    def test_create_opponent_initiated_negative_invalid_input_dialogue_reference(self):
        """Negative test for the '_create_opponent_initiated' method: input dialogue label has invalid dialogue reference."""
        assert len(self.own_dialogues.dialogues) == 0

        try:
            self.own_dialogues._create_opponent_initiated(
                self.opponent_address, ("", str(1)), Dialogue.Role.ROLE2
            )
            result = True
        except AEAEnforceError:
            result = False

        assert not result
        assert len(self.own_dialogues.dialogues) == 0

    def test_create_with_message(self):
        """Positive test for create with message."""
        msg = DefaultMessage(
            dialogue_reference=self.own_dialogues.new_self_initiated_dialogue_reference(),
            performative=DefaultMessage.Performative.BYTES,
            content=b"Hello",
        )
        self.own_dialogues.create_with_message("opponent", msg)

    def test__create_positive(self):
        """Positive test for the '_create' method."""
        pass

    def test__create_negative_incomplete_dialogue_label_present(self):
        """Negative test for the '_create' method: incomplete dialogue label already present."""
        pass

    def test__create_negative_dialogue_label_present(self):
        """Negative test for the '_create' method: dialogue label already present."""
        pass

    def test_generate_dialogue_nonce(self):
        """Test the '_generate_dialogue_nonce' method."""
        nonce = self.own_dialogues._generate_dialogue_nonce()
        assert (
            isinstance(nonce, str)
            and nonce != ""
            and len(nonce) == DialogueLabel.NONCE_BYTES_NB * 2
        )
        second_nonce = self.own_dialogues._generate_dialogue_nonce()
        assert nonce != second_nonce

    def test_get_dialogues_with_counterparty(self):
        """Test get dialogues with counterparty."""
        assert (
            self.own_dialogues.get_dialogues_with_counterparty(self.opponent_address)
            == []
        )
        _, dialogue = self.own_dialogues.create(
            self.opponent_address, DefaultMessage.Performative.BYTES, content=b"Hello"
        )
        assert self.own_dialogues.get_dialogues_with_counterparty(
            self.opponent_address
        ) == [dialogue]
