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

import json

from django.core.urlresolvers import reverse

from mtp.apps.roleplays.models import Rating
from mtp.apps.roleplays.tests.test_cases import RoleplayTestCase


class RoleplayRatingListApiViewTestCase(RoleplayTestCase):
    """Tests for the API roleplay ratings save"""

    @classmethod
    def setUpTestData(cls):
        super().setUpTestData()

    def get_rating_data(self):
        """Shortcut method to build a data set to test with"""

        ratings_data = []

        for criterion in self.roleplay.steps.first().criteria.quantitative():
            ratings_data.append({
                'criterion': criterion.pk,
                'score': 2,
                'text': ''
            })

        for criterion in self.roleplay.steps.first().criteria.boolean():
            ratings_data.append({
                'criterion': criterion.pk,
                'score': 100,
                'text': ''
            })

        for criterion in self.roleplay.steps.first().criteria.qualitative():
            ratings_data.append({
                'criterion': criterion.pk,
                'text': 'test',
                'score': None
            })

        return ratings_data

    def test_roleplay_detail_api_response(self):
        """Test the ``roleplay_detail`` API endpoint"""

        url = reverse('api:roleplay-detail', kwargs={
            'roleplay_uuid': self.roleplay.id
        })

        response = self.client.get(url)
        self.assertEqual(response.status_code, 200)

        roleplay_data = json.loads(response.content.decode('utf-8'))
        self.assertEqual(roleplay_data['name'], self.roleplay.name)

    def test_soft_deleted_roleplay_step_and_criterion_is_not_in_response(self):
        """Test that a soft-deleted roleplay step and criterion should not be in the response"""

        url = reverse('api:roleplay-detail', kwargs={
            'roleplay_uuid': self.roleplay.id
        })

        def get_roleplay_data():
            """Shortcut method to call the roleplay detail endpoint

            Returns
            -------
            json
                The decoded json response

            """
            response = self.client.get(url)

            return json.loads(response.content.decode('utf-8'))

        # We got 36 criteria in the first step
        self.assertEqual(len(get_roleplay_data()['steps'][0]['criteria']), 36)

        # Soft delete the first criterion
        with self.training.override_version_state(self.training.VERSION_STATES.DRAFT):
            self.roleplay.steps.first().criteria.first().remove()

        # We now get only 35 criteria
        self.assertEqual(len(get_roleplay_data()['steps'][0]['criteria']), 35)

        # We got 3 steps in the response
        self.assertEqual(len(get_roleplay_data()['steps']), 3)

        # Soft delete the first roleplay step
        with self.training.override_version_state(self.training.VERSION_STATES.DRAFT):
            self.roleplay.steps.first().remove()

        # We now get only 2 steps
        self.assertEqual(len(get_roleplay_data()['steps']), 2)

    def post_roleplay_results(self, ratings_data):
        """Shortcut method to post some results to the
        ``roleplay_ratings_list`` API endpoint."""

        url = reverse('api:roleplay-ratings-list', kwargs={
            'roleplay_uuid': self.roleplay.id,
            'user_uuid': self.trainee.id,
            'session_uuid': self.training_session.id
        })
        response = self.client.post(url, {'ratings': json.dumps({'ratings': ratings_data})})
        return response.status_code, json.loads(response.content.decode('utf-8'))

    def get_roleplay_ratings(self):
        """Shortcut method to get the results from the
        ``roleplay_list_ratings`` API endpoint."""

        url = reverse('api:roleplay-ratings-list', kwargs={
            'roleplay_uuid': self.roleplay.id,
            'user_uuid': self.trainee.id,
            'session_uuid': self.training_session.id
        })
        response = self.client.get(url)
        return response.status_code, json.loads(response.content.decode('utf-8'))

    def sort_ratings(self, ratings):
        """Shortcut method to sort a ratings dict list."""

        return sorted(ratings, key=lambda k: k['criterion'])

    def compare_ratings(self, expected_ratings, current_ratings):
        """Shortcut method to sort and compare two ratings dict list."""

        self.assertEqual(
            self.sort_ratings(expected_ratings),
            self.sort_ratings(current_ratings)
        )

    def test_save_correct_ratings(self):
        ratings_data = self.get_rating_data()
        status_code, __ = self.post_roleplay_results(ratings_data)
        self.assertEqual(status_code, 200)

        # The user should have a new rating for each criterion in the first step
        user_ratings = Rating.objects.filter(
            training_session=self.training_session,
            trainee=self.trainee,
            criterion__step=self.roleplay.steps.first()
        )

        self.assertEqual(user_ratings.count(), 36)

        # We update the ratings for the user...
        ratings_data = self.get_rating_data()

        # ...For the ``QUANTITATIVE`` criteria...
        for rating in ratings_data[:12]:
            rating['score'] = 1

        # ...For the ``BOOLEAN`` criteria.
        for rating in ratings_data[12:24]:
            rating['score'] = 0

        # ...For the ``QUALITATIVE`` criteria.
        for rating in ratings_data[24:36]:
            rating['text'] = 'test'

        self.post_roleplay_results(ratings_data)

        self.assertEqual(user_ratings.count(), 36)

    def test_save_incorrect_ratings(self):
        ratings_data = self.get_rating_data()
        ratings_data[0]['criterion'] = None

        status_code, __ = self.post_roleplay_results(ratings_data)

        self.assertEqual(status_code, 400)

        # The user shouldn't have ratings in the first step
        user_ratings_count = Rating.objects.filter(
            training_session=self.training_session,
            trainee=self.trainee,
            criterion__step=self.roleplay.steps.first()
        ).count()

        self.assertEqual(user_ratings_count, 0)

    def test_save_empty_text_criterion(self):
        """Test saving an empty text rating for a qualitative criterion"""

        ratings_data = self.get_rating_data()
        ratings_data[-1]['text'] = ''

        status_code, __ = self.post_roleplay_results(ratings_data)

        self.assertEqual(status_code, 400)

    def test_retrieve_full_ratings(self):
        ratings_data = self.get_rating_data()

        # We post the ratings
        status_code, __ = self.post_roleplay_results(ratings_data)
        self.assertEqual(status_code, 200)

        # We retrieve the saved ratings
        status_code, json_content = self.get_roleplay_ratings()

        self.assertEqual(status_code, 200)

        # We saved 36 ratings, we should get 36 ratings
        self.assertEqual(len(json_content), 36)

        # We compare the sorted list of ratings
        self.compare_ratings(ratings_data, json_content)

    def test_retrieve_partial_ratings(self):
        ratings_data = self.get_rating_data()

        # We post only the 5 first ratings
        ratings_data = ratings_data[:5]
        status_code, __ = self.post_roleplay_results(ratings_data)
        self.assertEqual(status_code, 200)

        # We retrieve the saved ratings
        status_code, json_content = self.get_roleplay_ratings()

        self.assertEqual(status_code, 200)

        # We saved 5 ratings, we should get 5 ratings
        self.assertEqual(len(json_content), 5)

        # We compare the sorted list of ratings
        self.compare_ratings(ratings_data[:5], json_content)
