"""Model-related test cases for the ``ideas`` app"""

import os
from mock import patch

from django.conf import settings
from django.core.exceptions import ValidationError
from django.test.testcases import TestCase
from django.utils.translation import override, ugettext_lazy as _

from mtp.apps.common.tests.test_cases import WithMediaOnDisk
from mtp.apps.data_export.exporters import ExcelDataExporter
from mtp.apps.trainings.factories import TrainingFactory, TrainingSessionFactory

from ..apiv2.serializers import (
    BrainstormingIdeaSerializer,
    BrainstormingTagSerializer,
    QuestionWallIdeaSerializer,
    WordCloudIdeaSerializer,
)
from ..factories import (
    BrainstormingFactory,
    BrainstormingIdeaFactory,
    BrainstormingTagFactory,
    QuestionWallIdeaFactory,
    WordCloudIdeaFactory,
)
from ..models import (
    BrainstormingIdea,
    BrainstormingTag,
    WordCloudIdea
)

from .test_cases import IdeasTestCase


class WordCloudTestCase(IdeasTestCase):
    """Test case for the ``WordCloud`` model"""

    def test_wordcloudidea_should_raise_if_space_in_test(self):
        """Test that one cannot add a ``WordCloudIdea`` with spaces in text"""

        # The exception message is casted to a string by ``assertRaisesMessage``
        message = str({'text': [str(_('No spaces are allowed'))]})

        with self.assertRaisesMessage(ValidationError, message):
            wordcloudidea = WordCloudIdea(
                wordcloud=self.wordcloud,
                session=self.training_session,
                text='there is space in this string'
            )

            wordcloudidea.full_clean()

        # Without spaces, it should work
        wordcloudidea.text = 'nospacesanymore'
        wordcloudidea.full_clean()


class BrainstormingTagTestCase(IdeasTestCase):
    """Test case for the ``BrainstormingTag`` model"""

    def test_versionable_instance_should_return_none_if_linked_to_a_session(self):
        """Test the ``versionable_instance`` property"""

        # The default tag is not linked to a session
        self.assertIsNone(self.brainstorming_tag.session)
        self.assertEqual(
            self.brainstorming_tag.versionable_instance,
            self.training
        )

        # Create another tag linked to a session
        new_tag = BrainstormingTagFactory(
            brainstorming=self.brainstorming,
            session=self.training_session
        )
        self.assertIsNone(new_tag.versionable_instance)

    def test_should_be_sent_to_broker_should_return_false_if_tag_is_not_linked_to_a_session(self):
        """Test that the ``should_be_sent_to_broker`` method is working correctly"""

        # The default tag is not linked to a session
        self.assertIsNone(self.brainstorming_tag.session)
        self.assertFalse(self.brainstorming_tag.should_be_sent_to_broker('any'))

        # New tag linked to a session
        new_tag = BrainstormingTagFactory(
            brainstorming=self.brainstorming,
            session=self.training_session
        )

        # This tag should be sent to the broker
        self.assertTrue(new_tag.should_be_sent_to_broker('any'))

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

        # We need a tag linked to a session
        new_tag = BrainstormingTagFactory(
            brainstorming=self.brainstorming,
            session=self.training_session
        )

        self.assertEqual(
            new_tag.get_broker_topic(),
            '%s/brainstormings/%s/tags' % (
                new_tag.session_id,
                new_tag.brainstorming_id
            )
        )

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

        self.assertEqual(
            self.brainstorming_tag.get_serialized_representation(),
            BrainstormingTagSerializer(self.brainstorming_tag).data
        )

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

        with patch('mtp.apps.broker_models_hooks.tasks.publish_to_broker.delay') as mocked:
            new_tag = BrainstormingTagFactory(
                brainstorming=self.brainstorming,
                session=self.training_session
            )

        mocked.assert_called_once_with(
            new_tag.get_serialized_representation(),
            new_tag.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"""

        # We need a tag linked to a session
        new_tag = BrainstormingTagFactory(
            brainstorming=self.brainstorming,
            session=self.training_session
        )

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

        mocked.assert_called_once_with(
            new_tag.get_serialized_representation(),
            new_tag.get_broker_topic(),
            'UPDATE'
        )


class BrainstormingIdeaTestCase(IdeasTestCase):
    """Test case for the ``BrainstormingIdea`` model"""

    def test_versionable_instance_should_return_none_if_linked_to_a_session(self):
        """Test the ``versionable_instance`` property"""

        # The default idea is not linked to a session
        self.assertIsNone(self.brainstorming_idea.session)
        self.assertEqual(
            self.brainstorming_idea.versionable_instance,
            self.training
        )

        # Create another idea linked to a session
        new_idea = BrainstormingIdeaFactory(
            brainstorming=self.brainstorming,
            session=self.training_session
        )
        self.assertIsNone(new_idea.versionable_instance)

    def test_should_be_sent_to_broker_should_return_false_if_idea_is_not_linked_to_a_session(self):
        """Test that the ``should_be_sent_to_broker`` method is working correctly"""

        # The default idea is not linked to a session
        self.assertIsNone(self.brainstorming_idea.session)
        self.assertFalse(self.brainstorming_idea.should_be_sent_to_broker('any'))

        # New idea linked to a session
        new_idea = BrainstormingIdeaFactory(
            brainstorming=self.brainstorming,
            session=self.training_session
        )

        # This idea should be sent to the broker
        self.assertTrue(new_idea.should_be_sent_to_broker('any'))

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

        # We need an idea linked to a session
        new_idea = BrainstormingIdeaFactory(
            brainstorming=self.brainstorming,
            session=self.training_session
        )

        self.assertEqual(
            new_idea.get_broker_topic(),
            '%s/brainstormings/%s/ideas' % (
                new_idea.session_id,
                new_idea.brainstorming_id
            )
        )

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

        self.assertEqual(
            self.brainstorming_idea.get_serialized_representation(),
            BrainstormingIdeaSerializer(self.brainstorming_idea).data
        )

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

        with patch('mtp.apps.broker_models_hooks.tasks.publish_to_broker.delay') as mocked:
            new_idea = BrainstormingIdeaFactory(
                brainstorming=self.brainstorming,
                session=self.training_session
            )

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

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

        # We need an idea linked to a session
        new_idea = BrainstormingIdeaFactory(
            brainstorming=self.brainstorming,
            session=self.training_session
        )

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

        mocked.assert_called_once_with(
            new_idea.get_serialized_representation(),
            new_idea.get_broker_topic(),
            'UPDATE'
        )

    def test_like_an_idea_should_call_publish_to_broker(self):
        """Test that liking an idea should post to the broker"""

        # We need an idea linked to a session
        new_idea = BrainstormingIdeaFactory(
            brainstorming=self.brainstorming,
            session=self.training_session
        )

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

        mocked.assert_called_once_with(
            new_idea.get_serialized_representation(),
            new_idea.get_broker_topic(),
            'UPDATE'
        )


class BrainstormingDuplicateTestCase(TestCase):
    """Test case related to the ``Duplicate`` behaviour of the ``Brainstorming`` model"""

    @classmethod
    def setUpTestData(cls):
        """Create a training with only one ``Brainstorming`` to duplicate"""

        super().setUpTestData()

        cls.training = TrainingFactory()
        cls.brainstorming = BrainstormingFactory(training=cls.training)
        cls.brainstorming_tag = BrainstormingTagFactory(brainstorming=cls.brainstorming)
        cls.brainstorming_idea = BrainstormingIdeaFactory(brainstorming=cls.brainstorming)
        cls.brainstorming_idea.tags.add(cls.brainstorming_tag)

    def test_duplicate_training_should_duplicate_only_the_training_tags_and_ideas(self):
        """Test that duplicate a training should only duplicate the training tags and ideas"""

        # We got one tag and one idea
        self.assertEqual(BrainstormingIdea.objects.count(), 1)
        self.assertEqual(BrainstormingTag.objects.count(), 1)

        self.training.duplicate()

        # The "training" tags and ideas were duplicated
        self.assertEqual(BrainstormingIdea.objects.count(), 2)
        self.assertEqual(BrainstormingTag.objects.count(), 2)

        # Add a new session tag and a new session idea
        BrainstormingTagFactory(
            brainstorming=self.brainstorming,
            session=TrainingSessionFactory()
        )
        BrainstormingIdeaFactory(
            brainstorming=self.brainstorming,
            session=TrainingSessionFactory()
        )

        # We got a total of 3 tags and 3 ideas
        self.assertEqual(BrainstormingIdea.objects.count(), 3)
        self.assertEqual(BrainstormingTag.objects.count(), 3)

        self.training.duplicate()

        # Only the tags and ideas linked to the training were duplicated (1 new tag and 1 new idea)
        self.assertEqual(BrainstormingIdea.objects.count(), 4)
        self.assertEqual(BrainstormingTag.objects.count(), 4)

        # We add a new "session idea" linked to a "training tag"
        idea = BrainstormingIdeaFactory(
            brainstorming=self.brainstorming,
            session=TrainingSessionFactory()
        )
        idea.tags.add(self.brainstorming_tag)

        # We add a new "session tag" linked to a "training idea"
        tag = BrainstormingTagFactory(
            brainstorming=self.brainstorming,
            session=TrainingSessionFactory()
        )
        tag.ideas.add(self.brainstorming_idea)

        self.training.duplicate()

        # Only the tags and ideas linked to the training were duplicated (1 new tag and 1 new idea)
        self.assertEqual(BrainstormingIdea.objects.count(), 6)
        self.assertEqual(BrainstormingTag.objects.count(), 6)


class QuestionWallIdeaTestCase(IdeasTestCase):
    """Test case for the ``QuestionWallIdea`` model"""

    @classmethod
    def setUpTestData(cls):
        """Create a new ``QuestionWallIdea``"""

        super().setUpTestData()

        cls.questionwall_idea = QuestionWallIdeaFactory(
            questionwall=cls.questionwall,
            session=cls.training_session
        )

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

        self.assertEqual(
            self.questionwall_idea.get_broker_topic(),
            '%s/questionwalls/%s/ideas' % (
                self.questionwall_idea.session_id,
                self.questionwall_idea.questionwall_id
            )
        )

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

        self.assertEqual(
            self.questionwall_idea.get_serialized_representation(),
            QuestionWallIdeaSerializer(self.questionwall_idea).data
        )

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

        with patch('mtp.apps.broker_models_hooks.tasks.publish_to_broker.delay') as mocked:
            new_idea = QuestionWallIdeaFactory(
                questionwall=self.questionwall,
                session=self.training_session
            )

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

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

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

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

    def test_like_an_idea_should_call_publish_to_broker(self):
        """Test that liking an idea should post to the broker"""

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

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


class WordCloudIdeaTestCase(IdeasTestCase):
    """Test case for the ``WordCloudIdea`` model"""

    @classmethod
    def setUpTestData(cls):
        """Create a new ``WordCloudIdea``"""

        super().setUpTestData()

        cls.wordcloud_idea = WordCloudIdeaFactory(
            wordcloud=cls.wordcloud,
            session=cls.training_session
        )

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

        self.assertEqual(
            self.wordcloud_idea.get_broker_topic(),
            '%s/wordclouds/%s/ideas' % (
                self.wordcloud_idea.session_id,
                self.wordcloud_idea.wordcloud_id
            )
        )

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

        self.assertEqual(
            self.wordcloud_idea.get_serialized_representation(),
            WordCloudIdeaSerializer(self.wordcloud_idea).data
        )

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

        with patch('mtp.apps.broker_models_hooks.tasks.publish_to_broker.delay') as mocked:
            new_idea = WordCloudIdeaFactory(
                wordcloud=self.wordcloud,
                session=self.training_session
            )

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

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

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

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

    def test_like_an_idea_should_call_publish_to_broker(self):
        """Test that liking an idea should post to the broker"""

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

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


class BrainstormingResultsTestCase(WithMediaOnDisk, IdeasTestCase):
    """Test case related to the results export for the ``Brainstorming`` model"""

    @override('en')
    def test_brainstorming_get_results_excel_data(self):
        """Test "data" key from the ``get_results_excel_data`` of the ``Brainstorming`` model"""

        data_frame = self.brainstorming.get_results_excel_data(self.training_session)['data']

        # We have one idea (created at the training level)
        self.assertEqual(len(data_frame.index), 1)

        # Check the columns of the data frame
        columns = [
            str(_('Likes')),
            'Tag %d: %s' % (1, self.brainstorming_tag.name),
            str(_('Author first name')),
            str(_('Author last name')),
        ]

        self.assertEqual(list(data_frame.columns.values), columns)

        # Check the values of our first row (this will fail if the index don't exist
        self.assertEqual(
            list(data_frame.loc['%d - %s' % (0, self.brainstorming_idea.text)]),
            [0.0, 'x', '', '']
        )

        # New idea
        new_idea = BrainstormingIdeaFactory(
            brainstorming=self.brainstorming,
            session=self.training_session,
            user=self.trainee
        )

        data_frame = self.brainstorming.get_results_excel_data(self.training_session)['data']

        # We should now get two rows
        self.assertEqual(len(data_frame.index), 2)

        # Check the values of our second row
        self.assertEqual(
            list(data_frame.loc['%d - %s' % (1, new_idea.text)]),
            [0.0, '', self.trainee.first_name, self.trainee.last_name]
        )

        # Associate the idea with the tag
        new_idea.tags.add(self.brainstorming_tag)

        data_frame = self.brainstorming.get_results_excel_data(self.training_session)['data']

        # Check the values of our second row
        self.assertEqual(
            list(data_frame.loc['%d - %s' % (1, new_idea.text)]),
            [0.0, 'x', self.trainee.first_name, self.trainee.last_name]
        )

        # Like this idea
        self.trainee.like(new_idea)

        data_frame = self.brainstorming.get_results_excel_data(self.training_session)['data']

        # Check the values of our second row
        self.assertEqual(
            list(data_frame.loc['%d - %s' % (1, new_idea.text)]),
            [1.0, 'x', self.trainee.first_name, self.trainee.last_name]
        )

    def test_export_excel_from_data_results(self):
        """Test that exporting an excel file from the ``get_results_excel_data`` should work"""

        path = os.path.join(settings.MEDIA_ROOT, 'test.xlsx')
        exporter = ExcelDataExporter(path)
        results = self.brainstorming.get_results_excel_data(self.training_session)

        results['sheet_name'] = self.brainstorming.name

        exporter.add_sheet(**results)
        exporter.save()

        self.assertTrue(os.path.isfile(path))


class QuestionWallResultsTestCase(WithMediaOnDisk, IdeasTestCase):
    """Test case related to the results export for the ``QuestionWall`` model"""

    @override('en')
    def test_questionwall_get_results_excel_data(self):
        """Test "data" key from the ``get_results_excel_data`` of the ``QuestionWall`` model"""

        data_frame = self.questionwall.get_results_excel_data(self.training_session)['data']

        # We have 0 idea
        self.assertEqual(len(data_frame.index), 0)

        # Check the columns of the data frame
        columns = [
            str(_('Likes')),
            str(_('Author first name')),
            str(_('Author last name')),
        ]

        self.assertEqual(list(data_frame.columns.values), columns)

        # New idea
        new_idea = QuestionWallIdeaFactory(
            questionwall=self.questionwall,
            session=self.training_session,
            user=self.trainee
        )

        data_frame = self.questionwall.get_results_excel_data(self.training_session)['data']

        # We should now get one row
        self.assertEqual(len(data_frame.index), 1)

        # Check the values of first row
        self.assertEqual(
            list(data_frame.loc['%d - %s' % (0, new_idea.text)]),
            [0.0, self.trainee.first_name, self.trainee.last_name]
        )

        # Like this idea
        self.trainee.like(new_idea)

        data_frame = self.questionwall.get_results_excel_data(self.training_session)['data']

        # Check the values of our second row
        self.assertEqual(
            list(data_frame.loc['%d - %s' % (0, new_idea.text)]),
            [1.0, self.trainee.first_name, self.trainee.last_name]
        )

        # Another idea
        QuestionWallIdeaFactory(
            questionwall=self.questionwall,
            session=self.training_session,
            user=self.trainee
        )

        data_frame = self.questionwall.get_results_excel_data(self.training_session)['data']

        # We should now get two rows
        self.assertEqual(len(data_frame.index), 2)

    def test_export_excel_from_data_results(self):
        """Test that exporting an excel file from the ``get_results_excel_data`` should work"""

        path = os.path.join(settings.MEDIA_ROOT, 'test.xlsx')
        exporter = ExcelDataExporter(path)
        results = self.questionwall.get_results_excel_data(self.training_session)

        results['sheet_name'] = self.questionwall.name

        exporter.add_sheet(**results)
        exporter.save()

        self.assertTrue(os.path.isfile(path))


class WordCloudResultsTestCase(WithMediaOnDisk, IdeasTestCase):
    """Test case related to the results export for the ``WordCloud`` model"""

    @override('en')
    def test_wordcloud_get_results_excel_data(self):
        """Test "data" key from the ``get_results_excel_data`` of the ``WordCloud`` model"""

        data_frame = self.wordcloud.get_results_excel_data(self.training_session)['data']

        # We have 0 idea
        self.assertEqual(len(data_frame.index), 0)

        # Check the columns of the data frame
        columns = [
            str(_('Likes')),
            str(_('Author first name')),
            str(_('Author last name')),
        ]

        self.assertEqual(list(data_frame.columns.values), columns)

        # New idea
        new_idea = WordCloudIdeaFactory(
            wordcloud=self.wordcloud,
            session=self.training_session,
            user=self.trainee
        )

        data_frame = self.wordcloud.get_results_excel_data(self.training_session)['data']

        # We should now get one row
        self.assertEqual(len(data_frame.index), 1)

        # Check the values of first row
        self.assertEqual(
            list(data_frame.loc['%d - %s' % (0, new_idea.text)]),
            [0.0, self.trainee.first_name, self.trainee.last_name]
        )

        # Like this idea
        self.trainee.like(new_idea)

        data_frame = self.wordcloud.get_results_excel_data(self.training_session)['data']

        # Check the values of our second row
        self.assertEqual(
            list(data_frame.loc['%d - %s' % (0, new_idea.text)]),
            [1.0, self.trainee.first_name, self.trainee.last_name]
        )

        # Another idea
        WordCloudIdeaFactory(
            wordcloud=self.wordcloud,
            session=self.training_session,
            user=self.trainee
        )

        data_frame = self.wordcloud.get_results_excel_data(self.training_session)['data']

        # We should now get two rows
        self.assertEqual(len(data_frame.index), 2)

    def test_export_excel_from_data_results(self):
        """Test that exporting an excel file from the ``get_results_excel_data`` should work"""

        path = os.path.join(settings.MEDIA_ROOT, 'test.xlsx')
        exporter = ExcelDataExporter(path)
        results = self.wordcloud.get_results_excel_data(self.training_session)

        results['sheet_name'] = self.questionwall.name

        exporter.add_sheet(**results)
        exporter.save()

        self.assertTrue(os.path.isfile(path))
