"""This module holds the test cases for the models of the ``models_hooks`` discrete app"""

from mock import call, patch

from .broker_models_hooks_testapp.models import (
    ModelWithBrokerHookM2MRelatedMixin,
    ModelWithM2M,
)
from .test_case import BaseModelsHooksTestCase


class BrokerHookMixinTestCase(BaseModelsHooksTestCase):
    """Test case for the ``BrokerHookMixin``"""

    def test_get_broker_topic_should_return_the_broker_topic(self):
        """Test that the ``get_broker_topic``should return the broker topic"""

        self.assertEqual(
            self.model_with_broker_hook.get_broker_topic(),
            'test_topic'
        )

    def test_get_serialized_representation_should_work(self):
        """Test that the ``get_serialized_representation``should work"""

        self.assertEqual(
            self.model_with_broker_hook.get_serialized_representation(),
            {
                'id': self.model_with_broker_hook.id
            }
        )

    def test_saving_the_model_should_call_publish_to_broker_task(self):
        """Test that saving the model should call the ``publish_to_broker`` task"""

        with patch('mtp.apps.broker_models_hooks.tasks.publish_to_broker.delay') as mocked:
            self.model_with_broker_hook.save()

        mocked.assert_called_once_with(
            self.model_with_broker_hook.get_serialized_representation(),
            self.model_with_broker_hook.get_broker_topic(),
            'UPDATE'
        )

    def test_saving_base_should_not_call_the_publish_to_broker_task(self):
        """Test that save_base the model with raw should not call the ``publish_to_broker`` task"""

        with patch('mtp.apps.broker_models_hooks.tasks.publish_to_broker.delay') as mocked:
            self.model_with_broker_hook.save_base(raw=True)

        mocked.assert_not_called()

    def test_should_be_sent_to_broker_method(self):
        """Test the ``should_be_sent_to_broker`` method"""

        with patch('mtp.apps.broker_models_hooks.tasks.publish_to_broker.delay') as mocked_task:
            with patch.object(self.model_with_broker_hook,
                              'should_be_sent_to_broker',
                              return_value=False):
                self.model_with_broker_hook.save()

        # The hook is not called since the ``should_be_sent_to_broker`` returned False
        mocked_task.assert_not_called()

    def test_saving_syncable_model_should_call_publish_to_broker_task(self):
        """Test that saving the syncable model should call the ``publish_to_broker`` task"""

        # Make sure the syncable model is not deleted
        self.syncable_model.unremove()

        with patch('mtp.apps.broker_models_hooks.tasks.publish_to_broker.delay') as mocked:
            self.syncable_model.save()

        mocked.assert_called_once_with(
            self.syncable_model.get_serialized_representation(),
            self.syncable_model.get_broker_topic(),
            'UPDATE'
        )

    def test_remove_syncable_model_should_call_publish_to_broker_task(self):
        """Test that removing the syncable model should call the ``publish_to_broker`` task"""

        with patch('mtp.apps.broker_models_hooks.tasks.publish_to_broker.delay') as mocked:
            self.syncable_model.remove()

        mocked.assert_called_once_with(
            self.syncable_model.get_serialized_representation(),
            self.syncable_model.get_broker_topic(),
            'REMOVE'
        )


class BrokerHookM2MRelatedMixinTestCase(BaseModelsHooksTestCase):
    """Test case for the ``BrokerHookM2MRelatedMixin``"""

    def test_add_a_new_related_instance_should_call_publish_to_broker(self):
        """Test that adding a new related instance should call the ``publish_to_broker`` task"""

        new_related_model = ModelWithBrokerHookM2MRelatedMixin.objects.create()

        with patch('mtp.apps.broker_models_hooks.tasks.publish_to_broker.delay') as mocked:
            self.model_with_m2m.things.add(new_related_model)

        mocked.assert_called_once_with(
            new_related_model.get_serialized_representation(),
            new_related_model.get_modelwithm2m_broker_topic(self.model_with_m2m),
            'ADD'
        )

    def test_add_multiple_related_instances_should_call_publish_to_broker(self):
        """Test that adding multiple related instances should call the ``publish_to_broker`` task"""

        new_related_model_1 = ModelWithBrokerHookM2MRelatedMixin.objects.create()
        new_related_model_2 = ModelWithBrokerHookM2MRelatedMixin.objects.create()

        with patch('mtp.apps.broker_models_hooks.tasks.publish_to_broker.delay') as mocked:
            self.model_with_m2m.things.add(new_related_model_1, new_related_model_2)

        calls = [
            call(
                new_related_model_1.get_serialized_representation(),
                new_related_model_1.get_modelwithm2m_broker_topic(self.model_with_m2m),
                'ADD'
            ),
            call(
                new_related_model_2.get_serialized_representation(),
                new_related_model_2.get_modelwithm2m_broker_topic(self.model_with_m2m),
                'ADD'
            )
        ]

        mocked.assert_has_calls(calls, any_order=True)

    def test_remove_a_related_object_should_call_publish_to_broker(self):
        """Test that removing a related instance should call the ``publish_to_broker`` task"""

        # Add the related instance
        new_related_model = ModelWithBrokerHookM2MRelatedMixin.objects.create()
        with patch('mtp.apps.broker_models_hooks.tasks.publish_to_broker.delay'):
            self.model_with_m2m.things.add(new_related_model)

        # Remove the related instance
        with patch('mtp.apps.broker_models_hooks.tasks.publish_to_broker.delay') as mocked:
            self.model_with_m2m.things.remove(new_related_model)

        mocked.assert_called_once_with(
            new_related_model.get_serialized_representation(),
            new_related_model.get_modelwithm2m_broker_topic(self.model_with_m2m),
            'REMOVE'
        )

    def test_remove_multiple_related_instances_should_call_publish_to_broker(self):
        """Test that removing multiple instances should call the ``publish_to_broker`` task"""

        # Add the related instances
        new_related_model_1 = ModelWithBrokerHookM2MRelatedMixin.objects.create()
        new_related_model_2 = ModelWithBrokerHookM2MRelatedMixin.objects.create()
        with patch('mtp.apps.broker_models_hooks.tasks.publish_to_broker.delay'):
            self.model_with_m2m.things.add(new_related_model_1, new_related_model_2)

        # Remove the related instances
        with patch('mtp.apps.broker_models_hooks.tasks.publish_to_broker.delay') as mocked:
            self.model_with_m2m.things.remove(new_related_model_1, new_related_model_2)

        calls = [
            call(
                new_related_model_1.get_serialized_representation(),
                new_related_model_1.get_modelwithm2m_broker_topic(self.model_with_m2m),
                'REMOVE'
            ),
            call(
                new_related_model_2.get_serialized_representation(),
                new_related_model_2.get_modelwithm2m_broker_topic(self.model_with_m2m),
                'REMOVE'
            )
        ]

        mocked.assert_has_calls(calls, any_order=True)

    def test_add_one_related_instance_from_inverse_should_call_publish_to_broker(self):
        """Test when adding a related instance from the inverse relation"""

        # Add the related instance
        new_related_model = ModelWithBrokerHookM2MRelatedMixin.objects.create()
        with patch('mtp.apps.broker_models_hooks.tasks.publish_to_broker.delay') as mocked:
            new_related_model.modelwithm2m_set.add(self.model_with_m2m)

        mocked.assert_called_once_with(
            new_related_model.get_serialized_representation(),
            new_related_model.get_modelwithm2m_broker_topic(self.model_with_m2m),
            'ADD'
        )

    def test_add_multiple_related_instances_from_inverse_should_call_publish_to_broker(self):
        """Test when adding multiple related instance from the inverse relation"""

        # Add the related instances
        new_related_model = ModelWithBrokerHookM2MRelatedMixin.objects.create()
        new_model_with_m2m = ModelWithM2M.objects.create()
        with patch('mtp.apps.broker_models_hooks.tasks.publish_to_broker.delay') as mocked:
            new_related_model.modelwithm2m_set.add(self.model_with_m2m, new_model_with_m2m)

        calls = [
            call(
                new_related_model.get_serialized_representation(),
                new_related_model.get_modelwithm2m_broker_topic(self.model_with_m2m),
                'ADD'
            ),
            call(
                new_related_model.get_serialized_representation(),
                new_related_model.get_modelwithm2m_broker_topic(new_model_with_m2m),
                'ADD'
            )
        ]

        mocked.assert_has_calls(calls, any_order=True)

    def test_remove_one_related_instance_from_inverse_should_call_publish_to_broker(self):
        """Test when removing a related instance from the inverse relation"""

        # Add the related instance
        new_related_model = ModelWithBrokerHookM2MRelatedMixin.objects.create()
        new_related_model.modelwithm2m_set.add(self.model_with_m2m)

        # And remove it
        with patch('mtp.apps.broker_models_hooks.tasks.publish_to_broker.delay') as mocked:
            new_related_model.modelwithm2m_set.remove(self.model_with_m2m)

        mocked.assert_called_once_with(
            new_related_model.get_serialized_representation(),
            new_related_model.get_modelwithm2m_broker_topic(self.model_with_m2m),
            'REMOVE'
        )

    def test_remove_multiple_related_instances_from_inverse_should_call_publish_to_broker(self):
        """Test when removing multiple related instance from the inverse relation"""

        # Add the related instances
        new_related_model = ModelWithBrokerHookM2MRelatedMixin.objects.create()
        new_model_with_m2m = ModelWithM2M.objects.create()
        new_related_model.modelwithm2m_set.add(self.model_with_m2m, new_model_with_m2m)

        # Remove the related instances
        with patch('mtp.apps.broker_models_hooks.tasks.publish_to_broker.delay') as mocked:
            new_related_model.modelwithm2m_set.remove(self.model_with_m2m, new_model_with_m2m)

        calls = [
            call(
                new_related_model.get_serialized_representation(),
                new_related_model.get_modelwithm2m_broker_topic(self.model_with_m2m),
                'REMOVE'
            ),
            call(
                new_related_model.get_serialized_representation(),
                new_related_model.get_modelwithm2m_broker_topic(new_model_with_m2m),
                'REMOVE'
            )
        ]

        mocked.assert_has_calls(calls, any_order=True)
