import os
import threading

from django.conf import settings
from django.core.files.storage import default_storage
from django.db import models
from django.db.models.signals import pre_delete
from django.dispatch.dispatcher import receiver

from .settings import (FLOWJS_AUTO_DELETE_CHUNKS,
                       FLOWJS_JOIN_CHUNKS_IN_BACKGROUND, FLOWJS_PATH,
                       FLOWJS_REMOVE_FILES_ON_DELETE, FLOWJS_WITH_CELERY)
from .signals import file_is_ready, file_joining_failed, file_upload_failed
from .utils import chunk_upload_to, guess_filetype


class FlowFile(models.Model):
    """
    A file upload through Flow.js
    """
    STATE_UPLOADING = 1
    STATE_COMPLETED = 2
    STATE_UPLOAD_ERROR = 3
    STATE_JOINING = 4
    STATE_JOINING_ERROR = 5

    STATE_CHOICES = [
        (STATE_UPLOADING, "Uploading"),
        (STATE_COMPLETED, "Completed"),
        (STATE_UPLOAD_ERROR, "Upload Error"),
        (STATE_JOINING, "Joining chunks"),
        (STATE_JOINING_ERROR, "Joining chunks error"),
    ]

    # identification and file details
    identifier = models.SlugField(max_length=255, unique=True, db_index=True)
    original_filename = models.CharField(max_length=200)
    final_file = models.FileField(
        upload_to=chunk_upload_to, max_length=255, null=True, blank=True)
    total_size = models.IntegerField(default=0)
    total_chunks = models.IntegerField(default=0)

    # current state
    total_chunks_uploaded = models.IntegerField(default=0)
    state = models.IntegerField(choices=STATE_CHOICES, default=STATE_UPLOADING)
    created = models.DateTimeField(auto_now_add=True)
    updated = models.DateField(auto_now=True)

    def __unicode__(self):
        return self.identifier

    def update(self):
        self.total_chunks_uploaded = self.chunks.count()
        super(FlowFile, self).save()
        self.join_chunks()

    @property
    def extension(self):
        """
        Return the extension of the upload file
        """
        name, ext = os.path.splitext(self.original_filename)
        return ext

    @property
    def chunks(self):
        return FlowFileChunk.objects.filter(chunks=self)

    @property
    def filename(self):
        """
        Return the unique filename generated based on the identifier
        """
        return '%s%s' % (self.identifier, self.extension)

    @property
    def file(self):
        """
        Return the uploaded file
        """
        if self.state == self.STATE_COMPLETED:
            return default_storage.open(self.path)
        return None

    @property
    def path(self):
        """
        Return the path of the file uploaded
        """
        return os.path.join(FLOWJS_PATH, self.filename)

    @property
    def url(self):
        """
        Return the path of the file uploaded
        """
        return os.path.join(settings.MEDIA_URL, FLOWJS_PATH, self.filename)

    def get_chunk_filename(self, number):
        """
        Return the filename of the chunk based on the identifier and chunk number
        """
        return '%s-%s.tmp' % (self.identifier, number)

    @property
    def join_in_background(self):
        """
        Check if the file should be joined in background
        """
        if not hasattr(self, '_join_in_background'):
            filetype = guess_filetype(self.original_filename)
            self._join_in_background = FLOWJS_JOIN_CHUNKS_IN_BACKGROUND == 'all' \
                or (FLOWJS_JOIN_CHUNKS_IN_BACKGROUND == 'media' and filetype in ['audio', 'video'])
        return self._join_in_background

    def join_chunks(self):
        """
        Join all the chucks in one file
        """
        if self.state == self.STATE_UPLOADING and self.total_chunks_uploaded == self.total_chunks:
            if self.join_in_background:
                self.state = self.STATE_JOINING
                super(FlowFile, self).save()
                if FLOWJS_WITH_CELERY:
                    from tasks import join_chunks_task
                    join_chunks_task.delay(self)
                else:
                    t = threading.Thread(target=self._join_chunks)
                    t.setDaemon(True)
                    t.start()
            else:
                self._join_chunks()

    def _join_chunks(self):
        try:
            temp_file = default_storage.open(self.path, 'wb')
            for chunk in self.chunks.all():
                chunk_bytes = chunk.file.read()
                temp_file.write(chunk_bytes)
            temp_file.close()
            self.final_file = self.path
            self.state = self.STATE_COMPLETED
            super(FlowFile, self).save()

            # send ready signal
            if self.join_in_background:
                file_is_ready.send(self)

            if FLOWJS_AUTO_DELETE_CHUNKS:
                self.delete_chunks()
        except Exception as e:
            self.state = self.STATE_JOINING_ERROR
            super(FlowFile, self).save()

            # send error signal
            if self.join_in_background:
                file_joining_failed.send(self)

    def delete_chunks(self):
        if FLOWJS_WITH_CELERY:
            from tasks import delete_chunks_task
            delete_chunks_task.delay(self)
        else:
            t = threading.Thread(target=self._delete_chunks)
            t.setDaemon(True)
            t.start()

    def _delete_chunks(self):
        for chunk in self.chunks.all():
            chunk.delete()

    def is_valid_session(self, session):
        """
        Check if a session id is the same that uploaded the file
        """
        return self.identifier.startswith(session)


class FlowFileChunk(models.Model):
    """
    A chunk is part of the file uploaded
    """
    class Meta:
        ordering = ['number']

    # identification and file details
    parent = models.ForeignKey(FlowFile, related_name="chunks")
    file = models.FileField(max_length=255, upload_to=chunk_upload_to)
    number = models.IntegerField()
    created_at = models.DateTimeField(auto_now_add=True)

    def __unicode__(self):
        return self.filename

    @property
    def filename(self):
        return self.parent.get_chunk_filename(self.number)

    def save(self, *args, **kwargs):
        super(FlowFileChunk, self).save(*args, **kwargs)
        self.parent.update()


@receiver(pre_delete, sender=FlowFile)
def flow_file_delete(sender, instance, **kwargs):
    """
    Remove files on delete if is activated in settings
    """
    if FLOWJS_REMOVE_FILES_ON_DELETE:
        default_storage.delete(instance.path)


@receiver(pre_delete, sender=FlowFileChunk)
def flow_file_chunk_delete(sender, instance, **kwargs):
    """
    Remove file when chunk is deleted
    """
    instance.file.delete(False)