"""This module holds models for ``photostories`` app."""

import os

from django.conf import settings
from django.core.urlresolvers import reverse
from django.db import models
from django.utils.translation import ugettext_lazy as _

from django_extensions.db.models import TimeStampedModel
from django_hashable_file_fields.hashers import with_default
from django_hashable_file_fields.models import HashableFileFieldsBaseModel
from extended_choices import Choices
from orderable.models import Orderable

from mtp.apps.broker_models_hooks.models import BrokerHookMixin
from mtp.apps.sync.models import Syncable
from mtp.apps.sync.managers import SyncableManager
from mtp.apps.trainings.models import TrainingActivity

from .exports import StripsArchive
from .managers import PhotostoryManager


class Photostory(TrainingActivity):
    """An photostory to create photostories."""

    verbose_activity_name = _('Photostory')

    objects = PhotostoryManager()

    def create_archive(self, session=None):
        """Create an archive containing strips of this photostory.

        Parameters
        ----------
        session: TrainingSession
            The session used to filter strips to archive.

        Returns
        -------
        StripsArchive

        """

        query = Photo.objects.non_deleted().select_related('strip').filter(strip__photostory=self)
        if session:
            query = query.filter(strip__session=session)

        strips = {}
        for photo in query:
            if not photo.strip in strips:
                strips[photo.strip] = []
            photo_path = os.path.join(settings.MEDIA_ROOT, photo.image.path)
            strips[photo.strip].append(photo_path)

        return StripsArchive(strips)

    @classmethod
    def get_training_create_url(cls, training):
        """Get the url to create a new photostory for the provided training

        Parameters
        ----------
        training: Training
            The training in which the photostory will be created.

        Returns
        -------
        str

        """

        return reverse('trainings:photostory-create', kwargs={
            'training_uuid': training.id
        })

    @classmethod
    def get_create_url(cls):
        """Get the url to create a new photostory

        Returns
        -------
        str

        """

        return reverse('photostories:photostory-create')

    def get_absolute_url(self):
        """Define the photostory absolute url to the detail url.

        Returns
        -------
        str

        """

        return self.get_detail_url()

    def get_detail_url(self):
        """Get the photostory detail url.

        Returns
        -------
        str

        """

        return reverse('photostories:photostory-detail', kwargs={
            'photostory_uuid': self.id
        })

    def get_edit_url(self):
        """Get the photostory edit url.

        Returns
        -------
        str

        """

        return reverse('photostories:photostory-edit', kwargs={
            'photostory_uuid': self.id
        })


class Strip(BrokerHookMixin, TimeStampedModel, Syncable):
    """A strip is composed of several photo."""

    STRIP_STATE = Choices(
        ('ONGOING', 'on', _('Ongoing')),
        ('PUBLISHED', 'pu', _('Published'))
    )

    photostory = models.ForeignKey(
        'Photostory',
        related_name='strips',
        verbose_name=_('photostory'),
        on_delete=models.CASCADE
    )
    name = models.CharField(
        max_length=150,
        verbose_name=_('name'),
    )
    session = models.ForeignKey(
        'trainings.TrainingSession',
        related_name='strips',
        verbose_name=_('session'),
        on_delete=models.CASCADE
    )
    owner = models.ForeignKey(
        settings.AUTH_USER_MODEL,
        related_name='strips',
        verbose_name=_('owner'),
        on_delete=models.CASCADE
    )
    state = models.CharField(
        choices=STRIP_STATE,
        default=STRIP_STATE.ONGOING,
        max_length=2,
        verbose_name=_('state'),
    )

    def __str__(self):
        return self.name

    def add_photo(self, photo):
        """Add a photo in this strip.

        Parameters
        ----------
        photo: InMemoryUploadedFile
            The photo to add in the strip.

        """

        Photo.objects.create(strip=self, image=photo)

    def has_photos(self):
        """Check if this strip contains photos.

        Returns
        -------
        bool

        """

        return self.photos.non_deleted().exists()

    def publish(self):
        """Publish this strip if it isn't already published."""

        if self.state != self.STRIP_STATE.PUBLISHED:
            self.state = self.STRIP_STATE.PUBLISHED
            self.save()

    def create_archive(self):
        """Create an archive containing this strip.

        Returns
        -------
        StripsArchive

        """

        photos_path = Photo.objects.non_deleted().filter(strip_id=self.id).values_list(
            'image', flat=True
        )

        return StripsArchive({
            self: [os.path.join(settings.MEDIA_ROOT, path) for path in photos_path]
        })

    def get_broker_topic(self):
        """Get the broker topic to publish the changes of this model

        Returns
        -------
        str

        """

        return '%s/photostories/%s/strips' % (
            self.session_id,
            self.photostory_id
        )

    def get_serialized_representation(self):
        """Get the serialized representation of the current strip

        Returns
        -------
        dict

        """

        # Avoid circular imports
        from mtp.apps.photostories.apiv2.serializers import StripSerializer

        return StripSerializer(self).data


class Photo(BrokerHookMixin, Orderable, TimeStampedModel, HashableFileFieldsBaseModel, Syncable):
    """A photo."""

    strip = models.ForeignKey(
        'Strip',
        related_name='photos',
        on_delete=models.CASCADE
    )
    image = models.ImageField(upload_to='photos', verbose_name=_('image'))
    image_hash = models.CharField(max_length=64, blank=True)

    hashable_fields = [('image', 'image_hash', with_default(settings.EMPTY_FILE_HASH))]

    sync_files_to_master = True

    # Needed because django take the first in the list of models, which is ``OrderableManager``
    # and we don't case, so we force the only one we want, which is ``SyncableManager``
    objects = SyncableManager()

    def __str__(self):
        return self.image.path

    def get_broker_topic(self):
        """Get the broker topic to publish the changes of this model

        Returns
        -------
        str

        """

        return '%s/photostories/%s/strips/%s/photos' % (
            self.strip.session_id,
            self.strip.photostory_id,
            self.strip_id
        )

    def get_serialized_representation(self):
        """Get the serialized representation of the current photo

        Returns
        -------
        dict

        """

        # Avoid circular imports
        from mtp.apps.photostories.apiv2.serializers import PhotoSerializer

        return PhotoSerializer(self).data
