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

from django.conf import settings
from django.utils import timezone

from mtp.apps.common.tests.test_cases import MTPTestCase, WithMediaOnDisk

from mtp.apps.photostories.factories import PhotoFactory, PhotostoryFactory, StripFactory
from mtp.apps.trainings.factories import TrainingFactory, TrainingSessionFactory
from mtp.apps.trainings.models import TrainingSession

from ..models import Photo, Photostory, Strip


class PhotostoriesManagerTestCase(MTPTestCase, WithMediaOnDisk):
    """Test case for the manager of Photostory Model
    We are not using directy MTPTestCase because we need multiple training and sessions

    """

    PHOTOSTORY_PATH = os.path.join(settings.MEDIA_ROOT, 'upload/photostory')
    DATE_FORMAT = "%Y-%m-%d_%H-%M-%S"

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

        super().setUpClass()

        date = timezone.now()
        cls.date_str = date.strftime(cls.DATE_FORMAT)

        # Create 3 admin trainings, associated with a business unit.
        cls.admin_trainings = TrainingFactory.create_batch(
            3,
            owner=cls.admin,
            business_unit=cls.business_unit,
            job_profile=cls.job_profile,
            theme=cls.theme,
        )
        # Create 3 trainings from admin trainings
        cls.trainings = []
        for admin_training in cls.admin_trainings:
            # Publish the training
            admin_training.version_state = cls.admin_training.VERSION_STATES.PUBLISHED
            admin_training.save()
            # Duplicate the training to the trainer
            cls.trainings.append(admin_training.duplicate_to_members()[0])

        cls.training_sessions = {}
        # Create 3 sessions for each training
        for training in cls.trainings:
            cls.training_sessions[training] = TrainingSessionFactory.create_batch(
                3,
                training=training,
                trainer=cls.trainer,
                state=TrainingSession.STATE_CHOICES.OPEN,
                permissions=TrainingSession.PERMISSION_CHOICES.PUBLIC,
            )

        cls.unknown_id = '12345678-1234-1234-1234-123456789123'
        cls.wrong_id = '12345678-1234-1234-1234'

        cls.first_training = cls.trainings[0]
        cls.second_training = cls.trainings[1]
        cls.first_training_session = cls.training_sessions[cls.first_training][0]
        cls.second_training_session = cls.training_sessions[cls.first_training][1]

        cls.first_training_path = os.path.join(cls.PHOTOSTORY_PATH, cls.first_training.id)
        cls.second_training_path = os.path.join(cls.PHOTOSTORY_PATH, cls.second_training.id)
        cls.first_training_session_path = os.path.join(
            cls.first_training_path,
            cls.first_training_session.id
        )
        cls.second_training_session_path = os.path.join(
            cls.first_training_path,
            cls.second_training_session.id
        )
        cls.not_exist_photo_path = os.path.join(
            cls.first_training_session_path,
            'not_exist.jpg'
        )

        # First photo for the first session of the first training
        cls.first_photo_path = os.path.join(
            cls.first_training_session_path,
            '_result_session_%s_ps_user_%s_%s_%i.jpg' % (
                cls.first_training_session.id,
                cls.trainee.id,
                cls.date_str,
                0
            )
        )

    @classmethod
    def setup_data_before_import(cls):
        """ Prepare test data before import."""

        shutil.rmtree(cls.PHOTOSTORY_PATH, ignore_errors=True)

        for training in cls.trainings:
            for session in cls.training_sessions[training]:
                session_path = os.path.join(cls.PHOTOSTORY_PATH, training.id, session.id)
                # create session dirs (recursive)
                os.makedirs(session_path)
                # create 2 photos for each trainee
                for trainee in cls.trainees:
                    for index in range(2):
                        photo_path = os.path.join(
                            session_path,
                            '_result_session_%s_ps_user_%s_%s_%i.jpg' % (
                                session.id,
                                trainee.id,
                                cls.date_str,
                                index
                            )
                        )
                        open(photo_path, 'a').close()

        # we create 2 wrong training dirs with unknown id and wrong id
        training_path = os.path.join(cls.PHOTOSTORY_PATH, cls.unknown_id)
        os.makedirs(training_path)
        training_path = os.path.join(cls.PHOTOSTORY_PATH, cls.wrong_id)
        os.makedirs(training_path)

        # for the first training
        # we create 2 wrong session dirs with unknown id and wrong id
        session_path = os.path.join(cls.first_training_path, cls.unknown_id)
        os.makedirs(session_path)
        session_path = os.path.join(cls.first_training_path, cls.wrong_id)
        os.makedirs(session_path)

        # for the first session of the first training
        # we create 3 wrongs photo files with unknown id, wrong id, and wrong name
        session_path = os.path.join(
            cls.PHOTOSTORY_PATH,
            cls.first_training.id,
            cls.first_training_session.id
        )
        photo_path = os.path.join(
            session_path,
            '_result_session_%s_ps_user_%s_%s_%i.jpg' % (
                cls.unknown_id,
                cls.unknown_id,
                cls.date_str,
                0
            )
        )
        open(photo_path, 'a').close()
        photo_path = os.path.join(
            session_path,
            '_result_session_%s_ps_user_%s_%s_%i.jpg' % (
                cls.wrong_id,
                cls.wrong_id,
                cls.date_str,
                0
            )
        )
        open(photo_path, 'a').close()
        photo_path = os.path.join(session_path, 'wrong.jpg')
        open(photo_path, 'a').close()

    @classmethod
    def setup_data_before_export(cls):
        """ Prepare test data before export. """

        # we clean the path
        shutil.rmtree(cls.PHOTOSTORY_PATH, ignore_errors=True)

        # we create an existing dir with same name as first training id
        os.makedirs(cls.first_training_path)

        # we create an existing file with same name as second training id
        open(cls.second_training_path, 'a').close()

        # For the fisrt training
        # we create an existing dir with same name as session id
        os.makedirs(cls.first_training_session_path)

        # we create an existing file with same name as the second sessionid
        open(cls.second_training_session_path, 'a').close()

        # we create photostories
        cls.photostories = {}
        cls.strips = {}
        cls.photos = {}
        for training in cls.trainings:
            cls.photostories[training] = PhotostoryFactory.create_batch(2, training=training)
            # And for each photostories and each sessions
            # we create 1 strip with the trainee, and one with new trainee
            for photostory in cls.photostories[training]:
                for session in cls.training_sessions[training]:
                    strips = [
                        StripFactory(
                            photostory=photostory,
                            session=session,
                            owner=cls.trainee
                        ),
                        StripFactory(
                            photostory=photostory,
                            session=session
                        )
                    ]
                    if photostory not in cls.strips:
                        cls.strips[photostory] = strips
                    else:
                        cls.strips[photostory].append(strips)

                    # for each strips
                    # we create 2 photos
                    for strip in strips:
                        cls.photos[strip] = PhotoFactory.create_batch(2, strip=strip)

        # One live photostory, with one strip and one photo
        cls.live_photostory = PhotostoryFactory(
            training=None,
            session=cls.first_training_session
        )
        strip = StripFactory(
            photostory=cls.live_photostory,
            session=cls.live_photostory.session
        )
        cls.strips[cls.live_photostory] = strip
        cls.photos[strip] = PhotoFactory(strip=strip)

    def test_list_trainings_dir(self):
        """Test trainings directory iterator"""

        self.setup_data_before_import()

        # 3 training directory are valid, 2 not
        self.assertEqual(
            sum(1 for i in os.listdir(self.PHOTOSTORY_PATH)),
            5
        )
        self.assertEqual(
            sum(1 for i in Photostory.objects.list_trainings_dir(self.PHOTOSTORY_PATH)),
            3
        )

    def test_list_sessions_dir(self):
        """Test sessions directory iterator"""

        self.setup_data_before_import()

        # 3 session directory are valid, 2 not
        self.assertEqual(
            sum(1 for i in os.listdir(self.first_training_path)),
            5
        )
        self.assertEqual(
            sum(1 for i in Photostory.objects.list_sessions_dir(self.first_training_path)),
            3
        )

    def test_list_photos_files(self):
        """Test photos files iterator"""

        self.setup_data_before_import()

        # 6 photo files (2 photos for each 3 trainee) are valid, 3 not
        self.assertEqual(
            sum(1 for i in os.listdir(self.first_training_session_path)),
            9
        )
        self.assertEqual(
            sum(1 for i in Photostory.objects.list_photos_files(self.first_training_session_path)),
            6
        )

    def test_import_photo_works(self):
        """Test photo import when data are valid"""

        # we clean the path
        shutil.rmtree(self.PHOTOSTORY_PATH, ignore_errors=True)

        # we create an existing dir with same name as first training id
        os.makedirs(self.first_training_session_path)

        # we create an existing photo file
        open(self.first_photo_path, 'a').close()

        photostory = PhotostoryFactory(
            training=None,
            session=self.first_training_session
        )

        # 0 strip and 0 photo
        self.assertEqual(
            photostory.strips.count(),
            0
        )
        self.assertEqual(
            Photo.objects.filter(strip__photostory=photostory).count(),
            0
        )

        # Now we import photo
        self.assertEqual(
            Photostory.objects.import_photo(
                photostory,
                self.first_training_session,
                self.trainee,
                self.first_photo_path
            ),
            True
        )

        # 1 strip is created
        self.assertEqual(
            photostory.strips.count(),
            1
        )
        strip = photostory.strips.all()[0]
        self.assertEqual(strip.photostory, photostory)
        self.assertEqual(strip.session, self.first_training_session)
        self.assertEqual(strip.owner, self.trainee)
        self.assertEqual(strip.state, Strip.STRIP_STATE.PUBLISHED)
        self.assertEqual(
            strip.name,
            '%s - %s %s' % (photostory.name, self.trainee.first_name, self.trainee.last_name)
        )
        # 1 photo is created in the strip
        self.assertEqual(
            strip.photos.count(),
            1
        )
        photo = strip.photos.all()[0]
        # File is copied
        new_photo_path = os.path.join(settings.MEDIA_ROOT, photo.image.path)
        self.assertEqual(
            os.path.exists(new_photo_path),
            True
        )
        self.assertEqual(
            os.path.exists(self.first_photo_path),
            True
        )

    def test_import_photo_not_works(self):
        """Test photo import when data are NOT valid"""

        photostory = PhotostoryFactory(
            training=None,
            session=self.first_training_session
        )

        # 0 strip and 0 photo
        self.assertEqual(
            photostory.strips.count(),
            0
        )
        self.assertEqual(
            Photo.objects.filter(strip__photostory=photostory).count(),
            0
        )

        # File not exist
        self.assertEqual(
            os.path.exists(self.not_exist_photo_path),
            False
        )

        # Now we import photo with a no existing file
        self.assertEqual(
            Photostory.objects.import_photo(
                photostory,
                self.first_training_session,
                self.trainee,
                self.not_exist_photo_path
            ),
            False
        )

        # 0 strip is created
        self.assertEqual(
            photostory.strips.count(),
            0
        )
        # 0 photo is created in the strip
        self.assertEqual(
            Photo.objects.filter(strip__photostory=photostory).count(),
            0
        )

    def test_export_photo_works(self):
        """Test photo export when data are valid"""

        # we clean the path
        shutil.rmtree(self.PHOTOSTORY_PATH, ignore_errors=True)

        photostory = PhotostoryFactory(training=self.first_training)
        strip = StripFactory(session=self.first_training_session, photostory=photostory)
        photo = PhotoFactory(strip=strip)

        photo_path = os.path.join(
            self.first_training_session_path,
            '_result_session_%s_ps_user_%s_%s_%i.jpg' % (
                strip.session_id,
                strip.owner_id,
                photo.created.strftime(self.DATE_FORMAT),
                0
            )
        )

        # File not exist yet
        self.assertEqual(
            os.path.exists(photo_path),
            False
        )

        # Now we export photo
        self.assertEqual(
            Photostory.objects.export_photo(
                self.PHOTOSTORY_PATH,
                strip,
                photo
            ),
            True
        )

        # File is created
        self.assertEqual(
            os.path.exists(photo_path),
            True
        )

        # Photo is not deleted
        self.assertEqual(
            Photo.objects.filter(id=photo.id).exists(),
            True
        )

        # Now, we want to export the same photo, but the file 0 is already created
        photo_path_new = os.path.join(
            self.first_training_session_path,
            '_result_session_%s_ps_user_%s_%s_%i.jpg' % (
                strip.session_id,
                strip.owner_id,
                photo.created.strftime(self.DATE_FORMAT),
                1
            )
        )

        # File not already exist with index 1
        self.assertEqual(
            os.path.exists(photo_path_new),
            False
        )

         # Now we export photo
        self.assertEqual(
            Photostory.objects.export_photo(
                self.PHOTOSTORY_PATH,
                strip,
                photo
            ),
            True
        )

        # File is created with index 1
        self.assertEqual(
            os.path.exists(photo_path_new),
            True
        )

        # Photo is not deleted
        self.assertEqual(
            Photo.objects.filter(id=photo.id).exists(),
            True
        )

    def test_export_photo_dest_dir_exist(self):
        """Test photo export when data are NOT valid"""

        # we clean the path
        shutil.rmtree(self.PHOTOSTORY_PATH, ignore_errors=True)

        # we create an existing dir with same name as first training id
        os.makedirs(self.first_training_path)

        # we create an existing file with same name as second training id
        open(self.second_training_session_path, 'a').close()

        # for the second session, we have already a file with the same name as dest dir
        photostory = PhotostoryFactory(training=self.first_training)
        strip = StripFactory(session=self.second_training_session, photostory=photostory)
        photo = PhotoFactory(strip=strip)

        # dest path is a file
        self.assertEqual(
            os.path.isfile(self.second_training_session_path),
            True
        )

        # Now we export photo
        # As dest path is file, that's not work
        self.assertEqual(
            Photostory.objects.export_photo(
                self.PHOTOSTORY_PATH,
                strip,
                photo
            ),
            False
        )

        # File is not created
        photo_path = os.path.join(
            self.first_training_session_path,
            '_result_session_%s_ps_user_%s_%s_%i.jpg' % (
                strip.session_id,
                strip.owner_id,
                photo.created.strftime(self.DATE_FORMAT),
                0
            )
        )
        self.assertEqual(
            os.path.exists(photo_path),
            False
        )

    def test_import_photostory_from(self):
        """Test old photostory directory import"""

        self.setup_data_before_import()

        Photostory.objects.import_photostory_from(self.PHOTOSTORY_PATH)

        # After import there is still wrong photo that are not imported
        # 2 photos with undefined UUID and one with wrong pattern
        self.assertEqual(
            sum(1 for i in os.listdir(self.first_training_session_path)),
            3
        )
        # All sessions are deleted except the first session
        self.assertEqual(
            sum(1 for i in Photostory.objects.list_sessions_dir(self.first_training_path)),
            1
        )
        self.assertEqual(os.path.exists(self.first_training_session_path), True)
        # and with the 2 wrong directories
        self.assertEqual(
            sum(1 for i in os.listdir(self.first_training_path)),
            3
        )
        # All trainings are deleted except the first training
        self.assertEqual(
            sum(1 for i in Photostory.objects.list_trainings_dir(self.PHOTOSTORY_PATH)),
            1
        )
        self.assertEqual(os.path.exists(self.first_training_path), True)
        # and with the 2 wrong directories
        self.assertEqual(
            sum(1 for i in os.listdir(self.PHOTOSTORY_PATH)),
            3
        )

        for training in self.trainings:
            for session in self.training_sessions[training]:
                # 1 photostory by session
                self.assertEqual(
                    Photostory.objects.filter(session=session).count(),
                    1
                )
                # 1 strip for each trainee
                self.assertEqual(
                    Strip.objects.filter(session=session).count(),
                    len(self.trainees)
                )
                # 2 photo for each strip
                for strip in Strip.objects.filter(session=session).all():
                    self.assertEqual(
                        strip.photos.count(),
                        2
                    )

            # 3 photostory for training, only one by session
            self.assertEqual(
                Photostory.objects.filter(session__training=training).count(),
                3
            )

    def test_export_photostory_from(self):
        """Test export of photostories in old photostory directory"""

        self.setup_data_before_export()

        Photostory.objects.export_photostory_to(self.PHOTOSTORY_PATH)

        # For the first training, we should still and only have strips of the second session
        # as they could not be exported
        # 2 strips * 1 session * 2 photostory * 1 training
        self.assertEqual(
            Strip.objects.filter(
                photostory__training=self.first_training,
                session=self.second_training_session
            ).count(),
            4
        )
        self.assertEqual(
            Strip.objects.filter(photostory__training=self.first_training).count(),
            4
        )
        # 2 photos by strips
        self.assertEqual(
            Photo.objects.filter(
                strip__session=self.second_training_session
            ).count(),
            8
        )
        # For the second training, we should still have all strips
        # and all photos of the three session as they could not be exported
        # 2 strips * 3 session * 2 photostory * 1 training
        self.assertEqual(
            Strip.objects.filter(photostory__training=self.second_training).count(),
            12
        )
        # 2 photos by strips
        self.assertEqual(
            Photo.objects.filter(strip__photostory__training=self.second_training).count(),
            24
        )
        # For the third training, we should have 0 strips and 0 photos
        # But still have 2 photostory
        training = self.trainings[2]
        self.assertEqual(
            Strip.objects.filter(photostory__training=training).count(),
            0
        )
        self.assertEqual(
            Photo.objects.filter(strip__photostory__training=training).count(),
            0
        )
        self.assertEqual(
            Photostory.objects.filter(training=training).count(),
            2
        )
        # For the live photostory we should have 0 strips, 0 photos and 0 photostory (as is live)
        self.assertEqual(
            Strip.objects.filter(photostory=self.live_photostory).count(),
            0
        )
        self.assertEqual(
            Photo.objects.filter(strip__photostory=self.live_photostory).count(),
            0
        )
        self.assertEqual(
            Photostory.objects.filter(pk=self.live_photostory).count(),
            0
        )
