"""This module holds the test for the views of the ``evaluations`` app."""

from django.core.urlresolvers import reverse

from ..models import Criterion, Exercise

from .test_cases import EvaluationTestCase


class EvaluationEditViewsTestCase(EvaluationTestCase):
    """Test the edit views of the evaluation"""

    @classmethod
    def setUpTestData(cls):
        """Setup the test data"""

        super().setUpTestData()

        cls.urls_to_test = [
            cls.evaluation.get_absolute_url(),
            cls.evaluation.get_detail_url(),
            cls.evaluation.get_exercise_create_url(),
            cls.evaluation.get_exercises_sort_url(),
            cls.evaluation.get_training_create_url(training=cls.training)
        ]

        with cls.training.override_version_state(cls.training.VERSION_STATES.DRAFT):
            cls.training.version_state = cls.training.VERSION_STATES.DRAFT
            cls.training.save()

    def test_all_routes_should_work_as_the_owner(self):
        """Test the response_code of all the evaluations routes, as the owner"""

        self.login_as_trainer()

        for url in self.urls_to_test:
            with self.subTest(url=url):
                response = self.client.get(url)
                self.assertEqual(response.status_code, 200)

    def test_all_routes_should_be_404_as_the_non_owner(self):
        """Test the response_code of all the evaluations routes, as a non-owner"""

        self.login_as_admin()

        for url in self.urls_to_test:
            with self.subTest(url=url):
                response = self.client.get(url)
                self.assertEqual(response.status_code, 404, url)


class EvaluationExerciseEditViewTestCase(EvaluationTestCase):
    """Test case for the views related to the ``Exercise`` model"""

    @classmethod
    def setUpTestData(cls):
        """Build the data expected by the ``EvaluationExercisesCriteriaFormSet``"""

        super().setUpTestData()

        # We cannot edit the workshop if the training is not in ``DRAFT`` state
        cls.training.version_state = cls.training.VERSION_STATES.DRAFT
        cls.training.save()

        cls.create_url = cls.evaluation.get_exercise_create_url()

        # Data to create a new exercise
        cls.formset_create_data = {
            'name': 'New exercise',
            'description': 'Exercise description',
            'start_range': 1,
            'end_range': 5,
            'step': 1,
            # criteria meta-data
            'criteria-TOTAL_FORMS': 0,  # we have 0 criteria
            'criteria-INITIAL_FORMS': 0,
            'criteria-MIN_NUM_FORMS': 0,
            'criteria-MAX_NUM_FORMS': 1000  # Django add this per default
        }

        cls.exercise = cls.exercises[0]

        # Data to update an existing exercise
        cls.formset_update_data = {
            # exercise
            'name': cls.exercise.name,
            'description': cls.exercise.description,
            'start_range': cls.exercise.start_range,
            'end_range': cls.exercise.end_range,
            'step': cls.exercise.step,
            # criteria meta-data
            'criteria-TOTAL_FORMS': cls.exercise.criteria.count(),
            'criteria-INITIAL_FORMS': cls.exercise.criteria.count(),
            'criteria-MIN_NUM_FORMS': 0,
            'criteria-MAX_NUM_FORMS': 1000  # Django add this per default
        }

        # criteria data
        for index, criterion in enumerate(cls.exercise.criteria.all()):
            cls.formset_update_data.update({
                'criteria-%s-id' % index: criterion.pk,
                'criteria-%s-order' % index: index + 1,
                'criteria-%s-description' % index: criterion.description,
                'criteria-%s-criterion_type' % index: criterion.criterion_type,
                'criteria-%s-question' % index: cls.exercise.pk,
            })

    def test_create_view_should_create_a_new_exercise(self):
        """Test that the create view should allow us to create a new exercise"""

        self.login_as_trainer()

        response = self.client.post(self.create_url, self.formset_create_data)
        self.assertTrue(response.status_code, 200)
        self.assertTrue(Exercise.objects.filter(name='New exercise').exists())

    def test_create_view_should_create_a_new_exercise_and_criteria(self):
        """Test that the create view should allow us to create a new exercise and criteria"""

        self.login_as_trainer()

        self.formset_create_data['criteria-TOTAL_FORMS'] = 1

        # Data for the criterion
        self.formset_create_data.update({
            'criteria-0-order': 0,
            'criteria-0-description': 'test description',
            'criteria-0-criterion_type': Criterion.CRITERION_TYPES.QUALITATIVE
        })

        response = self.client.post(self.create_url, self.formset_create_data)
        self.assertTrue(response.status_code, 200)

        # Our exercise was created
        exercise = Exercise.objects.get(name='New exercise')

        # This exercise got one criterion, the one we created
        self.assertEqual(exercise.criteria.count(), 1)
        criterion = exercise.criteria.first()
        self.assertEqual(criterion.description, 'test description')
        self.assertEqual(criterion.criterion_type, Criterion.CRITERION_TYPES.QUALITATIVE)

    def test_update_view_should_update_the_exercise_and_the_criteria(self):
        """Test that the update view should allow us to update the exercise and the criteria"""

        self.login_as_trainer()

        # We update the name of the exercise
        self.formset_update_data['name'] = 'Updated name'
        # And the description, type of the first criterion
        self.formset_update_data['criteria-0-description'] = 'Updated description'
        self.formset_update_data[
            'criteria-0-criterion_type'
        ] = Criterion.CRITERION_TYPES.QUALITATIVE

        response = self.client.post(
            self.exercise.get_edit_url(),
            self.formset_update_data
        )

        # We should be redirected to the details of the evaluation
        self.assertRedirects(response, self.evaluation.get_detail_url())

        self.exercise.refresh_from_db()
        self.assertEqual(self.exercise.name, 'Updated name')

        first_criterion = self.exercise.criteria.first()
        self.assertEqual(first_criterion.description, 'Updated description')
        self.assertEqual(first_criterion.criterion_type, Criterion.CRITERION_TYPES.QUALITATIVE)

    def test_update_view_should_allow_adding_new_criterion(self):
        """Test that the update view should allow us to add a new criterion"""

        self.login_as_trainer()

        # At first, we got twelve criteria on the exercise
        self.assertEqual(self.exercise.criteria.count(), 12)

        formset_update_data = self.formset_update_data.copy()

        formset_update_data['criteria-TOTAL_FORMS'] = 13
        formset_update_data.update({
            'criteria-12-order': 0,
            'criteria-12-description': 'test description',
            'criteria-12-criterion_type': Criterion.CRITERION_TYPES.QUALITATIVE,
            'criteria-12-exercise': self.exercise.pk,
        })

        response = self.client.post(
            self.exercise.get_edit_url(),
            formset_update_data
        )

        self.assertRedirects(response, self.evaluation.get_detail_url())

        # We should now have 13 criteria
        self.assertEqual(self.exercise.criteria.count(), 13)

    def test_update_view_should_allow_soft_delete_criterion(self):
        """Test that the update view should allow us to soft-delete a criterion"""

        self.login_as_trainer()

        # We got two non-deleted criteria
        self.assertEqual(self.exercise.criteria.non_deleted().count(), 12)

        # Add post data to set the first criteria as deleted
        self.formset_update_data['criteria-0-DELETE'] = True

        response = self.client.post(
            self.exercise.get_edit_url(),
            self.formset_update_data
        )

        self.assertRedirects(response, self.evaluation.get_detail_url())

        # We got only one non-deleted criterion
        self.assertEqual(self.exercise.criteria.non_deleted().count(), 11)

        # But still a total of 12 criteria
        self.assertEqual(self.exercise.criteria.count(), 12)

    def test_exercises_delete_view_should_allow_us_to_soft_delete_multiple_exercises(self):
        """Test that the delete view should allow us to delete multiple exercises"""

        # At first, we got 3 non-deleted exercises, and 3 exercises in total
        self.assertEqual(self.evaluation.exercises.non_deleted().count(), 3)
        self.assertEqual(self.evaluation.exercises.count(), 3)

        self.login_as_trainer()

        # We delete the first exercise
        response = self.client.post(
            self.evaluation.get_exercises_delete_url(),
            {
                'exercises': [self.evaluation.exercises.first().id]
            }
        )

        # We should be redirected to the evaluation detail url
        self.assertRedirects(response, self.evaluation.get_absolute_url())

        # We should have 2 non-deleted and 3 exercises in total
        self.assertEqual(self.evaluation.exercises.non_deleted().count(), 2)
        self.assertEqual(self.evaluation.exercises.count(), 3)

        # Now, we delete two more exercises
        response = self.client.post(
            self.evaluation.get_exercises_delete_url(),
            {
                'exercises': [
                    self.evaluation.exercises.all()[1].id,
                    self.evaluation.exercises.all()[2].id
                ]
            }
        )

        # We should be redirected to the workshop detail url
        self.assertRedirects(response, self.evaluation.get_absolute_url())

        # We should have 0 step non-deleted and 3 exercises in total
        self.assertEqual(self.evaluation.exercises.non_deleted().count(), 0)
        self.assertEqual(self.evaluation.exercises.count(), 3)

    def test_formset_should_not_display_soft_deleted_criteria(self):
        """Test that formset should not contains the soft-deleted criteria"""

        self.login_as_trainer()

        # At first, we should have all our initial criteria displayed in the form
        response = self.client.get(self.exercise.get_edit_url())

        # We got 13 forms displayed for the criteria (12 + one empty)
        self.assertEqual(
            len(response.context['criteria_formset']),
            13
        )

        # Soft delete the first criterion
        self.exercise.criteria.first().remove()

        # We should get only 12 forms now (11 + one empty)
        response = self.client.get(self.exercise.get_edit_url())
        self.assertEqual(
            len(response.context['criteria_formset']),
            12
        )


class XLSEvaluationImportViewTestCase(EvaluationTestCase):
    """Test the ``XLSEvaluationImportView`` view."""

    def test_template_example_file(self):
        """Test the ``template_example_file`` variable exists."""

        self.login_as_trainer()
        response = self.client.get(reverse('evaluations:evaluation-import-xls', kwargs={
            'training_uuid': self.training.id,
        }))

        self.assertIn('template_example_file', response.context)
        self.assertIsNotNone(response.context['template_example_file'])
