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

from mock import patch

from mtp.apps.brainstormers.api.serializers import (
    BrainstormerPropositionGroupSimpleSerializer,
    BrainstormerPropositionSerializer
)

from mtp.apps.brainstormers.constants import BRAINSTORMER_TYPE_CHOICES

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 BrainstormerTestCase


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()

        brainstormer = BrainstormerFactory(
            brainstormer_type=BRAINSTORMER_TYPE_CHOICES.PROPOSITIONWALL
        )
        cls.proposition_wall = brainstormer.proxified()

    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_create_brainstormer_from_brainstormer_template_type(self):
        """Test creating a new brainstormer from a template with a brainstormer_type."""
        for choice in BRAINSTORMER_TYPE_CHOICES.choices:
            with self.subTest(choice=choice):
                brainstormer_template = BrainstormerTemplateFactory(
                    brainstormer_type=choice[0]
                )
                brainstormer = Brainstormer(session=self.training_session)
                brainstormer.save(template=brainstormer_template)
                self.assertEqual(brainstormer.brainstormer_type, choice[0])

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

        # Test return all instances
        propositions = BrainstormerPropositionFactory.create_batch(
            2,
            brainstormer=self.proposition_wall
        )

        # link the propositions to the group
        group.propositions.add(*propositions)

        self.assertEqual(len(self.proposition_wall.get_propositions()), 2)

        # Test not return instances ``is deleted``
        propositions = BrainstormerPropositionFactory.create_batch(
            2,
            brainstormer=self.proposition_wall,
            is_deleted=True
        )

        self.assertEqual(len(self.proposition_wall.get_propositions()), 2)

    def test_get_grouped_propositions_return_grouped(self):
        """Test that ``get_grouped_propositions`` return all instances with a group."""
        # only propositionwalls have groups
        group = BrainstormerPropositionGroupFactory(brainstormer=self.proposition_wall)

        # create propositions with groups
        propositions = BrainstormerPropositionFactory.create_batch(
            2,
            brainstormer=self.proposition_wall
        )
        for proposition in propositions:
            proposition.groups.add(group)

        # create propositions without groups
        BrainstormerPropositionFactory.create_batch(
            2,
            brainstormer=self.proposition_wall
        )

        key = [key for key in self.proposition_wall.get_grouped_propositions().keys()][0]
        self.assertEqual(len(self.proposition_wall.get_grouped_propositions()), 1)
        self.assertEqual(len(self.proposition_wall.get_grouped_propositions()[key]), 2)

    def test_ungrouped_propositions_return_ungrouped(self):
        """Test that ``get_ungrouped_propositions`` return all instances with no group."""
        # only propositionwalls have groups

        group = BrainstormerPropositionGroupFactory(brainstormer=self.proposition_wall)
        propositions = BrainstormerPropositionFactory.create_batch(
            2,
            brainstormer=self.proposition_wall
        )
        for proposition in propositions:
            proposition.groups.add(group)
        BrainstormerPropositionFactory.create_batch(
            2,
            brainstormer=self.proposition_wall
        )
        self.assertEqual(len(self.proposition_wall.get_ungrouped_propositions()), 2)
        for proposition in self.proposition_wall.get_ungrouped_propositions():
            self.assertEqual(len(proposition.groups.all()), 0)

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

           We use a PropositionWall proxy model because Brainstormers does not have the
           `get_groups() methods`
        """
        nb_groups = len(self.proposition_wall.get_groups())

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

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


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

    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(
                brainstormer=self.brainstormer
            )

        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(BrainstormerTestCase):
    """Test case for the ``BrainstormerPropositionGroup`` model"""

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

        super().setUpTestData()

        cls.brainstormer = BrainstormerFactory()
        cls.group = BrainstormerPropositionGroupFactory(brainstormer=cls.brainstormer)

    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(),
            BrainstormerPropositionGroupSimpleSerializer(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'
        )
