import datetime
import uuid

from io import BytesIO, StringIO
from zipfile import ZipFile, BadZipFile

from django.contrib import messages
from django.core.files import File
from django.http import HttpResponseRedirect, HttpResponse
from django.urls import reverse
from django.utils.formats import date_format
from django.utils.translation import gettext as _
from cookbook.models import Keyword


class Integration:
    request = None
    keyword = None
    files = None

    def __init__(self, request):
        """
        Integration for importing and exporting recipes
        :param request: request context of import session (used to link user to created objects)
        """
        self.request = request
        self.keyword = Keyword.objects.create(
            name=f'Import {date_format(datetime.datetime.now(), "DATETIME_FORMAT")}.{datetime.datetime.now().strftime("%S")}',
            description=f'Imported by {request.user.get_user_name()} at {date_format(datetime.datetime.now(), "DATETIME_FORMAT")}',
            icon='📥'
        )

    def do_export(self, recipes):
        """
        Perform the export based on a list of recipes
        :param recipes: list of recipe objects
        :return: HttpResponse with a ZIP file that is directly downloaded
        """
        export_zip_stream = BytesIO()
        export_zip_obj = ZipFile(export_zip_stream, 'w')

        for r in recipes:
            if r.internal:
                recipe_zip_stream = BytesIO()
                recipe_zip_obj = ZipFile(recipe_zip_stream, 'w')

                recipe_stream = StringIO()
                filename, data = self.get_file_from_recipe(r)
                recipe_stream.write(data)
                recipe_zip_obj.writestr(filename, recipe_stream.getvalue())
                recipe_stream.close()

                try:
                    recipe_zip_obj.write(r.image.path, 'image.png')
                except ValueError:
                    pass

                recipe_zip_obj.close()
                export_zip_obj.writestr(str(r.pk) + '.zip', recipe_zip_stream.getvalue())

        export_zip_obj.close()

        response = HttpResponse(export_zip_stream.getvalue(), content_type='application/force-download')
        response['Content-Disposition'] = 'attachment; filename="export.zip"'
        return response

    def import_file_name_filter(self, zip_info_object):
        """
        Since zipfile.namelist() returns all files in all subdirectories this function allows filtering of files
        If false is returned the file will be ignored
        By default all files are included
        :param zip_info_object: ZipInfo object
        :return: Boolean if object should be included
        """
        return True

    def do_import(self, files):
        """
        Imports given files
        :param files: List of in memory files
        :return: HttpResponseRedirect to the recipe search showing all imported recipes
        """
        try:
            self.files = files
            for f in files:
                if '.zip' in f.name:
                    import_zip = ZipFile(f.file)
                    for z in import_zip.filelist:
                        if self.import_file_name_filter(z):
                            recipe = self.get_recipe_from_file(BytesIO(import_zip.read(z.filename)))
                            recipe.keywords.add(self.keyword)
                    import_zip.close()
                else:
                    recipe = self.get_recipe_from_file(f.file)
                    recipe.keywords.add(self.keyword)
        except BadZipFile:
            messages.add_message(self.request, messages.ERROR, _('Importer expected a .zip file. Did you choose the correct importer type for your data ?'))

        return HttpResponseRedirect(reverse('view_search') + '?keywords=' + str(self.keyword.pk))

    @staticmethod
    def import_recipe_image(recipe, image_file):
        """
        Adds an image to a recipe naming it correctly
        :param recipe: Recipe object
        :param image_file: ByteIO stream containing the image
        """
        recipe.image = File(image_file, name=f'{uuid.uuid4()}_{recipe.pk}.png')
        recipe.save()

    def get_recipe_from_file(self, file):
        """
        Takes any file like object and converts it into a recipe
        :param file: ByteIO or any file like object, depends on provider
        :return: Recipe object
        """
        raise NotImplementedError('Method not implemented in storage integration')

    def get_file_from_recipe(self, recipe):
        """
        Takes a recipe object and converts it to a string (depending on the format)
        returns both the filename of the exported file and the file contents
        :param recipe: Recipe object that should be converted
        :returns:
            - name - file name in export
            - data - string content for file to get created in export zip
        """
        raise NotImplementedError('Method not implemented in storage integration')
