"""Base test cases for testing surveys."""

from mtp.apps.common.tests.test_cases import (
    MTPTestCase,
    MTPTransactionTestCase,
    patch_timezone_now_field,
)

from mtp.apps.surveys.factories import ChoiceFactory, PoolFactory, QuestionFactory
from mtp.apps.surveys.models import Answer, Question
from mtp.apps.surveys.redis_models import GlobalSatisfaction, QuestionData


class BaseSurveyTestCase:
    """Base test case to tests surveys..

    It provides some default test data useful in a lot of tests.

    Attributes
    ----------
    admin_pool : surveys.models.Pool
        A ``Pool` owned by ``self.admin``.
    trainer_pool : surveys.models.Pool
        A ``Pool` owned by ``self.trainer``.
    single_choice_question : surveys.models.Question
        A ``Question`` on ``self.trainer_poool``, with question type ``SINGLE_CHOICE``
        with 5 choices.
    multi_choice_question : surveys.models.Question
        A ``Question`` on ``self.trainer_poool``, with question type ``MULTIPLE_CHOICES``
        with 5 choices.
    open_question : surveys.models.Question
        A ``Question`` on ``self.trainer_poool``, with question type ``OPEN.``

    """

    @classmethod
    def create_single_choice_question(cls, pool, **kwargs):
        """Create a ``SINGLE_CHOICE`` question with 5 choices.

        The question is set with ``NUMBER`` as value type.

        Parameters
        ----------
        pool : surveys.models.Pool
            ``Pool`` the question will belong too.
        kwargs : dict
            kwargs are passed to the ``QuestionFactory``.

        Returns
        -------
        tuple (Question, [Choice])
            A tuple with the created question as first entry, and the list of choices attached to
            this question as second entry.

        """

        question = QuestionFactory(
            question_type=Question.QUESTION_TYPES.SINGLE_CHOICE,
            value_type=Question.VALUE_TYPES.NUMBER,
            pool=pool,
            **kwargs
        )
        choices = ChoiceFactory.create_batch(5, question=question)
        return question, choices

    @classmethod
    def create_multi_choices_question(cls, pool, **kwargs):
        """Create a ``MULTIPLE_CHOICES`` question with 5 choices.

        The question is set with ``NUMBER`` as value type.

        Parameters
        ----------
        pool : surveys.models.Pool
            ``Pool`` the question will belong too.
        kwargs : dict
            kwargs are passed to the ``QuestionFactory``.

        Returns
        -------
        tuple (Question, [Choice])
            A tuple with the created question as first entry, and the list of choices attached to
            this question as second entry.

        """

        question = QuestionFactory(
            question_type=Question.QUESTION_TYPES.MULTIPLE_CHOICES,
            value_type=Question.VALUE_TYPES.NUMBER,
            pool=pool,
            **kwargs
        )
        choices = ChoiceFactory.create_batch(5, question=question)
        return question, choices

    @classmethod
    def create_open_question(cls, pool, **kwargs):
        """Create a ``OPEN`` question.

        Parameters
        ----------
        pool : surveys.models.Pool
            ``Pool`` the question will belong too.
        kwargs : dict
            kwargs are passed to the ``QuestionFactory``.

        Returns
        -------
        Question
            The created question

        """

        question = QuestionFactory(
            question_type=Question.QUESTION_TYPES.OPEN,
            value_type=Question.VALUE_TYPES.NONE,
            pool=pool,
            **kwargs
        )
        return question

    @classmethod
    def set_choices_values(cls, choices, field, values):
        """Set some values to a list of choices.

        Each choice in ``choices`` will be given a value from ``values``. Both list will be
        walked simultanously.

        Parameters
        ----------
        choices : list
            List of ``Choice`` instances to update.
        field : string
            Name of the field to update for each choice. Should be `value_text` or `value_number`.
        values : list
            List of values to set for each choice.

        """

        for choice, value in zip(choices, values):
            setattr(choice, field, value)
            choice.save(update_fields=[field])

    @classmethod
    def get_choice_having_value(cls, choices, field, value):
        """Return the (first) choice having the given value.

        Parameters
        ----------
        choices : list
            List of ``Choice`` where the value will be searched.
        field : string
            Name of the field in which to search for each choice. Should be `value_text` or
            `value_number`.
        value : ?
            The value to search for.

        Returns
        -------
        Choice
            The first ``Choice`` from ``choices`` that have a matching value.

        Raises
        ------
        IndexError
            If the value is not found in the list of choices.

        """

        return [c for c in choices if getattr(c, field) == value][0]

    @classmethod
    def save_answer(cls, question, choices, **kwargs):
        """Add a answer to the given question with all the given choices.

        Except ``question`` and ``choices``, all arguments to the ``save_answer`` method
        of the ``Answer`` manager could be passed in ``kwargs``. Else, data from ``cls``
        will be used.

        """
        def do_save():
            """Actually save the answer object."""
            return Answer.objects.save_answer(
                session=kwargs.get('training_session') or cls.training_session,
                survey=kwargs.get('survey') or cls.survey,
                trainee=kwargs.get('trainee') or cls.trainee,
                question_pk=question.pk,
                choice_pks=[choice.pk for choice in choices],
                text=kwargs.get('text') or '',
            )

        # If an answer time is given, we don't want to use the auto-filled one from the answer
        # object, so we mock it to be set to the correct value.
        answer_time = kwargs.get('answer_time')
        if answer_time:
            with patch_timezone_now_field(Answer, 'time', answer_time):
                return do_save()
        else:
            return do_save()

    def check_sortedset(self, field, expected_values):
        """Assert that the content of the given zset field matches the expected values.

        Parameters
        ----------
        field : limpyd.fields.SortedSetField
            The ``SortedSetField`` for which we want to retrieve the content.
        expected_values : list(tuple)
            The values to use to compare with the data from the sorted set.
            It's a list of tuples, each tuple having the key and the score.

        Notes
        -----
        Values are sorted before comparison.

        """

        stored_value = sorted(field.zrange(0, -1, withscores=True))
        self.assertListEqual(stored_value, sorted(expected_values))

    @classmethod
    def setUpTestData(cls):
        """Create some data to use in surveys tests."""

        super().setUpTestData()

        cls.admin_pool = PoolFactory(author=cls.admin)
        cls.trainer_pool = PoolFactory(author=cls.trainer)

        cls.single_choice_question, cls.single_choice_choices = \
            cls.create_single_choice_question(pool=cls.trainer_pool)
        cls.multi_choices_question, cls.multi_choices_choices = \
            cls.create_multi_choices_question(pool=cls.trainer_pool)
        cls.open_question = cls.create_open_question(pool=cls.trainer_pool)

    def setUp(self):
        """Clear redis data before each test."""

        super().setUp()

        # Clear all question data from redis model
        for entry in QuestionData.collection().instances():
            entry.delete()

        # Clear all saved global satisfaction
        GlobalSatisfaction.get_instance().delete()

        # Easy access to global satisfaction
        self.global_satisfaction = GlobalSatisfaction.get_instance()


class SurveyTestCase(BaseSurveyTestCase, MTPTestCase):
    """A test case based on ``BaseSurveyTestCase`` and ``MTPTestCase``.

    Should be used in most cases.

    """


class SurveyTransactionTestCase(BaseSurveyTestCase, MTPTransactionTestCase):
    """A test case based on ``BaseSurveyTestCase`` and ``MTPTransactionTestCase``.

    Use this with parsimony, ``TransactionTestCase`` is a *lot* slower that default ``TestCase``.

    """
