""" This module holds the tests models of the ``brainstormers`` app. """

from mock import patch

from mtp.apps.brainstormers.api.serializers import (
    BrainstormerSimplePropositionGroupSerializer,
    BrainstormerPropositionSerializer
)
from mtp.apps.brainstormers.constants import DEFAULT_GROUP_ATTRIBUTES
from mtp.apps.brainstormers.factories import (
    BrainstormerFactory,
    BrainstormerPropositionFactory,
    BrainstormerPropositionGroupFactory,
    BrainstormerTemplateFactory,
)
from mtp.apps.brainstormers.models import Brainstormer, BrainstormerTemplate
from mtp.apps.common.tests.test_cases import MTPTestCase

from .test_cases import BrainstormerActivityTestCase


class BrainstormerTemplateModelTestCase(MTPTestCase):
    """Test case for the ``BrainstormerTemplate`` model"""

    def test_save_a_brainstormer_template_with_extra_long_name(self):
        """Test that save a template with a long name should crop it"""

        # Create a brainstormer template with extra long name
        template = BrainstormerTemplate(
            name='x' * 101,
            training_id=self.training.id
        )
        template.save()

        # The name field should be cropped
        self.assertEqual(
            template.name,
            'x' * 95 + '(...)'
        )


class BrainstormerModelTestCase(MTPTestCase):
    """ Test case that tests the ``Brainstormer`` model. """

    @classmethod
    def setUpTestData(cls):
        super().setUpTestData()

        cls.brainstormer = BrainstormerFactory()

    def test_create_brainstormer_from_brainstormer_template_with_long_name(self):
        """Test creating a new brainstormer from a template with a long name"""

        brainstormer_template = BrainstormerTemplateFactory(
            name='x' * 101  # This should be truncated (length > 100)
        )

        # Create the brainstormer from this template
        brainstormer = Brainstormer(session=self.training_session)
        brainstormer.save(template=brainstormer_template)

        # The field "name" on the brainstormer should be truncated
        self.assertEqual(
            brainstormer.name,
            'x' * 95 + '(...)'
        )

    def test_default_group_should_be_created(self):
        """Test that the default group is created."""
        self.assertTrue(self.brainstormer.groups.filter(
            name=DEFAULT_GROUP_ATTRIBUTES.get('name')
        ).exists())

    def test_get_propositions(self):
        """Test that ``get_propositions`` return all instances expected instances ``is_deleted`` """
        group = BrainstormerPropositionGroupFactory(brainstormer=self.brainstormer)

        # Test return all instances
        BrainstormerPropositionFactory.create_batch(2, group=group)
        self.assertEqual(len(self.brainstormer.get_propositions()), 2)

        # Test not return instances ``is deleted``
        BrainstormerPropositionFactory.create_batch(3, group=group, is_deleted=True)
        self.assertEqual(len(self.brainstormer.get_propositions()), 2)

    def test_get_groups(self):
        """Test that ``get_groups`` return all instances excluding instances ``is_deleted`` """
        nb_group = len(self.brainstormer.get_groups())

        # Test return all instances
        BrainstormerPropositionGroupFactory.create_batch(2, brainstormer=self.brainstormer)
        self.assertEqual(len(self.brainstormer.get_groups()), nb_group + 2)

        # Test not return instances ``is deleted``
        BrainstormerPropositionGroupFactory.create_batch(
            3, brainstormer=self.brainstormer, is_deleted=True
        )
        self.assertEqual(len(self.brainstormer.get_groups()), nb_group + 2)

    def test_compute_hash(self):
        """Test that brainstormer creation, compute hash fields"""
        self.assertIsNotNone(self.brainstormer.activity.hash_preview)


class BrainstormerActivityModelTestCase(BrainstormerActivityTestCase):
    """Test cases for the ``BrainstormerActivity`` model"""

    def test_media_files(self):
        """Test that the media file property should return the paths of the media files"""

        self.assertCountEqual(
            self.brainstormer_activity.media_files,
            [
                self.brainstormer_activity.preview.name,
                self.brainstormer_activity.image.name,
            ]
        )


class BrainstormerPropositionModelTestCase(BrainstormerActivityTestCase):
    """Test case for the ``BrainstormerProposition`` model"""

    @classmethod
    def setUpTestData(cls):
        """Create a brainstormer and a proposition"""

        super().setUpTestData()

        cls.brainstormer = BrainstormerFactory()
        cls.proposition = BrainstormerPropositionFactory(
            group=cls.brainstormer.groups.first()
        )

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

        self.assertEqual(
            self.proposition.get_broker_topic(),
            '%s/brainstormers/%s/propositions' % (
                self.brainstormer.session_id,
                self.brainstormer.id
            )
        )

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

        self.assertEqual(
            self.proposition.get_serialized_representation(),
            BrainstormerPropositionSerializer(self.proposition).data
        )

    def test_creating_a_new_proposition_should_post_to_the_broker(self):
        """Test that creating a new proposition should post to the broker"""

        with patch('mtp.apps.broker_models_hooks.tasks.publish_to_broker.delay') as mocked:
            proposition = BrainstormerPropositionFactory(
                group=self.brainstormer.groups.first()
            )

        mocked.assert_called_once_with(
            proposition.get_serialized_representation(),
            proposition.get_broker_topic(),
            'CREATE'
        )

    def test_updating_an_existing_proposition_should_call_publish_to_broker(self):
        """Test that updating an existing proposition should post to the broker"""

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

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

    def test_like_a_proposition_should_call_publish_to_broker(self):
        """Test that liking a proposition should post to the broker"""

        with patch('mtp.apps.broker_models_hooks.tasks.publish_to_broker.delay') as mocked:
            self.trainee.like(self.proposition)

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


class BrainstormerPropositionGroupModelTestCase(BrainstormerActivityTestCase):
    """Test case for the ``BrainstormerPropositionGroup`` model"""

    @classmethod
    def setUpTestData(cls):
        """Create a brainstormer and a group"""

        super().setUpTestData()

        cls.brainstormer = BrainstormerFactory()
        cls.group = cls.brainstormer.groups.first()

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

        self.assertEqual(
            self.group.get_broker_topic(),
            '%s/brainstormers/%s/groups' % (
                self.brainstormer.session.id,
                self.brainstormer.id
            )
        )

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

        self.assertEqual(
            self.group.get_serialized_representation(),
            BrainstormerSimplePropositionGroupSerializer(self.group).data
        )

    def test_creating_a_new_group_should_post_to_the_broker(self):
        """Test that creating a new group should post to the broker"""

        with patch('mtp.apps.broker_models_hooks.tasks.publish_to_broker.delay') as mocked:
            group = BrainstormerPropositionGroupFactory(
                brainstormer=self.brainstormer
            )

        mocked.assert_called_once_with(
            group.get_serialized_representation(),
            group.get_broker_topic(),
            'CREATE'
        )

    def test_updating_an_existing_group_should_post_to_the_broker(self):
        """Test that updating an existing group should post to the broker"""

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

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