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

import json

from django.core.urlresolvers import reverse

from mtp.apps.brainstormers.constants import BRAINSTORMER_TYPE_CHOICES
from mtp.apps.brainstormers.factories import (
    BrainstormerFactory,
    BrainstormerPropositionGroupFactory
)

from mtp.apps.brainstormers.models import (
    Brainstormer,
    BrainstormerProposition,
    BrainstormerPropositionGroup
)

from mtp.apps.brainstormers.tests.test_cases import BrainstormerTestCase


class BrainstormersApiTestCase(BrainstormerTestCase):
    """Test case for the brainstormer api endpoints"""

    @classmethod
    def setUpTestData(cls):
        """Create a brainstormer and store some test data"""

        super().setUpTestData()

        # One like on this proposition
        cls.trainee.like(cls.proposition)

        cls.new_brainstormer_data = {
            'name': 'test_brainstormer_sample',
            'session': cls.training_session.id
        }

    def test_post_should_create_brainstormer(self):
        """
        Test that the ``Brainstormer`` is created.
        """
        url = reverse('api:brainstormer-list')
        response = self.client.post(url, json.dumps(self.new_brainstormer_data),
                                    content_type='application/json')

        # Test creation brainstormer
        self.assertEqual(response.status_code, 201)

        data = json.loads(response.content.decode('utf-8'))
        new_brainstormer = Brainstormer.objects.get(id=data.get('id'))

        # Data verification
        expected = self.new_brainstormer_data
        self.assertEqual(expected.get('name'), new_brainstormer.name)
        self.assertEqual(expected.get('session'), new_brainstormer.session.id)

    def test_post_should_update_brainstormer(self):
        """
        Test that the ``Brainstormer`` is updated.
        """
        url = reverse('api:brainstormer-detail', kwargs={'pk': self.brainstormer.id})
        response = self.client.patch(url, json.dumps(self.new_brainstormer_data),
                                     content_type='application/json')

        # Test update brainstormer
        self.assertEqual(response.status_code, 200)

        up_brainstormer = Brainstormer.objects.get(id=self.brainstormer.id)

        # Data verification
        expected = self.new_brainstormer_data
        self.assertEqual(expected.get('name'), up_brainstormer.name)
        self.assertEqual(expected.get('session'), up_brainstormer.session.id)

    def test_delete_should_archive_brainstormer(self):
        """
        Test that the ``Brainstormer`` is archived with his groups.
        """
        BrainstormerPropositionGroupFactory.create_batch(3, brainstormer=self.brainstormer)

        url = reverse('api:brainstormer-detail', kwargs={'pk': self.brainstormer.id})
        response = self.client.delete(url)

        # Test delete brainstormer
        self.assertEqual(response.status_code, 204)

        del_brainstormer = Brainstormer.objects.get(id=self.brainstormer.id)

        # Data verification
        self.assertTrue(del_brainstormer.is_deleted)
        for group in del_brainstormer.groups.all():
            self.assertTrue(group.is_deleted)

    def test_deleted_objects_should_not_be_shown(self):
        """Test that deleted "groups", "propositions" or "likes" should not appear"""

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

        def get_brainstormer_json_from_api():
            """Shortcut to load the brainstormer from the api"""
            return self.client.get(url).data

        brainstormer_json = get_brainstormer_json_from_api()

        # One group
        self.assertEqual(
            len(brainstormer_json['proposal_groups']),
            1
        )

        # One proposition in this group
        self.assertEqual(
            len(brainstormer_json['proposals']),
            1
        )
        self.assertEqual(len(brainstormer_json['proposals']), 1)

        # One like on this proposition
        self.assertEqual(
            len(brainstormer_json["proposals"][0]["likes"]),
            1
        )

        # Soft-delete the like
        self.trainee.unlike(self.proposition)
        self.assertEqual(
            len(get_brainstormer_json_from_api()["proposals"][0]["likes"]),
            0
        )

        # Soft-delete the proposition
        self.proposition.remove()
        self.assertEqual(
            len(get_brainstormer_json_from_api()["proposals"]), 0)

        # Soft-delete the group
        self.brainstormer.groups.first().remove()

        self.assertEqual(
            len(get_brainstormer_json_from_api()['proposal_groups']),
            0
        )


class BrainstormersPropositionApiTestCase(BrainstormerTestCase):
    """Test case for the brainstormerpropositions api endpoints."""

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

        cls.new_proposition_data = {
            "text": "test_proposition",
            "groups": [BrainstormerPropositionGroupFactory(brainstormer=cls.brainstormer).id],
            "user": cls.trainee.id
        }

    def test_post_should_create_proposition(self):
        """
        Test that the ``Brainstormer Proposition`` is created.
        """
        url = reverse('api:brainstormer_proposition-list', args=(self.brainstormer.id,))
        response = self.client.post(url, json.dumps(self.new_proposition_data),
                                    content_type='application/json')

        # Test creation brainstormer proposition
        self.assertEqual(response.status_code, 201)

        data = json.loads(response.content.decode('utf-8'))
        new_proposition = BrainstormerProposition.objects.get(id=data.get('id'))

        # Data verification
        expected = self.new_proposition_data
        self.assertEqual(expected.get('text'), new_proposition.text)
        self.assertEqual(expected.get('groups'), [new_proposition.groups.first().id])
        self.assertEqual(expected.get('user'), new_proposition.user.id)

        # Test that the data contains the created / modified fields
        self.assertIn('created', data)
        self.assertIn('modified', data)

    def test_post_should_accept_text_respecting_maxlengh_for_brainstormer_type(self):
        """The propositions could only be created if the text maxlength is respected"""

        for brainstormer_type, maxlength in [
                (BRAINSTORMER_TYPE_CHOICES.QUESTIONWALL, 140),
                (BRAINSTORMER_TYPE_CHOICES.PROPOSITIONWALL, 80),
                (BRAINSTORMER_TYPE_CHOICES.WORDCLOUD, 30),
            ]:

            with self.subTest(brainstormer_type=brainstormer_type, maxlength=maxlength):

                brainstormer = BrainstormerFactory(brainstormer_type=brainstormer_type)

                url = reverse('api:brainstormer_proposition-list', args=(brainstormer.id,))

                # ok with less
                response = self.client.post(
                    url,
                    json.dumps(
                        dict(self.new_proposition_data, text='x' * (maxlength-1))
                    ),
                    content_type='application/json'
                )
                self.assertEqual(response.status_code, 201)

                # ok with max
                response = self.client.post(
                    url,
                    json.dumps(
                        dict(self.new_proposition_data, text='x' * maxlength)
                    ),
                    content_type='application/json'
                )
                self.assertEqual(response.status_code, 201)

                # not ok with more
                response = self.client.post(
                    url,
                    json.dumps(
                        dict(self.new_proposition_data, text='x' * (maxlength+1))
                    ),
                    content_type='application/json'
                )
                self.assertEqual(response.status_code, 400)

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

                # Ensure we have only one error, about the text, containing the limitation
                self.assertSetEqual(set(data.keys()), {'text'})
                self.assertEqual(len(data['text']), 1)
                self.assertIn('%s characters' % maxlength, data['text'][0])

    def test_post_should_create_proposition_without_group(self):
        """Test that the ``Brainstormer Proposition`` without group is created without group."""
       # Delete group
        entry = self.new_proposition_data.copy()
        entry.pop('groups')

        url = reverse('api:brainstormer_proposition-list', args=(self.brainstormer.id,))
        response = self.client.post(url, json.dumps(entry), content_type='application/json')

        # Test creation brainstormer proposition without group
        self.assertEqual(response.status_code, 201)

        data = json.loads(response.content.decode('utf-8'))
        new_proposition = BrainstormerProposition.objects.get(id=data.get('id'))

        # Data verification
        expected = self.new_proposition_data
        self.assertEqual(expected.get('text'), new_proposition.text)
        self.assertEqual(expected.get('user'), new_proposition.user.id)

    def test_post_should_update_proposition(self):
        """
        Test that the ``Brainstormer Proposition`` is updated.
        """
        url = reverse(
            'api:brainstormer_proposition-detail',
            args=(self.proposition.brainstormer.id, self.proposition.id)
        )
        response = self.client.patch(url, json.dumps(self.new_proposition_data),
                                     content_type='application/json')

        # Test update brainstormer proposition
        self.assertEqual(response.status_code, 200)

        up_proposition = BrainstormerProposition.objects.get(id=self.proposition.id)

        # Data verification
        expected = self.new_proposition_data
        self.assertEqual(expected.get('text'), up_proposition.text)
        self.assertEqual(expected.get('groups'), [up_proposition.groups.first().id])
        self.assertEqual(expected.get('user'), up_proposition.user.id)

    def test_delete_should_archive_proposition(self):
        """
        Test that the ``Brainstormer Proposition`` is archived.
        """
        url = reverse(
            'api:brainstormer_proposition-detail',
            args=(self.proposition.brainstormer.id, self.proposition.id)
        )
        response = self.client.delete(url)

        # Test delete brainstormer proposition
        self.assertEqual(response.status_code, 204)

        del_proposition = BrainstormerProposition.objects.get(id=self.proposition.id)

        # Data verification
        self.assertTrue(del_proposition.is_deleted)


class BrainstormersPropositionGroupApiTestCase(BrainstormerTestCase):
    """Test case for the brainstormerpropositiongroup api endpoints."""

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

        cls.new_group_data = {
            "name": "test_group",
            "color": "steelblue",
        }

    def test_post_should_create_group(self):
        """
        Test that the ``Brainstormer Group`` is created.
        """
        url = reverse(
            'api:brainstormer_group-list', args=(self.brainstormer.id,)
        )
        response = self.client.post(url, json.dumps(self.new_group_data),
                                    content_type='application/json')

        # Test creation brainstormer group
        self.assertEqual(response.status_code, 201)

        data = json.loads(response.content.decode('utf-8'))
        new_group = BrainstormerPropositionGroup.objects.get(id=data.get('id'))

        # Data verification
        expected = self.new_group_data
        expected.update({
            'brainstormer': self.brainstormer.id,
        })
        self.assertEqual(expected.get('name'), new_group.name)
        self.assertEqual(expected.get('color'), new_group.color)
        self.assertEqual(expected.get('brainstormer'), new_group.brainstormer.id)

    def test_post_should_update_group(self):
        """
        Test that the ``Brainstormer Group`` is updated.
        """
        url = reverse('api:brainstormer_group-detail', args=(self.group.brainstormer.id,
                                                             self.group.id))
        response = self.client.patch(url, json.dumps(self.new_group_data),
                                     content_type='application/json')

        # Test update brainstormer group
        self.assertEqual(response.status_code, 200)

        up_group = BrainstormerPropositionGroup.objects.get(id=self.group.id)

        # Data verification
        expected = self.new_group_data
        expected.update({
            'brainstormer': self.group.brainstormer,
        })
        self.assertEqual(expected.get('name'), up_group.name)
        self.assertEqual(expected.get('color'), up_group.color)
        self.assertEqual(expected.get('brainstormer'), up_group.brainstormer)

    def test_delete_should_archive_group(self):
        """Test that the ``Brainstormer Group`` is archived with his propositions."""
        url = reverse('api:brainstormer_group-detail', args=(self.group.brainstormer.id,
                                                             self.group.id))
        del_group = BrainstormerPropositionGroup.objects.get(id=self.group.id)

        self.assertFalse(del_group.is_deleted)
        self.assertFalse(del_group.propositions.get().is_deleted)

        self.proposition.group = del_group
        self.proposition.save()

        response = self.client.delete(url)

        # Test delete brainstormer group
        self.assertEqual(response.status_code, 204)

        del_group = BrainstormerPropositionGroup.objects.get(id=self.group.id)

        # Data verification
        self.assertTrue(del_group.is_deleted)

        self.assertTrue(del_group.propositions.get().is_deleted)
