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

import json

from django.core.urlresolvers import reverse

from rest_framework.test import APIRequestFactory


from mtp.apps.api.tests.test_cases import MTPAPITestCase
from mtp.apps.brainstormers.factories import (
    BrainstormerActivityFactory,
    BrainstormerFactory,
    BrainstormerPropositionFactory,
    BrainstormerPropositionGroupFactory
)
from mtp.apps.brainstormers.models import (
    DEFAULT_GROUP_ATTRIBUTES,
    Brainstormer,
    BrainstormerProposition,
    BrainstormerPropositionGroup
)
from mtp.apps.brainstormers.tests.test_cases import BrainstormerActivityTestCase
from mtp.apps.common.tests.test_cases import MTPTestCase

from ..views import BrainstormerActivityViewSet


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

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

        super().setUpTestData()

        cls.brainstormer = BrainstormerFactory()

        # One proposition
        cls.proposition = BrainstormerPropositionFactory(
            group=cls.brainstormer.groups.first()
        )

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

        cls.new_brainstormer_data = {
            'title': 'test_brainstormer_sample',
            'training_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('title'), new_brainstormer.name)
        self.assertEqual(expected.get('training_session'), new_brainstormer.session.id)

    def test_post_should_create_brainstormer_with_activity(self):
        """
        Test that the ``Brainstormer`` with a ``Brainstormer Activity`` is created.
        """
        # Add activity to brainstormer
        entry = self.new_brainstormer_data.copy()
        entry.update(
            {
                'activity': BrainstormerActivityFactory().id
            }
        )

        url = reverse('api:brainstormer-list')
        response = self.client.post(url, json.dumps(entry), 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 = entry
        self.assertEqual(expected.get('activity'), new_brainstormer.activity.id)

    def test_post_should_not_create_brainstormer_with_wrong_activity_id(self):
        """
        Test that the ``Brainstormer`` with a wrong ``Brainstormer Activity`` is not created.
        """
        # Add activity to brainstormer
        entry = self.new_brainstormer_data.copy()
        entry.update(
            {
                'activity': 'wrong-activity-id'
            }
        )

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

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

    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('title'), up_brainstormer.name)
        self.assertEqual(expected.get('training_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['proposal_groups'][0]['propositions']),
            1
        )
        self.assertEqual(len(brainstormer_json['proposals']), 1)

        # One like on this proposition
        self.assertEqual(
            len(brainstormer_json['proposal_groups'][0]['propositions'][0]['likes']),
            1
        )

        # Soft-delete the like
        self.trainee.unlike(self.proposition)

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

        # Soft-delete the proposition
        self.proposition.remove()

        self.assertEqual(
            len(get_brainstormer_json_from_api()['proposal_groups'][0]['propositions']),
            0
        )
        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(MTPTestCase):

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

        cls.brainstormer = BrainstormerFactory()

        cls.proposition = BrainstormerPropositionFactory()

        cls.new_proposition_data = {
            "text": "test_proposition",
            "group": 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('group'), new_proposition.group.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_create_proposition_without_group(self):
        """
        Test that the ``Brainstormer Proposition`` without group is created in the default group.
        """
        # Delete group
        entry = self.new_proposition_data.copy()
        entry.pop('group')

        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
        expected.update({
            'name': DEFAULT_GROUP_ATTRIBUTES.get('name')
        })
        self.assertEqual(expected.get('text'), new_proposition.text)
        self.assertEqual(expected.get('name'), new_proposition.group.name)
        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.group.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('group'), up_proposition.group.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.group.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(MTPTestCase):

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

        cls.group = BrainstormerPropositionGroupFactory()

        cls.brainstormer = BrainstormerFactory()

        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.
        """
        BrainstormerPropositionFactory.create_batch(3, group=self.group)

        url = reverse('api:brainstormer_group-detail', args=(self.group.brainstormer.id,
                                                             self.group.id))
        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)
        for proposition in del_group.propositions.all():
            self.assertTrue(proposition.is_deleted)


class BrainstormerActivityApiTestCase(BrainstormerActivityTestCase):
    """Test case for brainstormer activities api."""

    def setUp(self):
        """Create a request factory."""

        super().setUp()

        self.factory = APIRequestFactory()

    def test_get_brainstormer_activities_list(self):
        """Test that the route return the serialized brainstormer activities list."""

        url = reverse('api:brainstormeractivity-list')
        request = self.factory.get(url)
        request.user = self.admin

        view = BrainstormerActivityViewSet.as_view({'get': 'list'})
        response = view(request)

        self.assertEqual(response.status_code, 200)

        self.assertCountEqual(
            response.data,
            [
                {
                    'id': self.brainstormer_activity.id,
                    'name': self.brainstormer_activity.name,
                    'trainings': [self.training.id],
                    'image': request.build_absolute_uri(self.brainstormer_activity.image.url),
                    'preview': request.build_absolute_uri(self.brainstormer_activity.preview.url),
                    'hash_image': self.brainstormer_activity.hash_image,
                    'hash_preview': self.brainstormer_activity.hash_preview,
                },
            ]
        )
