from __future__ import unicode_literals

import base64
import logging
import os
import time

from furl import furl

from django.core.files import File
from django.core.files.base import ContentFile
from django.urls import reverse
from django.utils.encoding import force_text, python_2_unicode_compatible
from django.utils.six.moves.urllib.parse import quote_plus, unquote_plus

from mayan.apps.converter import TransformationResize, converter_class

from .storages import storage_staging_file_image_cache

logger = logging.getLogger(__name__)


class PseudoFile(File):
    def __init__(self, file, name):
        self.name = name
        self.file = file
        self.file.seek(0, os.SEEK_END)
        self.size = self.file.tell()
        self.file.seek(0)


class SourceUploadedFile(File):
    def __init__(self, source, file, extra_data=None):
        self.file = file
        self.source = source
        self.extra_data = extra_data


@python_2_unicode_compatible
class StagingFile(object):
    """
    Simple class to extend the File class to add preview capabilities
    files in a directory on a storage
    """
    def __init__(self, staging_folder, filename=None, encoded_filename=None):
        self.staging_folder = staging_folder
        if encoded_filename:
            self.encoded_filename = str(encoded_filename)
            self.filename = base64.urlsafe_b64decode(
                unquote_plus(self.encoded_filename)
            ).decode('utf8')
        else:
            self.filename = filename
            self.encoded_filename = quote_plus(base64.urlsafe_b64encode(
                filename.encode('utf8')
            ))

    def __str__(self):
        return force_text(self.filename)

    def as_file(self):
        return File(
            file=open(self.get_full_path(), mode='rb'), name=self.filename
        )

    @property
    def cache_filename(self):
        return '{}{}'.format(self.staging_folder.pk, self.encoded_filename)

    def delete(self):
        storage_staging_file_image_cache.delete(self.cache_filename)
        os.unlink(self.get_full_path())

    def generate_image(self, *args, **kwargs):
        transformation_list = self.get_combined_transformation_list(*args, **kwargs)

        # Check is transformed image is available
        logger.debug('transformations cache filename: %s', self.cache_filename)

        if storage_staging_file_image_cache.exists(self.cache_filename):
            logger.debug(
                'staging file cache file "%s" found', self.cache_filename
            )
        else:
            logger.debug(
                'staging file cache file "%s" not found', self.cache_filename
            )
            image = self.get_image(transformations=transformation_list)
            with storage_staging_file_image_cache.open(self.cache_filename, 'wb+') as file_object:
                file_object.write(image.getvalue())

        return self.cache_filename

    def get_api_image_url(self, *args, **kwargs):
        final_url = furl()
        final_url.args = kwargs
        final_url.path = reverse(
            'rest_api:stagingfolderfile-image-view', args=(
                self.staging_folder.pk,
                self.encoded_filename
            )
        )

        return final_url.tostr()

    def get_combined_transformation_list(self, *args, **kwargs):
        """
        Return a list of transformation containing the server side
        staging file transformation as well as tranformations created
        from the arguments as transient interactive transformation.
        """
        # Convert arguments into transformations
        transformations = kwargs.get('transformations', [])

        # Set sensible defaults if the argument is not specified or if the
        # argument is None
        width = self.staging_folder.preview_width
        height = self.staging_folder.preview_height

        # Generate transformation hash
        transformation_list = []

        # Interactive transformations second
        for transformation in transformations:
            transformation_list.append(transformation)

        if width:
            transformation_list.append(
                TransformationResize(width=width, height=height)
            )

        return transformation_list

    def get_date_time_created(self):
        return time.ctime(os.path.getctime(self.get_full_path()))

    def get_full_path(self):
        return os.path.join(self.staging_folder.folder_path, self.filename)

    def get_image(self, transformations=None):
        cache_filename = self.cache_filename
        file_object = None

        try:
            file_object = open(self.get_full_path(), mode='rb')
            converter = converter_class(file_object=file_object)

            page_image = converter.get_page()

            # Since open "wb+" doesn't create files, check if the file
            # exists, if not then create it
            if not storage_staging_file_image_cache.exists(cache_filename):
                storage_staging_file_image_cache.save(name=cache_filename, content=ContentFile(content=''))

            with storage_staging_file_image_cache.open(cache_filename, 'wb+') as file_object:
                file_object.write(page_image.getvalue())
        except Exception as exception:
            # Cleanup in case of error
            logger.error(
                'Error creating staging file cache "%s"; %s',
                cache_filename, exception
            )
            storage_staging_file_image_cache.delete(cache_filename)
            if file_object:
                file_object.close()
            raise

        for transformation in transformations:
            converter.transform(transformation=transformation)

        result = converter.get_page()
        file_object.close()
        return result
