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

import json

from django.contrib.auth import authenticate
from django.core.urlresolvers import reverse
from django.test.utils import override_settings

from mtp.apps.accounts.factories import AvatarFactory
from mtp.apps.accounts.models import MTPUser
from mtp.apps.api.tests.test_cases import MTPAPITestCase
from mtp.apps.common.tests.test_cases import MTPTestCase, DEFAULT_PASSWORD
from mtp.apps.documents.factories import NoteFactory
from mtp.apps.trainings.factories import TrainingSessionFactory


class UserViewsTestCase(MTPTestCase):

    NEW_USER_DATA = {
        'first_name': 'Kara',
        'last_name': 'Thrace',
        'mail': 'kara.thrace@galacti.ca',
        'login': 'starbuck',
        'pass': 'SoSayWeAll!'
    }

    url = reverse('api:users_list')

    def test_get_users_list_is_forbidden(self):
        """
        Test that the ``users_list`` view can't be accessed by GET.
        """
        response = self.client.get(self.url)
        self.assertEqual(response.status_code, 405)

    def test_post_users_list_creates_user(self):
        """
        Test that a POST to the ``users_list`` view creates a user.
        """
        response = self.client.post(self.url, self.NEW_USER_DATA)
        self.assertEqual(response.status_code, 200)

        new_user = MTPUser.objects.get(username=self.NEW_USER_DATA.get('login'))
        self.assertEqual(new_user.first_name, self.NEW_USER_DATA.get('first_name'))
        self.assertEqual(new_user.last_name, self.NEW_USER_DATA.get('last_name'))
        self.assertEqual(new_user.email, self.NEW_USER_DATA.get('mail'))

        new_user_group_names = [g.name for g in new_user.mgroups.all()]
        expected_group_names = ['trainee', 'whoswho']
        self.assertCountEqual(new_user_group_names, expected_group_names)

        logged_user = authenticate(
            username=self.NEW_USER_DATA.get('login'),
            password=self.NEW_USER_DATA.get('pass'),
        )
        self.assertIsNotNone(logged_user)

    def test_post_users_list_when_user_already_exists(self):
        """
        Test that a POST to the ``users_list`` returns 400
        if the specified user already exists.
        """
        response = self.client.post(self.url, self.NEW_USER_DATA)
        response = self.client.post(self.url, self.NEW_USER_DATA)
        self.assertEqual(response.status_code, 400)

    def test_post_users_list_with_a_specific_session(self):
        """
        Test that a POST to the ``users_list`` successfully
        adds the new user to a session if requested.
        """

        training_session = TrainingSessionFactory()
        new_user_data = self.NEW_USER_DATA
        new_user_data.update({
            'session_id': training_session.id,
        })

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

        new_user = MTPUser.objects.get(username=self.NEW_USER_DATA.get('login'))
        self.assertIn(new_user, training_session.users.all())

    @override_settings(INSTANCE_IS_SLAVE=True, INSTANCE_IS_MASTER=False)
    def test_post_users_list_should_correctly_set_the_created_from_attribute_on_slave(self):
        """Test that a POST to the ``users_list`` should set the origin of the user"""

        self.client.post(self.url, self.NEW_USER_DATA)
        new_user = MTPUser.objects.get(username=self.NEW_USER_DATA.get('login'))

        self.assertEqual(new_user.created_from, MTPUser.ORIGINS.SLAVE_REGISTRATION)

    @override_settings(INSTANCE_IS_SLAVE=False, INSTANCE_IS_MASTER=True)
    def test_post_users_list_should_correctly_set_the_created_from_attribute_on_master(self):
        """Test that a POST to the ``users_list`` should set the origin of the user"""

        self.client.post(self.url, self.NEW_USER_DATA)
        new_user = MTPUser.objects.get(username=self.NEW_USER_DATA.get('login'))

        self.assertEqual(new_user.created_from, MTPUser.ORIGINS.MASTER_REGISTRATION)


class UserApiTestCase(MTPTestCase):

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

        cls.url = reverse('api:user', kwargs={'user_uuid': cls.trainee.id})

        cls.NEW_USER_DATA = {
            'first_name': 'Kara',
            'last_name': 'Thrace',
            'mail': 'kara.thrace@galacti.ca'
        }

    def test_get_user_should_return_the_user(self):
        """
        Test that the ``user`` view return the user wanted.
        """
        response = self.client.get(self.url)

        self.assertEqual(response.status_code, 200)

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

        # Data verification
        self.assertEqual(data.get('id'), self.trainee.id)
        self.assertEqual(data.get('first_name'), self.trainee.first_name)
        self.assertEqual(data.get('username'), self.trainee.username)

    def test_get_user_with_notes(self):
        """
        Test that the ``user`` view return notes attributes to user.
        """
        note = NoteFactory(user=self.trainee)

        response = self.client.get(self.url)
        self.assertEqual(response.status_code, 200)
        data = json.loads(response.content.decode('utf-8')).get('user')

        # Data verification
        received_note = data.get('notes')[0]
        self.assertEqual(received_note.get('id'), note.id)
        self.assertEqual(received_note.get('text'), note.text)
        self.assertAlmostEqual(
            float(received_note.get('x')),
            note.coord_x,
            places=5,
        )

    def test_get_user_with_avatar(self):
        """Test that the ``user`` view return the avatars of the user."""

        avatar = AvatarFactory()
        self.trainee.photos.add(avatar)

        response = self.client.get(self.url)
        self.assertEqual(response.status_code, 200)
        data = json.loads(response.content.decode('utf-8')).get('user')

        # Data verification
        received_avatar = data.get('photos')[0]
        self.assertEqual(received_avatar['id'], avatar.id)
        self.assertEqual(received_avatar['hash'], avatar.hash)
        self.assertEqual(received_avatar['img'], avatar.img.url)

    def test_post_user_should_update_the_user(self):
        """
        Test that the ``user`` view update user.
        """
        response = self.client.post(self.url, self.NEW_USER_DATA)

        self.assertEqual(response.status_code, 200)

        up_trainee = MTPUser.objects.get(id=self.trainee.id)

        # Data verification
        self.assertEqual(self.NEW_USER_DATA['first_name'], up_trainee.first_name)
        self.assertEqual(self.NEW_USER_DATA['last_name'], up_trainee.last_name)
        self.assertEqual(self.NEW_USER_DATA['mail'], up_trainee.email)


class LoginApiTestCase(MTPAPITestCase):
    """Test case for the ``login`` api route"""

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

        super().setUpTestData()

        cls.login_url = reverse('api:login')

    def test_call_login_with_correct_crendentials_should_login_the_user(self):
        """Test that the ``login`` route should allow the user to login"""

        # The user is not logged in
        with self.assertRaises(KeyError):
            self.client.session['_auth_user_id']  # pylint: disable=pointless-statement

        status_code, json_response = self.get_api_response(
            self.login_url,
            'post', {
                'login': self.trainee.username,
                'pass': DEFAULT_PASSWORD
            }
        )

        # The response is good
        self.assertEqual(status_code, 200)
        self.assertEqual(json_response['user']['username'], self.trainee.username)

        # The user should be logged in
        self.assertEqual(self.client.session['_auth_user_id'], self.trainee.pk)

    def test_call_login_with_incorrect_credantials(self):
        """Test that the ``login`` route should return an error with incorrect credentials"""

        # Since the login route do not return a json response when invalid
        # credentials are submitted (what a surprise !)...
        response = self.client.post(
            self.login_url,
            {
                'login': 'thisusernameisnotused',
                'pass': 'thispasswordneither'
            }
        )

        # The route should return an error
        self.assertEqual(response.status_code, 400)

        # The user is not logged in
        with self.assertRaises(KeyError):
            self.client.session['_auth_user_id']  # pylint: disable=pointless-statement


class UserDetailViewTestCase(MTPAPITestCase):
    """Test case for the user-detail api view"""

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

        super().setUpTestData()

        cls.user_detail_url = reverse('api:user-detail', kwargs={
            'user_uuid': cls.trainee.pk
        })

    def test_user_detail_view_should_work(self):
        """Test that the user detail view work and output the correct data"""

        status_code, json_response = self.get_api_response(
            self.user_detail_url,
            'get',
            {}
        )

        self.assertEqual(status_code, 200)

        self.assertEqual(
            json_response,
            {
                'id': self.trainee.pk,
                'username': self.trainee.username,
                'first_name': self.trainee.first_name,
                'last_name': self.trainee.last_name,
                'email': self.trainee.email,
                'natural_language': self.trainee.natural_language,
                'photo': self.trainee.photo.id,
                'photos': [],
                'groups': [
                    group.name
                    for group in self.trainee.mgroups.all()
                ],
            }
        )


class LogoutViewTestCase(MTPAPITestCase):
    """Test case for the ``logout`` api view"""

    @classmethod
    def setUpClass(cls):
        """Build the api logout url"""

        super().setUpClass()

        cls.logout_url = reverse('api:logout')

    def test_logout_should_logout_the_user(self):
        """Test that the ``logout`` view actually logout the current user"""

        # Login a user and check that is actually authenticated
        self.login_as_trainee()
        self.assertIn('_auth_user_id', self.client.session)

        # Call the logout view
        self.client.post(self.logout_url)

        # The user is not authenticated anymore
        self.assertNotIn('_auth_user_id', self.client.session)

    def test_logout_should_not_throw_exceptions_if_the_user_is_not_login(self):
        """Test that the ``logout`` view don't throw exceptions if the user is not login"""

        # The user is not authenticated
        self.assertNotIn('_auth_user_id', self.client.session)

        # Call the logout view (the test will fail if any exception is raised)
        self.client.post(self.logout_url)
