"""This module holds viewsets tests for the ``photostories`` api."""

from io import BytesIO
from zipfile import ZipFile

from django.core.cache import cache
from django.core.urlresolvers import reverse

from slugify import slugify

from rest_framework import status
from rest_framework.test import APIRequestFactory

from mtp.apps.accounts.factories import TraineeFactory
from mtp.apps.common.tests.test_utils import create_fake_image_file

from mtp.apps.photostories.factories import StripFactory, PhotostoryFactory
from mtp.apps.photostories.models import Photo
from mtp.apps.photostories.tests.test_cases import PhotostoryTestCase
from mtp.apps.trainings.factories import TrainingSessionFactory


class PhotoViewSetTestCase(PhotostoryTestCase):
    """Test for the ``PhotoViewSet``."""

    @classmethod
    def setUpClass(cls):
        """Prepare test data."""

        super().setUpClass()

        cls.factory = APIRequestFactory()

    def test_creation(self):
        """Test the creation endpoint."""

        url = reverse(
            'apiv2:photo-list',
            kwargs={
                'parent_lookup_strip__photostory': self.strip.photostory.pk,
                'parent_lookup_strip': self.strip.pk
            }
        )

        # We connect with the strip owner
        self.login_as_trainee()

        response = self.client.post(url, {
            'image': create_fake_image_file()
        })

        # A new ``Photo`` instance should be created
        self.assertEqual(response.status_code, status.HTTP_201_CREATED)
        self.assertIsNotNone(response.data['id'])
        self.assertIsNotNone(response.data['image'])
        self.assertEqual(self.strip.photos.count(), 4)

    def test_creation_user_is_not_owner(self):
        """Test the creation endpoint, when the request user is not the owner"""

        url = reverse(
            'apiv2:photo-list',
            kwargs={
                'parent_lookup_strip__photostory': self.strip.photostory.pk,
                'parent_lookup_strip': self.strip.pk
            }
        )

        # We don't connect with the strip owner
        self.login_as_trainee(username=self.trainees[1].username)

        response = self.client.post(url, {
            'image': create_fake_image_file()
        })

        # Status code has to be unauthorized
        self.assertEqual(response.status_code, status.HTTP_403_FORBIDDEN)

    def test_order(self):
        """Test the order endpoint."""

        url = reverse('apiv2:photo-order', kwargs={
            'parent_lookup_strip__photostory': self.strip.photostory.pk,
            'parent_lookup_strip': self.strip.pk
        })

        photos = self.strip.photos.all()
        order = [photos[1].pk, photos[0].pk]

        # We connect with the strip owner
        self.login_as_trainee()
        response = self.client.post(url, {
            'order': order
        })

        # We should have apply the new order
        self.assertEqual(response.status_code, status.HTTP_200_OK)
        photos = self.strip.photos.all()
        self.assertEqual([photos[0].pk, photos[1].pk], order)

        # We connect with a trainee that is not the owner
        self.login_as_trainee(username=self.trainees[1].username)
        response = self.client.post(url, {
            'order': order
        })

        # Status code has to be unauthorized
        self.assertEqual(response.status_code, status.HTTP_403_FORBIDDEN)


class StripViewSetTestCase(PhotostoryTestCase):
    """Test for the ``PhotoStoryViewSet``."""

    @classmethod
    def setUpClass(cls):
        """Prepare test data."""

        super().setUpClass()

        cls.factory = APIRequestFactory()

    def test_creation(self):
        """Test the custom creation."""

        url = reverse(
            'apiv2:strip-list',
            kwargs={'parent_lookup_photostory': self.photostory.pk}
        )

        strip = {
            'name': 'Test',
            'session': self.training_session.id
        }

        # We connect with the default trainee
        self.login_as_trainee()

        # We post strip data
        response = self.client.post(url, strip)

        # A new ``Strip`` instance should be created, with the owner as the request user
        self.assertEqual(response.status_code, status.HTTP_201_CREATED)
        self.assertIsNotNone(response.data['id'])
        self.assertEqual(response.data['session'], self.training_session.id)
        self.assertEqual(response.data['owner'], self.trainee.id)
        self.assertEqual(response.data['photostory'], self.photostory.id)

    def test_publish(self):
        """Test the publish endpoint."""

        url = reverse(
            'apiv2:strip-publish',
            kwargs={
                'parent_lookup_photostory': self.photostory.pk,
                'pk': self.strip.id
            }
        )

        # We connect with the default trainee
        self.login_as_trainee()

        response = self.client.post(url)

        # This photoStory instance has to be this state to
        self.assertEqual(response.status_code, status.HTTP_200_OK)

    def test_publish_user_has_to_be_the_owner(self):
        """Test the publish endpoint when the user is not the owner."""

        url = reverse(
            'apiv2:strip-publish',
            kwargs={
                'parent_lookup_photostory': self.photostory.pk,
                'pk': self.strip.id
            }
        )

        # User is not the owner
        self.login_as_trainee(username=self.trainees[1].username)

        response = self.client.post(url)

        # Status code has to be forbidden
        self.assertEqual(response.status_code, status.HTTP_403_FORBIDDEN)

    def test_deleted_photo_not_shown_in_nested_photos(self):
        """Test the custom creation."""

        url = reverse(
            'apiv2:strip-detail',
            kwargs={
                'parent_lookup_photostory': self.photostory.pk,
                'pk': self.strip.id
            }
        )

        # We connect with the default trainee
        self.login_as_trainee()

        # Get the number of photos in the strip
        response = self.client.get(url)
        nb_photos = len(response.data['photos'])

        # Soft delete one photos in the strip
        self.strip.photos.first().remove()

        # The number of photos in the strip decrease of one
        response = self.client.get(url)
        self.assertEqual(nb_photos - 1, len(response.data['photos']))

    def test_strip_are_filter_by_session(self):
        """Test that strip are filtered by session"""

        url = '%s?session=%s' % (
            reverse('apiv2:strip-list', kwargs={
                'parent_lookup_photostory': self.photostory.pk,
            }),
            self.training_session.id
        )

        # We connect with the default trainee
        self.login_as_trainee()

        # Get the number of strips
        response = self.client.get(url)
        nb_strips = len(response.data)

        # We create a new session with its request
        new_session = TrainingSessionFactory(training=self.training, trainer=self.trainer)
        new_url = '%s?session=%s' % (
            reverse('apiv2:strip-list', kwargs={
                'parent_lookup_photostory': self.photostory.pk,
            }),
            new_session.id
        )

        # The list should contain 0 strips for the new session
        response = self.client.get(new_url)
        self.assertEqual(len(response.data), 0)

        # We create a new strip in the new session
        StripFactory(photostory=self.photostory, session=new_session)

        # We should still have the same number of strips in the first session
        response = self.client.get(url)
        self.assertEqual(len(response.data), nb_strips)

        # We should have 1 strip in the new session now
        response = self.client.get(new_url)
        self.assertEqual(len(response.data), 1)

        # We create a new photostory in the first session and one in an other session
        StripFactory(photostory=self.photostory, session=self.training_session)
        StripFactory(photostory=self.photostory)

        # We should have one more strip in the first session
        response = self.client.get(url)
        self.assertEqual(len(response.data), nb_strips + 1)

        # We should have still 1 strip in the new session
        response = self.client.get(new_url)
        self.assertEqual(len(response.data), 1)

    def test_strip_are_filter_by_owner(self):
        """Test that strip are filtered by owner"""

        url = '%s?owner=%s' % (
            reverse('apiv2:strip-list', kwargs={
                'parent_lookup_photostory': self.photostory.pk,
            }),
            self.trainee.id
        )

        # We connect with the default trainee
        self.login_as_trainee()

        # Get the number of strips with the initial owner
        response = self.client.get(url)
        nb_strips = len(response.data)

        # We create a new user with its request
        new_trainee = TraineeFactory()
        new_url = '%s?owner=%s' % (
            reverse('apiv2:strip-list', kwargs={
                'parent_lookup_photostory': self.photostory.pk,
            }),
            new_trainee.id
        )

        # The list should contain 0 strip for the new user
        response = self.client.get(new_url)
        self.assertEqual(len(response.data), 0)

        # We create a new strip with the new owner
        StripFactory(photostory=self.photostory, owner=new_trainee)

        # We should still have the same number of strips with the initial owner
        response = self.client.get(url)
        self.assertEqual(len(response.data), nb_strips)

        # We should have 1 strip with the new user now
        response = self.client.get(new_url)
        self.assertEqual(len(response.data), 1)

        # We create a new photostory with the first owner and one in an other owner
        StripFactory(photostory=self.photostory, owner=self.trainee)
        StripFactory(photostory=self.photostory)

        # We should have one more strip with the first owner
        response = self.client.get(url)
        self.assertEqual(len(response.data), nb_strips + 1)

        # We should have still 1 strip with the new owner
        response = self.client.get(new_url)
        self.assertEqual(len(response.data), 1)

    def test_download(self):
        """Test the download endpoint."""

        url = reverse('apiv2:strip-download', kwargs={
            'parent_lookup_photostory': self.strip.photostory.pk,
            'pk': self.strip.id
        })

        # We are not logged
        # Get the zip file
        response = self.client.get(url)

        # we are not authorized
        self.assertEqual(response.status_code, status.HTTP_401_UNAUTHORIZED)

        # We connect with the default trainee
        self.login_as_trainee()

        # Get the zip file
        response = self.client.get(url)

        self.assertEqual(response.status_code, status.HTTP_200_OK)
        self.assertEqual(response.get('Content-Disposition'), "attachment; filename=%s.%s" % (
            slugify(self.strip.name),
            'zip'
        ))

        # Test the content of the zip file
        with BytesIO(response.content) as content_file:
            with ZipFile(content_file, 'r') as zipped_file:
                # It should be valid
                self.assertIsNone(zipped_file.testzip())
                self.assertIn('%s/1.jpg' % self.strip.name, zipped_file.namelist())

                # Count the number of photo that the archive should contain
                nb_photo = Photo.objects.filter(strip__photostory=self.strip.photostory).filter(
                    strip=self.strip).count()
                # The archive should contain as many file as photos
                self.assertEqual(len(zipped_file.namelist()), nb_photo)


class PhotostoryViewSetTestCase(PhotostoryTestCase):
    """Test for the ``PhotoStoryViewSet``."""

    @classmethod
    def setUpClass(cls):
        """Prepare test data."""

        super().setUpClass()

        cls.factory = APIRequestFactory()

    def test_retrieve_training_photostory_view_should_work(self):
        """Test that the retrieve view for a training photostory work"""

        self.login_as_trainee()

        url = reverse('apiv2:photostory-detail', kwargs={'pk': self.photostory.pk})

        cache.clear()

        # 5 queries the first time
        with self.assertNumQueries(5):
            response = self.client.get(url)

        self.assertEqual(response.status_code, 200)

        # 4 queries the second time
        with self.assertNumQueries(4):
            response = self.client.get(url)

        self.assertEqual(response.status_code, 200)

    def test_retrieve_live_photostory_view_should_work(self):
        """Test that the retrieve view for a live photostory work"""

        self.login_as_trainee()

        url = reverse('apiv2:photostory-detail', kwargs={'pk': self.photostory_live.pk})

        cache.clear()

        # 5 queries the first time
        with self.assertNumQueries(5):
            response = self.client.get(url)

        self.assertEqual(response.status_code, 200)

        # 4 queries the second time
        with self.assertNumQueries(4):
            response = self.client.get(url)

        self.assertEqual(response.status_code, 200)

    def test_creation_forbidden_for_trainee(self):
        """Test the creation by a trainee is forbidden."""

        url = reverse('apiv2:photostory-list')

        photostory = {
            'session': self.training_session.id,
            'name': 'Test name',
            'description': 'Test description'
        }

        # Test the creation as trainee is forbidden.
        # We connect with the default trainee
        self.login_as_trainee()

        # We post photostory data
        response = self.client.post(url, photostory)

        # The trainee can't create a photostory
        self.assertEqual(response.status_code, status.HTTP_403_FORBIDDEN)

    def test_creation_with_session(self):
        """Test the creation with a session."""

        url = reverse('apiv2:photostory-list')

        photostory = {
            'session': self.training_session.id,
            'name': 'Test name',
            'description': 'Test description'
        }

        # We connect with the default trainer
        self.login_as_trainer()

        # We post photostory data
        response = self.client.post(url, photostory)

        # A new ``PhotoStory`` instance should be created
        self.assertEqual(response.status_code, status.HTTP_201_CREATED)
        self.assertIsNotNone(response.data['id'])
        self.assertEqual(response.data['session'], self.training_session.id)
        self.assertEqual(response.data['training'], None)
        self.assertEqual(response.data['name'], 'Test name')
        self.assertEqual(response.data['description'], 'Test description')

    def test_creation_with_training(self):
        """Test the creation with a training."""

        url = reverse('apiv2:photostory-list')

        # we test by creating with a training
        photostory = {
            'training': self.training_session.training.id,
            'name': 'Test name',
            'description': 'Test description'
        }

        # We connect with the default trainer
        self.login_as_trainer()

        # We post photostory data
        response = self.client.post(url, photostory)

        # A new ``PhotoStory`` instance should be created with not session set
        self.assertEqual(response.status_code, status.HTTP_201_CREATED)
        self.assertIsNotNone(response.data['id'])
        self.assertEqual(response.data['session'], None)
        self.assertEqual(response.data['training'], self.training_session.training.id)
        self.assertEqual(response.data['name'], 'Test name')
        self.assertEqual(response.data['description'], 'Test description')

    def test_photostory_are_filter_by_session(self):
        """Test that activities are filtered by session"""

        url = '%s?session=%s' % (
            reverse('apiv2:photostory-list'),
            self.training_session.id
        )

        # We connect with the default trainee
        self.login_as_trainee()

        # We should have 2 activities
        response = self.client.get(url)
        self.assertEqual(len(response.data), 2)

        # We create a new session with its request
        new_session = TrainingSessionFactory(training=self.training, trainer=self.trainer)
        new_url = '%s?session=%s' % (
            reverse('apiv2:photostory-list'),
            new_session.id
        )

        # We should have only one photostory inherited by its training with the new session
        response = self.client.get(new_url)
        self.assertEqual(len(response.data), 1)

        # We create a new live photostory in the new session
        PhotostoryFactory(training=None, session=new_session)

        # We should still have 2 activities in the first session
        response = self.client.get(url)
        self.assertEqual(len(response.data), 2)

        # We should have 2 activities in the new session now
        response = self.client.get(new_url)
        self.assertEqual(len(response.data), 2)

        # We create a new live photostory in the first session and one in an other training
        PhotostoryFactory(training=None, session=self.training_session)
        PhotostoryFactory()

        # We should have 3 activities in the first session
        response = self.client.get(url)
        self.assertEqual(len(response.data), 3)

        # We should have still 2 activities in the new session
        response = self.client.get(new_url)
        self.assertEqual(len(response.data), 2)

