"""Module holding tests related to ``mtp.app.surveys.redis_models``."""

from datetime import datetime

from django.test import override_settings
from django.utils.timezone import utc

from mtp.apps.accounts.factories import TrainerFactory
from mtp.apps.trainings.factories import TrainingFactory, TrainingSessionFactory

from ..models import Survey

from .test_cases import SurveyTestCase


class SurveyRedisModelTestCase(SurveyTestCase):

    @classmethod
    def setUpTestData(cls):
        """Create a survey in addition to existing test data."""

        super().setUpTestData()

        # Create a survey
        cls.survey, __ = Survey.objects.get_or_create_for_training(cls.training, cls.trainer)

    def prepare_question(self, question, choices):
        # Mark the question as used for global satisfaction to check that data is correctly saved.
        question.is_for_global_satisfaction = True
        # Global satisfaction expect percentage
        question.value_type = question.VALUE_TYPES.PERCENT
        question.save()

        # Update choices to have 0%, 25%, 50%, 75%, 100%
        self.set_choices_values(choices, 'value_number',
                                [x * 25 for x in range(0, len(choices))])


@override_settings(SURVEYS_AUTO_COMPUTE_STATS=True)
class QuestionDataTestCase(SurveyRedisModelTestCase):

    def check_single_answer(self, answer_time, value, check_global=True):
        """Utility function to check that there is only one answer with a specific value.

        It is useful for test that save a single answer for ``self.single_choice_question``.

        """

        # Retrieve the redis model instance
        question_data = self.single_choice_question.redis_instance

        # Compute some keys used to check data
        day = str(answer_time.date())
        answer_key = question_data.get_answer_key(self.training_session.pk, self.trainee.pk,
                                                  self.single_choice_question.pk)
        train_key = question_data.get_training_and_trainer_key(self.training.pk, self.trainer.pk)

        # Test the global number and average for this question and globally.
        test_data = [question_data]
        if check_global:
            test_data.append(self.global_satisfaction)

        for data in test_data:
            with self.subTest(data=data):
                self.check_sortedset(data.nb_by_day, [(day, 1)])
                self.check_sortedset(data.avg_by_day, [(day, value)])

                # Test the global number and average for this trainer.
                self.check_sortedset(data.nb_by_day_for_trainer(self.trainer.pk), [(day, 1)])
                self.check_sortedset(data.avg_by_day_for_trainer(self.trainer.pk), [(day, value)])

                # Test the global number and average for this training.
                self.check_sortedset(data.nb_by_day_for_training(self.training.pk), [(day, 1)])
                self.check_sortedset(data.avg_by_day_for_training(self.training.pk),
                                     [(day, value)])

                # Test the global number and average for this training+trainer tuple.
                self.check_sortedset(data.nb_by_day_for_training_and_trainer(train_key),
                                     [(day, 1)])
                self.check_sortedset(data.avg_by_day_for_training_and_trainer(train_key),
                                     [(day, value)])

                # Test that answer is well saved for the session
                self.check_sortedset(data.answers_for_session(self.training_session.pk),
                                     [(answer_key, value)])

    def test_answer_value_should_be_saved_in_all_needed_zsets(self):
        """Test that data are correctly saved.

        We check that each zset for a question and for global satisfaction are correctly filled.
        """

        # Make the question accept percentages and make it count for global satisfaction
        self.prepare_question(self.single_choice_question, self.single_choice_choices)

        # Selected choice.
        choice = self.get_choice_having_value(self.single_choice_choices, 'value_number', 25)

        # Save the answer, to have data to test.
        answer = self.save_answer(self.single_choice_question, [choice])

        # check that we have an answer for the correct date with the correct value
        self.check_single_answer(answer.time, 25)

    def test_new_value_for_answer_should_recompute_data(self):

        # Make the question accept percentages and make it count for global satisfaction
        self.prepare_question(self.single_choice_question, self.single_choice_choices)

        # Selected choice.
        choice = self.get_choice_having_value(self.single_choice_choices, 'value_number', 25)

        # Save the answer.
        self.save_answer(self.single_choice_question, [choice])

        # Select another choice.
        choice = self.get_choice_having_value(self.single_choice_choices, 'value_number', 50)

        # Saving the answer should update the value.
        answer = self.save_answer(self.single_choice_question, [choice])

        # Check that we have an answer for the correct date with the new correct value
        self.check_single_answer(answer.time, 50)

    def test_many_answers_should_be_saved_correctly(self):
        """Test nb and avg computations with many answers.

        For this test will create:
        For question 1   ,
            - 2 answers for question1 on day 1 on training 1 on session 1 for trainer 1
            - 2 answers for question1 on day 1 on training 1 on session 2 for trainer 2
            - 2 answers for question2 on day 1 on training 1 on session 3 for trainer 1
            - 2 answers for question1 on day 2 on training 1 on session 4 for trainer 1
        For question 2:
            - 2 answers for question2 on day 2 on training 2 on session 5 for trainer 2

        All answers will be about a question counting for global satisfaction.

        """

        # Prepare questions with choices
        question1, choices1 = self.create_single_choice_question(self.admin_pool)
        question2, choices2 = self.create_single_choice_question(self.admin_pool)
        self.prepare_question(question1, choices1)
        self.prepare_question(question2, choices2)

        # Prepare the trainers
        trainer1 = self.trainer
        trainer2 = TrainerFactory()

        # Prepare the trainings
        training1 = self.training
        training2 = TrainingFactory()

        # Prepare the trainees
        trainee1, trainee2, trainee3 = self.trainees

        # Prepare the sessions
        session1 = self.training_session
        session1.users.add(trainee2)  # trainee1 already in it
        session2 = TrainingSessionFactory(training=training1, trainer=trainer2)
        session2.users.add(trainee1, trainee3)
        session3 = TrainingSessionFactory(training=training1, trainer=trainer1)
        session3.users.add(trainee2, trainee3)
        session4 = TrainingSessionFactory(training=training1, trainer=trainer1)
        session4.users.add(trainee1, trainee2)
        session5 = TrainingSessionFactory(training=training2, trainer=trainer2)
        session5.users.add(trainee1, trainee3)

        # Prepare the days
        date1 = datetime(2015, 6, 2, 12, 12, 12, tzinfo=utc)
        day1 = str(date1.date())
        date2 = datetime(2015, 6, 3, 12, 12, 12, tzinfo=utc)
        day2 = str(date2.date())

        # Create the answers!
        def add_answer(question, session, trainee, when, value):
            choice = self.get_choice_having_value(question.choices.all(), 'value_number', value)
            survey, __ = Survey.objects.get_or_create_for_training(session.training,
                                                                   session.trainer)

            self.save_answer(
                question=question,
                choices=[choice],
                survey=survey,
                training_session=session,
                trainee=trainee,
                answer_time=when,
            )

        add_answer(question1, session1, trainee1, date1, 0)
        add_answer(question1, session1, trainee2, date1, 25)
        add_answer(question1, session2, trainee1, date1, 50)
        add_answer(question1, session2, trainee3, date1, 75)
        add_answer(question2, session3, trainee2, date1, 100)
        add_answer(question2, session3, trainee3, date1, 0)
        add_answer(question1, session4, trainee1, date2, 25)
        add_answer(question1, session4, trainee2, date2, 50)
        add_answer(question2, session5, trainee1, date2, 75)
        add_answer(question2, session5, trainee3, date2, 100)

        # Now we can check the data...

        data1 = question1.redis_instance
        data2 = question2.redis_instance
        datag = self.global_satisfaction

        # For question1, we should have:
        # - globally, on day1: 4 answers, (0+25+50+75)/4 = 37.5 avg
        #             on day2: 2 answers, (25+50)/2 = 37.5 avg
        self.check_sortedset(data1.nb_by_day, [(day1, 4), (day2, 2)])
        self.check_sortedset(data1.avg_by_day, [(day1, 37.5), (day2, 37.5)])
        # - for trainer1, on day1: 2 answers, (0+25)/2 = 12.5 avg
        #                 on day2: 2 answers, (25+50)/2 = 37.5 avg
        self.check_sortedset(data1.nb_by_day_for_trainer(trainer1.pk), [(day1, 2), (day2, 2)])
        self.check_sortedset(data1.avg_by_day_for_trainer(trainer1.pk),
                             [(day1, 12.5), (day2, 37.5)])
        # - for trainer2, on day1: 2 answers, (50+75)/2 = 62.5 avg
        #                 on day2: 0 answers
        self.check_sortedset(data1.nb_by_day_for_trainer(trainer2.pk), [(day1, 2)])
        self.check_sortedset(data1.avg_by_day_for_trainer(trainer2.pk), [(day1, 62.5)])
        # - for training1, on day1: 4 answers, (0+25+50+75)/4 = 37.5 avg
        #                  on day2: 2 answers, (25+50)/2 = 37.5 avg
        self.check_sortedset(data1.nb_by_day_for_training(training1.pk), [(day1, 4), (day2, 2)])
        self.check_sortedset(data1.avg_by_day_for_training(training1.pk),
                             [(day1, 37.5), (day2, 37.5)])
        # - for training2, on day1: 0 answers
        #                  on day2: 0 answers
        self.check_sortedset(data1.nb_by_day_for_training(training2.pk), [])
        self.check_sortedset(data1.avg_by_day_for_training(training2.pk), [])
        # - for training1 by trainer1, on day1: 2 answers, (0+25)/2 = 12.5 avg
        #                              on day2: 2 answers, (25+50)/2 = 37.5 avg
        train_key = data1.get_training_and_trainer_key(training1.pk, trainer1.pk)
        self.check_sortedset(data1.nb_by_day_for_training_and_trainer(train_key),
                             [(day1, 2), (day2, 2)])
        self.check_sortedset(data1.avg_by_day_for_training_and_trainer(train_key),
                             [(day1, 12.5), (day2, 37.5)])
        # - for training2 by trainer1, on day1: 0 answers
        #                              on day2: 0 answers
        train_key = data1.get_training_and_trainer_key(training2.pk, trainer1.pk)
        self.check_sortedset(data1.nb_by_day_for_training_and_trainer(train_key), [])
        self.check_sortedset(data1.avg_by_day_for_training_and_trainer(train_key), [])
        # - for training1 by trainer2, on day1: 2 answers, (50+75)/2 = 62.5 avg
        #                              on day2: 0 answers
        train_key = data1.get_training_and_trainer_key(training1.pk, trainer2.pk)
        self.check_sortedset(data1.nb_by_day_for_training_and_trainer(train_key), [(day1, 2)])
        self.check_sortedset(data1.avg_by_day_for_training_and_trainer(train_key), [(day1, 62.5)])
        # - for training2 by trainer2, on day1: 0 answers
        #                              on day2: 0 answers
        train_key = data1.get_training_and_trainer_key(training2.pk, trainer2.pk)
        self.check_sortedset(data1.nb_by_day_for_training_and_trainer(train_key), [])
        self.check_sortedset(data1.avg_by_day_for_training_and_trainer(train_key), [])
        # Then check that all answers are saved
        self.check_sortedset(data1.answers_for_session(session1.pk), [
            (data1.get_answer_key(session1.pk, trainee1.pk, question1.pk), 0),
            (data1.get_answer_key(session1.pk, trainee2.pk, question1.pk), 25)
        ])
        self.check_sortedset(data1.answers_for_session(session2.pk), [
            (data1.get_answer_key(session2.pk, trainee1.pk, question1.pk), 50),
            (data1.get_answer_key(session2.pk, trainee3.pk, question1.pk), 75)
        ])
        self.check_sortedset(data1.answers_for_session(session4.pk), [
            (data1.get_answer_key(session4.pk, trainee1.pk, question1.pk), 25),
            (data1.get_answer_key(session4.pk, trainee2.pk, question1.pk), 50)
        ])

        # For question2,
        # - globally, on day1: 2 answers, (100+0)/2 = 50 avg
        #             on day2: 2 answers, (75+100)/2 = 87.5 avg
        self.check_sortedset(data2.nb_by_day, [(day1, 2), (day2, 2)])
        self.check_sortedset(data2.avg_by_day, [(day1, 50), (day2, 87.5)])
        # - for trainer1, on day1: 2 answers, (100+0)/2 = 50 avg
        #                 on day2: 0 answers
        self.check_sortedset(data2.nb_by_day_for_trainer(trainer1.pk), [(day1, 2)])
        self.check_sortedset(data2.avg_by_day_for_trainer(trainer1.pk), [(day1, 50)])
        # - for trainer2, on day1: 0 answers
        #                 on day2: 2 answers, (75+100)/2 = 87.5 avg
        self.check_sortedset(data2.nb_by_day_for_trainer(trainer2.pk), [(day2, 2)])
        self.check_sortedset(data2.avg_by_day_for_trainer(trainer2.pk), [(day2, 87.5)])
        # - for training1, on day1: 2 answers, (100+0)/2 = 50 avg
        #                  on day2: 0 answers
        self.check_sortedset(data2.nb_by_day_for_training(training1.pk), [(day1, 2)])
        self.check_sortedset(data2.avg_by_day_for_training(training1.pk), [(day1, 50)])
        # - for training2, on day1: 0 answers
        #                  on day2: 2 answers, (75+100)/2 = 87.5 avg
        self.check_sortedset(data2.nb_by_day_for_training(training2.pk), [(day2, 2)])
        self.check_sortedset(data2.avg_by_day_for_training(training2.pk), [(day2, 87.5)])
        # - for training1 by trainer1, on day1: 2 answers, (100+0)/2 = 50 avg
        #                              on day2: 0 answers
        train_key = data2.get_training_and_trainer_key(training1.pk, trainer1.pk)
        self.check_sortedset(data2.nb_by_day_for_training_and_trainer(train_key), [(day1, 2)])
        self.check_sortedset(data2.avg_by_day_for_training_and_trainer(train_key), [(day1, 50)])
        # - for training2 by trainer 1, on day1: 0 answers
        #                              on day2: 0 answers
        train_key = data2.get_training_and_trainer_key(training2.pk, trainer1.pk)
        self.check_sortedset(data2.nb_by_day_for_training_and_trainer(train_key), [])
        self.check_sortedset(data2.avg_by_day_for_training_and_trainer(train_key), [])
        # - for training1 by trainer2, on day1: 0 answers
        #                              on day2: 0 answers
        train_key = data2.get_training_and_trainer_key(training1.pk, trainer2.pk)
        self.check_sortedset(data2.nb_by_day_for_training_and_trainer(train_key), [])
        self.check_sortedset(data2.avg_by_day_for_training_and_trainer(train_key), [])
        # - for training2 by trainer2, on day1: 0 answers
        #                              on day2: (75+100)/2 = 87.5 avg
        train_key = data2.get_training_and_trainer_key(training2.pk, trainer2.pk)
        self.check_sortedset(data2.nb_by_day_for_training_and_trainer(train_key), [(day2, 2)])
        self.check_sortedset(data2.avg_by_day_for_training_and_trainer(train_key), [(day2, 87.5)])
        # Then check that all answers are saved
        self.check_sortedset(data2.answers_for_session(session3.pk), [
            (data2.get_answer_key(session3.pk, trainee2.pk, question2.pk), 100),
            (data2.get_answer_key(session3.pk, trainee3.pk, question2.pk), 0)
        ])
        self.check_sortedset(data2.answers_for_session(session5.pk), [
            (data2.get_answer_key(session5.pk, trainee1.pk, question2.pk), 75),
            (data2.get_answer_key(session5.pk, trainee3.pk, question2.pk), 100)
        ])

        # Globally
        # - globally, on day1: 6 answers (0+25+50+75+100+0)/6 = 41.66
        #             on day2: 4 answers (25+50+75+100)/4 = 62.5 avg
        self.check_sortedset(datag.nb_by_day, [(day1, 6), (day2, 4)])
        self.check_sortedset(datag.avg_by_day, [(day1, 250/6), (day2, 62.5)])
        # - for trainer1, on day1: 4 answers (0+25+100+0)/4 = 31.25 avg
        #                 on day2: 2 answers (25+50)/2 = 37.5 avg
        self.check_sortedset(datag.nb_by_day_for_trainer(trainer1.pk), [(day1, 4), (day2, 2)])
        self.check_sortedset(datag.avg_by_day_for_trainer(trainer1.pk),
                             [(day1, 31.25), (day2, 37.5)])
        # - for trainer2, on day1: 2 answers (50+75)/2 = 62.5 avg
        #                 on day2: 2 answers (75+100)/2 = 87.5 avg
        self.check_sortedset(datag.nb_by_day_for_trainer(trainer2.pk), [(day1, 2), (day2, 2)])
        self.check_sortedset(datag.avg_by_day_for_trainer(trainer2.pk),
                             [(day1, 62.5), (day2, 87.5)])
        # - for training1, on day1: 6 answers (0+25+50+75+100+0)/6 = 41.66
        #                  on day2: 2 answers (25+50)/2 = 37.5 avg
        self.check_sortedset(datag.nb_by_day_for_training(training1.pk), [(day1, 6), (day2, 2)])
        self.check_sortedset(datag.avg_by_day_for_training(training1.pk),
                             [(day1, 250/6), (day2, 37.5)])
        # - for training2, on day1: 0 answers
        #                  on day2: 2 answers (75+100)/2 = 87.5 avg
        self.check_sortedset(datag.nb_by_day_for_training(training2.pk), [(day2, 2)])
        self.check_sortedset(datag.avg_by_day_for_training(training2.pk), [(day2, 87.5)])
        # - for training1 by trainer1, on day1: 4 answers (0+25+100+0)/4 = 31.25 avg
        #                              on day2: 2 answers (25+50)/2 = 37.5 avg
        train_key = datag.get_training_and_trainer_key(training1.pk, trainer1.pk)
        self.check_sortedset(datag.nb_by_day_for_training_and_trainer(train_key),
                             [(day1, 4), (day2, 2)])
        self.check_sortedset(datag.avg_by_day_for_training_and_trainer(train_key),
                             [(day1, 31.25), (day2, 37.5)])
        # - for training2 by trainer1, on day1: 0 answers
        #                              on day2: 0 answers
        train_key = datag.get_training_and_trainer_key(training2.pk, trainer1.pk)
        self.check_sortedset(datag.nb_by_day_for_training_and_trainer(train_key), [])
        self.check_sortedset(datag.avg_by_day_for_training_and_trainer(train_key), [])
        # - for training1 by trainer2, on day1: 2 answers (50+75)/2 = 62.5 avg
        #                              on day2: 0 answers
        train_key = datag.get_training_and_trainer_key(training1.pk, trainer2.pk)
        self.check_sortedset(datag.nb_by_day_for_training_and_trainer(train_key), [(day1, 2)])
        self.check_sortedset(datag.avg_by_day_for_training_and_trainer(train_key), [(day1, 62.5)])
        # - for training2 by trainer2, on day1: 0 answers
        #                              on day2: 2 answers (75+100)/2 = 87.5 avg
        train_key = datag.get_training_and_trainer_key(training2.pk, trainer2.pk)
        self.check_sortedset(datag.nb_by_day_for_training_and_trainer(train_key), [(day2, 2)])
        self.check_sortedset(datag.avg_by_day_for_training_and_trainer(train_key), [(day2, 87.5)])
        # Then check that all answers are saved
        self.check_sortedset(datag.answers_for_session(session1.pk), [
            (datag.get_answer_key(session1.pk, trainee1.pk, question1.pk), 0),
            (datag.get_answer_key(session1.pk, trainee2.pk, question1.pk), 25)
        ])
        self.check_sortedset(datag.answers_for_session(session2.pk), [
            (datag.get_answer_key(session2.pk, trainee1.pk, question1.pk), 50),
            (datag.get_answer_key(session2.pk, trainee3.pk, question1.pk), 75)
        ])
        self.check_sortedset(datag.answers_for_session(session4.pk), [
            (datag.get_answer_key(session4.pk, trainee1.pk, question1.pk), 25),
            (datag.get_answer_key(session4.pk, trainee2.pk, question1.pk), 50)
        ])
        self.check_sortedset(datag.answers_for_session(session3.pk), [
            (datag.get_answer_key(session3.pk, trainee2.pk, question2.pk), 100),
            (datag.get_answer_key(session3.pk, trainee3.pk, question2.pk), 0)
        ])
        self.check_sortedset(datag.answers_for_session(session5.pk), [
            (datag.get_answer_key(session5.pk, trainee1.pk, question2.pk), 75),
            (datag.get_answer_key(session5.pk, trainee3.pk, question2.pk), 100)
        ])

    def test_computing_missing_answers(self):
        """Test the ``update_single_choice_number_answers`` method of ``QuestionData``.

        All answers that are not present in redis should be used to update statistics.

        """
        def check_data(first_value, second_value):
            """Simple helper to check data in redis."""
            day = str(answer1.time.date())
            for data in (question_data, self.global_satisfaction):
                self.check_sortedset(data.answers_for_session(self.training_session.pk), [
                    (data.get_answer_key(
                        self.training_session.pk, self.trainee.pk, self.single_choice_question.pk
                    ), first_value),
                    (data.get_answer_key(
                        self.training_session.pk, self.trainees[1].pk,
                        self.single_choice_question.pk
                    ), second_value),
                ])
                self.check_sortedset(data.nb_by_day, [(day, 2)])
                self.check_sortedset(data.avg_by_day, [(day, (first_value+second_value)/2)])

        question_data = self.single_choice_question.redis_instance

        # Make the question accept percentages and make it count for global satisfaction
        self.prepare_question(self.single_choice_question, self.single_choice_choices)

        # Selected choice.
        choice1 = self.get_choice_having_value(self.single_choice_choices, 'value_number', 25)
        choice2 = self.get_choice_having_value(self.single_choice_choices, 'value_number', 75)

        # Create two answers, without auto-saving them in redis.
        with override_settings(SURVEYS_AUTO_COMPUTE_STATS=False):
            answer1 = self.save_answer(self.single_choice_question, [choice1])
            answer2 = self.save_answer(self.single_choice_question, [choice2],
                                       trainee=self.trainees[1])

        # Ensure we have nothing in redis
        for data in (question_data, self.global_satisfaction):
            self.check_sortedset(data.answers_for_session(self.training_session), [])

        # Now ask to save them
        updated, failed = self.single_choice_question.update_stats()

        # We should have 2 answers used to update stats
        self.assertEqual(updated, 2)
        self.assertEqual(failed, 0)

        # Ensure we have our statistics with our two answers computed
        check_data(25, 75)

        # Do it again: it should do nothing.
        updated, failed = self.single_choice_question.update_stats()
        self.assertEqual(updated, 0)
        self.assertEqual(failed, 0)
        # Ensure we have our statistics with our two answers still computed
        check_data(25, 75)

        # Do it again by forcing, stats should be updated.

        # We update an answer to verify its updated.
        choice3 = self.get_choice_having_value(self.single_choice_choices, 'value_number', 100)
        answer2.choices = [choice3]

        updated, failed = self.single_choice_question.update_stats(force_all=True)
        self.assertEqual(updated, 2)
        self.assertEqual(failed, 0)
        # Ensure we have our statistics with our two answers updated
        check_data(25, 100)

    def test_getting_stats(self):
        """Test the output of the ``get_stats`` method."""

        # Make the questions accept percentages and make it count for global satisfaction
        question1, choices1 = self.create_single_choice_question(self.admin_pool)
        self.prepare_question(question1, choices1)
        question2, choices2 = self.create_single_choice_question(self.admin_pool)
        self.prepare_question(question2, choices2)

        def add_answer(question, session, trainee, when, value):
            choice = self.get_choice_having_value(question.choices.all(), 'value_number', value)
            survey, __ = Survey.objects.get_or_create_for_training(session.training,
                                                                   session.trainer)

            self.save_answer(
                question=question,
                choices=[choice],
                survey=survey,
                training_session=session,
                trainee=trainee,
                answer_time=when,
            )

        # Make two answers on the first session on day 1
        date1 = datetime(2015, 6, 1, 12, 0, 0, tzinfo=utc)
        add_answer(question1, self.training_session, self.trainee, date1, 0)
        add_answer(question1, self.training_session, self.trainees[1], date1, 50)

        # Make answers on another session (same training and trainer) on day2
        date2 = datetime(2015, 6, 2, 12, 0, 0, tzinfo=utc)
        session2 = TrainingSessionFactory(training=self.training, trainer=self.trainer)
        session2.users.add(self.trainee, self.trainees[1])

        add_answer(question1, session2, self.trainee, date2, 50)
        add_answer(question1, session2, self.trainees[1], date2, 100)

        # Make answers on another *question*, on day2
        add_answer(question2, session2, self.trainee, date2, 25)
        add_answer(question2, session2, self.trainees[1], date2, 50)

        # Make two answers on day3, another session (same training, other trainer)
        trainer2 = TrainerFactory()
        date3 = datetime(2015, 6, 4, 12, 0, 0, tzinfo=utc)
        session3 = TrainingSessionFactory(training=self.training, trainer=trainer2)
        session3.users.add(self.trainee, self.trainees[1])

        add_answer(question1, session3, self.trainee, date3, 25)
        add_answer(question1, session3, self.trainees[1], date3, 75)

        # Now we can read stats
        def check_stats(source, axis, start_date, end_date, axis_values, expected_result):
            """Helper to check that stats are correct."""
            result = source.get_stats(axis, start_date, end_date, *axis_values)
            self.assertDictEqual(result, expected_result)

        # For readbility
        q1, q2 = question1.redis_instance, question2.redis_instance
        g = self.global_satisfaction
        day1, day2, day3 = date1.date(), date2.date(), date3.date()

        # Check session stats
        check_stats(q1, "session", None, None, [self.training_session.pk], {'nb': 2, 'avg': 25})
        check_stats(q2, "session", None, None, [self.training_session.pk], {'nb': 0, 'avg': 0})
        check_stats(g, "session", None, None, [self.training_session.pk], {'nb': 2, 'avg': 25})
        check_stats(q1, "session", None, None, [session2.pk], {'nb': 2, 'avg': 75})
        check_stats(q2, "session", None, None, [session2.pk], {'nb': 2, 'avg': 37.5})
        check_stats(g, "session", None, None, [session2.pk], {'nb': 4, 'avg': 56.25})
        check_stats(q1, "session", None, None, [session3.pk], {'nb': 2, 'avg': 50})
        check_stats(q2, "session", None, None, [session3.pk], {'nb': 0, 'avg': 0})
        check_stats(g, "session", None, None, [session3.pk], {'nb': 2, 'avg': 50})

        # Check stats for one day
        no_stats = {
            'nb': 0, 'avg': 0, 'start_date': '2015-06-01', 'end_date': '2015-06-01',
            'days': [('2015-06-01', 0, 0)]
        }
        expected = {
            'nb': 2, 'avg': 25, 'start_date': '2015-06-01', 'end_date': '2015-06-01',
            'days': [('2015-06-01', 2, 25)]
        }
        # Globally
        check_stats(q1, 'global', day1, day1, [], expected)
        check_stats(q2, 'global', day1, day1, [], no_stats)
        check_stats(g, 'global', day1, day1, [], expected)
        # By trainer
        check_stats(q1, 'trainer', day1, day1, [self.trainer.pk], expected)
        check_stats(q2, 'trainer', day1, day1, [self.trainer.pk], no_stats)
        check_stats(g, 'trainer', day1, day1, [self.trainer.pk], expected)
        check_stats(q1, 'trainer', day1, day1, [trainer2.pk], no_stats)
        check_stats(q2, 'trainer', day1, day1, [trainer2.pk], no_stats)
        check_stats(g, 'trainer', day1, day1, [trainer2.pk], no_stats)
        # By training
        check_stats(q1, 'training', day1, day1, [self.training.pk], expected)
        check_stats(q2, 'training', day1, day1, [self.training.pk], no_stats)
        check_stats(g, 'training', day1, day1, [self.training.pk], expected)
        # By training + trainer
        axis = 'training_and_trainer'
        check_stats(q1, axis, day1, day1, [self.training.pk, self.trainer.pk], expected)
        check_stats(q2, axis, day1, day1, [self.training.pk, self.trainer.pk], no_stats)
        check_stats(g, axis, day1, day1, [self.training.pk, self.trainer.pk], expected)
        check_stats(q1, axis, day1, day1, [self.training.pk, trainer2.pk], no_stats)
        check_stats(q2, axis, day1, day1, [self.training.pk, trainer2.pk], no_stats)
        check_stats(g, axis, day1, day1, [self.training.pk, trainer2.pk], no_stats)

        # Check stats for period
        no_stats = {
            'nb': 0, 'avg': 0, 'start_date': '2015-06-02', 'end_date': '2015-06-04',
            'days': [('2015-06-02', 0, 0), ('2015-06-03', 0, 0), ('2015-06-04', 0, 0)]
        }
        # Globally
        check_stats(q1, 'global', day2, day3, [], {
            'nb': 4, 'avg': 62.5, 'start_date': '2015-06-02', 'end_date': '2015-06-04',
            'days': [('2015-06-02', 2, 75), ('2015-06-03', 0, 0), ('2015-06-04', 2, 50)]
        })
        check_stats(q2, 'global', day2, day3, [], {
            'nb': 2, 'avg': 37.5, 'start_date': '2015-06-02', 'end_date': '2015-06-04',
            'days': [('2015-06-02', 2, 37.5), ('2015-06-03', 0, 0), ('2015-06-04', 0, 0)]
        })
        check_stats(g, 'global', day2, day3, [], {
            'nb': 6, 'avg': 325/6, 'start_date': '2015-06-02', 'end_date': '2015-06-04',
            'days': [('2015-06-02', 4, 56.25), ('2015-06-03', 0, 0), ('2015-06-04', 2, 50)]
        })
        # By trainer
        check_stats(q1, 'trainer', day2, day3, [self.trainer.pk], {
            'nb': 2, 'avg': 75, 'start_date': '2015-06-02', 'end_date': '2015-06-04',
            'days': [('2015-06-02', 2, 75), ('2015-06-03', 0, 0), ('2015-06-04', 0, 0)]
        })
        check_stats(q2, 'trainer', day2, day3, [self.trainer.pk], {
            'nb': 2, 'avg': 37.5, 'start_date': '2015-06-02', 'end_date': '2015-06-04',
            'days': [('2015-06-02', 2, 37.5), ('2015-06-03', 0, 0), ('2015-06-04', 0, 0)]
        })
        check_stats(g, 'trainer', day2, day3, [self.trainer.pk], {
            'nb': 4, 'avg': 56.25, 'start_date': '2015-06-02', 'end_date': '2015-06-04',
            'days': [('2015-06-02', 4, 56.25), ('2015-06-03', 0, 0), ('2015-06-04', 0, 0)]
        })
        check_stats(q1, 'trainer', day2, day3, [trainer2.pk], {
            'nb': 2, 'avg': 50, 'start_date': '2015-06-02', 'end_date': '2015-06-04',
            'days': [('2015-06-02', 0, 0), ('2015-06-03', 0, 0), ('2015-06-04', 2, 50)]
        })
        check_stats(q2, 'trainer', day2, day3, [trainer2.pk], no_stats)
        check_stats(g, 'trainer', day2, day3, [trainer2.pk], {
            'nb': 2, 'avg': 50, 'start_date': '2015-06-02', 'end_date': '2015-06-04',
            'days': [('2015-06-02', 0, 0), ('2015-06-03', 0, 0), ('2015-06-04', 2, 50)]
        })
        # By training
        check_stats(q1, 'training', day2, day3, [self.training.pk], {
            'nb': 4, 'avg': 62.5, 'start_date': '2015-06-02', 'end_date': '2015-06-04',
            'days': [('2015-06-02', 2, 75), ('2015-06-03', 0, 0), ('2015-06-04', 2, 50)]
        })
        check_stats(q2, 'training', day2, day3, [self.training.pk], {
            'nb': 2, 'avg': 37.5, 'start_date': '2015-06-02', 'end_date': '2015-06-04',
            'days': [('2015-06-02', 2, 37.5), ('2015-06-03', 0, 0), ('2015-06-04', 0, 0)]
        })
        check_stats(g, 'training', day2, day3, [self.training.pk], {
            'nb': 6, 'avg': 325/6, 'start_date': '2015-06-02', 'end_date': '2015-06-04',
            'days': [('2015-06-02', 4, 56.25), ('2015-06-03', 0, 0), ('2015-06-04', 2, 50)]
        })
        # By training + trainer
        axis = 'training_and_trainer'
        check_stats(q1, axis, day2, day3, [self.training.pk, self.trainer.pk], {
            'nb': 2, 'avg': 75, 'start_date': '2015-06-02', 'end_date': '2015-06-04',
            'days': [('2015-06-02', 2, 75), ('2015-06-03', 0, 0), ('2015-06-04', 0, 0)]
        })
        check_stats(q2, axis, day2, day3, [self.training.pk, self.trainer.pk], {
            'nb': 2, 'avg': 37.5, 'start_date': '2015-06-02', 'end_date': '2015-06-04',
            'days': [('2015-06-02', 2, 37.5), ('2015-06-03', 0, 0), ('2015-06-04', 0, 0)]
        })
        check_stats(g, axis, day2, day3, [self.training.pk, self.trainer.pk], {
            'nb': 4, 'avg': 56.25, 'start_date': '2015-06-02', 'end_date': '2015-06-04',
            'days': [('2015-06-02', 4, 56.25), ('2015-06-03', 0, 0), ('2015-06-04', 0, 0)]
        })
        check_stats(q1, axis, day2, day3, [self.training.pk, trainer2.pk], {
            'nb': 2, 'avg': 50, 'start_date': '2015-06-02', 'end_date': '2015-06-04',
            'days': [('2015-06-02', 0, 0), ('2015-06-03', 0, 0), ('2015-06-04', 2, 50)]
        })
        check_stats(q2, axis, day2, day3, [self.training.pk, trainer2.pk], no_stats)
        check_stats(g, axis, day2, day3, [self.training.pk, trainer2.pk], {
            'nb': 2, 'avg': 50, 'start_date': '2015-06-02', 'end_date': '2015-06-04',
            'days': [('2015-06-02', 0, 0), ('2015-06-03', 0, 0), ('2015-06-04', 2, 50)]
        })

        # Check stats without dates
        no_stats = {
            'nb': 0, 'avg': 0, 'start_date': None, 'end_date': None,
            'days': []
        }
        # Globally
        check_stats(q1, 'global', None, None, [], {
            'nb': 6, 'avg': 50, 'start_date': '2015-06-01', 'end_date': '2015-06-04',
            'days': [('2015-06-01', 2, 25), ('2015-06-02', 2, 75), ('2015-06-03', 0, 0), ('2015-06-04', 2, 50)]
        })
        check_stats(q2, 'global', None, None, [], {
            'nb': 2, 'avg': 37.5, 'start_date': '2015-06-02', 'end_date': '2015-06-02',
            'days': [('2015-06-02', 2, 37.5)]
        })
        check_stats(g, 'global', None, None, [], {
            'nb': 8, 'avg': 375/8, 'start_date': '2015-06-01', 'end_date': '2015-06-04',
            'days': [('2015-06-01', 2, 25), ('2015-06-02', 4, 56.25), ('2015-06-03', 0, 0), ('2015-06-04', 2, 50)]
        })
        # By trainer
        check_stats(q1, 'trainer', None, None, [self.trainer.pk], {
            'nb': 4, 'avg': 50, 'start_date': '2015-06-01', 'end_date': '2015-06-02',
            'days': [('2015-06-01', 2, 25), ('2015-06-02', 2, 75)]
        })
        check_stats(q2, 'trainer', None, None, [self.trainer.pk], {
            'nb': 2, 'avg': 37.5, 'start_date': '2015-06-02', 'end_date': '2015-06-02',
            'days': [('2015-06-02', 2, 37.5)]
        })
        check_stats(g, 'trainer', None, None, [self.trainer.pk], {
            'nb': 6, 'avg': 275/6, 'start_date': '2015-06-01', 'end_date': '2015-06-02',
            'days': [('2015-06-01', 2, 25), ('2015-06-02', 4, 56.25)]
        })
        check_stats(q1, 'trainer', None, None, [trainer2.pk], {
            'nb': 2, 'avg': 50, 'start_date': '2015-06-04', 'end_date': '2015-06-04',
            'days': [('2015-06-04', 2, 50)]
        })
        check_stats(q2, 'trainer', None, None, [trainer2.pk], no_stats)
        check_stats(g, 'trainer', None, None, [trainer2.pk], {
            'nb': 2, 'avg': 50, 'start_date': '2015-06-04', 'end_date': '2015-06-04',
            'days': [('2015-06-04', 2, 50)]
        })
        # By training
        check_stats(q1, 'training', None, None, [self.training.pk], {
            'nb': 6, 'avg': 50, 'start_date': '2015-06-01', 'end_date': '2015-06-04',
            'days': [('2015-06-01', 2, 25), ('2015-06-02', 2, 75), ('2015-06-03', 0, 0), ('2015-06-04', 2, 50)]
        })
        check_stats(q2, 'training', None, None, [self.training.pk], {
            'nb': 2, 'avg': 37.5, 'start_date': '2015-06-02', 'end_date': '2015-06-02',
            'days': [('2015-06-02', 2, 37.5)]
        })
        check_stats(g, 'training', None, None, [self.training.pk], {
            'nb': 8, 'avg': 375/8, 'start_date': '2015-06-01', 'end_date': '2015-06-04',
            'days': [('2015-06-01', 2, 25), ('2015-06-02', 4, 56.25), ('2015-06-03', 0, 0), ('2015-06-04', 2, 50)]
        })
        # By training + trainer
        axis = 'training_and_trainer'
        check_stats(q1, axis, None, None, [self.training.pk, self.trainer.pk], {
            'nb': 4, 'avg': 50, 'start_date': '2015-06-01', 'end_date': '2015-06-02',
            'days': [('2015-06-01', 2, 25), ('2015-06-02', 2, 75)]
        })
        check_stats(q2, axis, None, None, [self.training.pk, self.trainer.pk], {
            'nb': 2, 'avg': 37.5, 'start_date': '2015-06-02', 'end_date': '2015-06-02',
            'days': [('2015-06-02', 2, 37.5)]
        })
        check_stats(g, axis, None, None, [self.training.pk, self.trainer.pk], {
            'nb': 6, 'avg': 275/6, 'start_date': '2015-06-01', 'end_date': '2015-06-02',
            'days': [('2015-06-01', 2, 25), ('2015-06-02', 4, 56.25)]
        })
        check_stats(q1, axis, None, None, [self.training.pk, trainer2.pk], {
            'nb': 2, 'avg': 50, 'start_date': '2015-06-04', 'end_date': '2015-06-04',
            'days': [('2015-06-04', 2, 50)]
        })
        check_stats(q2, axis, None, None, [self.training.pk, trainer2.pk], no_stats)
        check_stats(g, axis, None, None, [self.training.pk, trainer2.pk], {
            'nb': 2, 'avg': 50, 'start_date': '2015-06-04', 'end_date': '2015-06-04',
            'days': [('2015-06-04', 2, 50)]
        })
