"""This module contains the test cases for the evaluations-related api views"""

import json

from django.core.urlresolvers import reverse
from django.db.models import Avg

from mtp.apps.api.tests.test_cases import APITestCase
from mtp.apps.evaluations.factories import RatingFactory
from mtp.apps.evaluations.models import Criterion, Rating as EvaluationRating
from mtp.apps.evaluations.tests.test_cases import EvaluationTestCase
from mtp.apps.common.tests.waffle_test_utils import override_switch


class EvaluationTrainingContentTestCase(EvaluationTestCase):
    """Test case for the evaluations-related contents of the api route"""

    @classmethod
    def setUpClass(cls):
        """Build the session content url"""

        super().setUpClass()

        cls.training_session_content_url = reverse(
            'api:session-content',
            kwargs={'session_uuid': cls.training_session.pk}
        )

    def get_evaluation_json(self):
        """Shortcut method to get the json content of the evaluation

        Returns
        -------
        evaluation_content: dict
            The content of the evaluation, as decoded json

        """

        training_session_content = json.loads(
            self.client.get(self.training_session_content_url).content.decode('utf-8')
        )

        return training_session_content['training']['items']['spider_graph']

    @override_switch('enable_evaluation', True)
    def test_evaluations_permissions(self):
        """Test that the response have correct evaluations-related permissions"""

        # Get the first evaluation permissions, we don't want to test all the output
        evaluation_permissions = self.get_evaluation_json()[0]['permissions']

        # Build the expected json
        expected_json = {
            'candidate': self.evaluation.candidates_group_names,
            'moderator': self.evaluation.moderators_group_names,
            'evaluator': self.evaluation.evaluators_group_names,
            'auto_evaluator': self.evaluation.auto_evaluators_group_names,
            'observer': self.evaluation.observers_group_names,
            'auto_observer': self.evaluation.auto_observers_group_names
        }

        for role, group_names in evaluation_permissions.items():
            with self.subTest(role=role):
                self.assertEqual(
                    group_names,
                    list(expected_json[role])
                )

    @override_switch('enable_evaluation', True)
    def test_evaluations_contents(self):
        """Test that the response have correct evaluations-related contents"""

        # Get the first evaluation settings, we don't want to test all the output
        evaluation_settings = self.get_evaluation_json()[0]['module_settings']

        # Build the expected shi*** json :)
        expected_json = []
        for exercise in self.evaluation.exercises.non_deleted():
            json_exercises = {
                'id': exercise.id,
                'title': exercise.name,
                'form': []
            }
            for criterion in exercise.criteria.non_deleted():
                if criterion.criterion_type == criterion.CRITERION_TYPES.QUANTITATIVE:
                    json_criterion = {
                        'id': criterion.id,
                        'type': 'numeric',
                        'label': criterion.description,
                        'min_value': exercise.start_range,
                        'max_value': exercise.end_range,
                        'default_value': exercise.end_range,
                        'step': exercise.step
                    }
                else:
                    json_criterion = {
                        'id': criterion.id,
                        'type': 'text',
                        'label': criterion.description
                    }

                json_exercises['form'].append(json_criterion)

            expected_json.append(json_exercises)

        self.assertEqual(
            evaluation_settings[1]['info']['exercices'],
            expected_json
        )


class EvaluationRatingsSaveTestCase(EvaluationTestCase):
    """Test case for the ``evaluation_save_criteria_num`` api route"""

    def setUp(self):

        self.exercise = self.evaluation.exercises.first()
        self.criteria = self.exercise.criteria.all()

        self.post_data = {
            'exercice_id': self.exercise.id,
            'session_id': self.training_session.id,
            'trainer_id': self.trainer.id,
            'user_id': self.trainee.id,
        }

    def _check_rating_response(self, response, field_name, expected_values):

        self.assertEqual(response.status_code, 200)
        ratings = EvaluationRating.objects.filter(
            judge_id=self.trainer.id,
            trainee_id=self.trainee.id,
            session_id=self.training_session.id,
            criterion__exercise_id=self.exercise.id,
        ).values_list(field_name, flat=True)
        self.assertSetEqual(set(ratings), expected_values)

    def test_record_numerical_ratings(self):

        criteria = self.criteria.quantitative()
        self.post_data.update({
            'json': json.dumps({
                'data': [
                    {'id': criteria[0].id, 'type': 'numeric', 'value': 1},
                    {'id': criteria[1].id, 'type': 'numeric', 'value': 2},
                    {'id': criteria[2].id, 'type': 'numeric', 'value': 3}
                ]
            })
        })

        url = reverse('api:evaluation_save_criteria_num', kwargs={
            'user_uuid': self.trainee.id
        })
        response = self.client.post(url, self.post_data)
        self._check_rating_response(response, 'score', {1, 2, 3})

    def test_record_textual_ratings(self):

        criteria = self.criteria.qualitative()
        self.post_data.update({
            'json': json.dumps({
                'data': [
                    {'id': criteria[0].id, 'type': 'text', 'items': [
                        {'id': 'Whatever', 'text': 'One'}
                    ]},
                    {'id': criteria[1].id, 'type': 'text', 'items': [
                        {'id': 'I-dont-care', 'text': 'Two'}
                    ]},
                    {'id': criteria[2].id, 'type': 'text', 'items': [
                        {'id': 'This-is-bullshit-anyway', 'text': 'Three'}
                    ]}
                ]
            })
        })

        url = reverse('api:evaluation_save_criteria_text', kwargs={
            'user_uuid': self.trainee.id
        })
        response = self.client.post(url, self.post_data)
        self._check_rating_response(response, 'text', {'One', 'Two', 'Three'})


class EvaluationGetResultsApiTestCase(APITestCase, EvaluationTestCase):
    """Test case for the api route to display the results of an evaluation"""

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

        super().setUpTestData()

        cls.result_url = reverse('api:evaluation_get_result', kwargs={
            'user_uuid': cls.trainee.pk,
            'eval_uuid': cls.evaluation.pk
        })

        cls.result_all_url = reverse('api:evaluation_get_result_all', kwargs={
            'eval_uuid': cls.evaluation.pk
        })

        # Create some ratings for our trainee
        cls.first_quantitative_criterion = Criterion.objects.quantitative().first()

        cls.first_qualitative_criterion = Criterion.objects.qualitative().first()

        cls.all_text_criteria = Criterion.objects.qualitative()
        cls.all_numeric_criteria = Criterion.objects.quantitative()

        # Two for the first trainee...
        RatingFactory(
            trainee=cls.trainee, session=cls.training_session,
            score=1, criterion=cls.first_quantitative_criterion
        )
        RatingFactory(
            trainee=cls.trainee, session=cls.training_session,
            text='Test', criterion=cls.first_qualitative_criterion
        )

        # And two extra on another trainee
        RatingFactory(
            trainee=cls.trainees[1], session=cls.training_session,
            score=1, criterion=cls.first_quantitative_criterion
        )
        RatingFactory(
            trainee=cls.trainees[1], session=cls.training_session,
            text='Test', criterion=cls.first_qualitative_criterion
        )

    def test_evaluation_get_result_should_work(self):
        """Test the route should work when calling with a single trainee"""

        status_code, decoded_result = self.get_api_response(
            self.result_url,
            'get',
            {'session_id': self.training_session.pk}
        )

        self.assertEqual(status_code, 200)

        qualitative_criteria_results = [
            {
                'id': criterion.pk,
                'label': criterion.description,
                'items': [
                    {
                        'id': rating.pk,
                        'text': rating.text
                    }
                    for rating in criterion.ratings.filter(trainee=self.trainee)
                ],
                'type': 'text',
                'order': criterion.sort_order
            }
            for criterion in self.all_text_criteria
        ]

        self.assertCountEqual(
            decoded_result['spider_graph']['criterias'],
            qualitative_criteria_results
        )

        quantitative_criteria_results = [
            {
                'name': exercise.name,
                'start_range': exercise.start_range,
                'end_range': exercise.end_range,
                'step': exercise.step,
                'colors': ['OxFF0000', 'Ox00FF00', 'Ox0000FF', 'OxFF00FF', 'OxFFFF00', 'Ox00FFFF'],
                'attributes': [
                    {
                        criterion.description: [
                            exercise.end_range,
                            (
                                criterion.ratings.filter(trainee=self.trainee).aggregate(
                                    Avg('score')
                                )['score__avg']
                            ) or exercise.start_range,
                        ]
                    }
                    for criterion in exercise.criteria.quantitative()
                ]
            }
            for exercise in self.evaluation.exercises.all()
        ]

        self.assertCountEqual(
            decoded_result['spider_graph']['graphs'],
            quantitative_criteria_results
        )

    def test_evaluation_get_result_all_should_work(self):
        """Test the route should work without a trainee parameter"""

        status_code, decoded_result = self.get_api_response(
            self.result_all_url,
            'get',
            {'session_id': self.training_session.pk}
        )

        self.assertEqual(status_code, 200)

        qualitative_criteria_results = [
            {
                'id': criterion.pk,
                'label': criterion.description,
                'items': [
                    {
                        'id': rating.pk,
                        'text': rating.text
                    }
                    for rating in criterion.ratings.all()
                ],
                'type': 'text',
                'order': criterion.sort_order
            }
            for criterion in self.all_text_criteria
        ]

        self.assertCountEqual(
            decoded_result['spider_graph']['criterias'],
            qualitative_criteria_results
        )

        quantitative_criteria_results = [
            {
                'name': exercise.name,
                'start_range': exercise.start_range,
                'end_range': exercise.end_range,
                'step': exercise.step,
                'colors': ['OxFF0000', 'Ox00FF00', 'Ox0000FF', 'OxFF00FF', 'OxFFFF00', 'Ox00FFFF'],
                'attributes': [
                    {
                        criterion.description: [
                            exercise.end_range,
                            (
                                criterion.ratings.aggregate(
                                    Avg('score')
                                )['score__avg']
                            ) or exercise.start_range,
                        ]
                    }
                    for criterion in exercise.criteria.quantitative()
                ]
            }
            for exercise in self.evaluation.exercises.all()
        ]

        self.assertCountEqual(
            decoded_result['spider_graph']['graphs'],
            quantitative_criteria_results
        )
