"""This module holds the mixins of the ``import_export`` app."""

import io

from django.contrib import messages
from django.db import transaction
from django.http import HttpResponse
from django.template.response import TemplateResponse
from django.utils.translation import ugettext as _
from django.views.generic.edit import FormView

from mtp.apps.common.views_utils import RestrictToSuperuserOrTrainerMixin, ExportFileMixin
from mtp.apps.translation.exceptions import PoFileCheckError
from .exceptions import ImportDataError
from .forms import ImportForm
from .xls import XLSReader, XLSWriter


class XLSImportViewMixin(RestrictToSuperuserOrTrainerMixin, FormView):
    """This view allows to import data from a xls file through a form.
    There are three structures available to import data : details, tables and nested tables.
    You can find below examples for import data according to the structure of your xls file.

    Attributes
    ----------
    template_name : string
        Template name will be used for display page
    object : models.Model()
        Object created by import
    form_class : forms.Form
        Form class of the form used for import file

    Examples
    --------

    # To import 2 values:
    data_object = self.reader.read_values(first_sheet_name, 2)

    training_session = get_object_or_404(TrainingSession, pk=self.kwargs['session_id'])
    obj = self.create_object(
        Brainstormer, data_object,
        training_session=training_session)
    obj.save()

    # To import simple table:
    for group_data in self.reader.read_table(first_sheet_name):
        group, __ = BrainstormerPropositionGroup.objects.get_or_create(
            brainstormer=obj,
            **group_data
        )
        obj.groups.add(group)

    # To import nested table:
    for data in self.reader.read_table(first_sheet_name):
        try:
            group = self.create_object(BrainstormerPropositionGroup, data, brainstormer=obj)
            group.save()
            obj.groups.add(group)
        except AttributeError:
            proposition = self.create_object(BrainstormerProposition, data, group=group)
            proposition.save()

    Notes
    -----
    There are 3 structures we can import : values, simple table and nested table.
    - Values
        It's a table with 1 column and 2 rows (header + value). We can read this with `read_value()`
        method or read many values with `read_values(..., X)` method.
    - Simple table
        It's a table contains a same type of object like BrainstormerProposition. We can read it
        with `read_table()`.
    - Nested table
        It's a table contains a 2 types of object like BrainstormerGroup with nesting of
        BrainstormerProposition. We can read it with `read_table()`.

    According structures of your file, you have to adapt `import_data` with this methods.
    """

    template_name = 'import_export/import.html'
    response_class = TemplateResponse
    imported_object = None
    form_class = ImportForm

    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)
        self.reader = None
        self.file = None
        self.is_assessing = False

    def get_success_url(self):
        if self.imported_object:
            try:
                return self.imported_object.get_absolute_url()
            except AttributeError:
                return self.imported_object.get_detail_url()
        return self.request.META.get('HTTP_REFERER')

    def form_valid(self, form):
        """ Process when form is validated. """
        self.import_file(form.cleaned_data['file'])
        self.is_assessing = form.cleaned_data['is_assessing']
        try:
            # Encompass import data with transaction to make a rollback if a error is raised
            with transaction.atomic():
                self.imported_object = self.import_sheets()  # Create object with imported data
        except ImportDataError as error:
            messages.error(self.request, error)
            return self.render_to_response(self.get_context_data(form=form))
        except PoFileCheckError as po_file_check:
            for error in po_file_check.errors:
                messages.error(
                    self.request,
                    _("There is an error in the translation of \"%(msgid)s\" : %(error)s.") % {
                        'msgid': error[0].msgid,
                        'error': error[1]
                    }
                )
            return self.render_to_response(self.get_context_data(form=form))
        return super().form_valid(form)

    def import_file(self, file):
        """Create reader class with given file and initialize it."""
        self.file = file
        self.reader = XLSReader(self.file)
        self.reader.initialize()

    def import_sheets(self):
        """Read sheets of import file. By default, only first sheet is used."""
        return self.import_data(self.reader.first_sheet_name)

    def import_data(self, sheet_name):
        """Algorithm for import data. Currently, it is specific for each template file."""
        raise NotImplementedError

    # Pylint don't like this super call, but we don't have the choice to this hack works
    # We need to call the ``render_to_response`` method of ``FormView`` class
    # pylint: disable=bad-super-call
    def render_to_response(self, context, **response_kwargs):
        """Force the render to import form.

        Notes
        -----
        We need this hack because of this fuck**g compatibility with old translation template.
        We need export current data before to import new data, so the `render_to_response` in
        export view was used instead of this one = fuck**g conflict /o\

        """

        return super(FormView, self).render_to_response(context, **response_kwargs)


class XLSExportViewMixin(ExportFileMixin, RestrictToSuperuserOrTrainerMixin):
    """ Export View to XLS format.

    Attributes
    ----------
    resource : mtp.apps.import_export.Resource
        Resource to export.
    title : string
        Title wanted into export file.
    """

    response_class = HttpResponse

    resource = None
    title = None

    def __init__(self, *args, **kwargs):
        if self.resource:
            self.model = self.resource.get_model()
        super().__init__(*args, **kwargs)
        self.stream = io.BytesIO()
        self.writer = XLSWriter(self.stream)

    @property
    def get_title(self):
        """ Get title to use in the sheet. """
        if self.title:
            return self.title
        else:
            return 'Export %s' % self.model._meta.verbose_name

    def export_data(self, sheet, obj):
        """Algorithm for export data. Is specific for each template file."""
        self.writer.write_values(sheet, obj, self.resource)
        self.writer.write_nested_table(sheet, obj, self.resource)

    def create_sheets(self, context):
        """ Create sheets of export file. By default, only first sheet is used.

        Parameters
        ----------
        context : dict
            Data computed in ``get_context_data`` that may be useful to create the doc.

        Example
        -------
        >>> sheet_name = self.writer.first_sheet_name
        >>> sheet_name = self.writer.initialize(sheet_name, self.get_title)
        >>>> self.export_data(sheet_name, self.object)
        """
        raise NotImplementedError

    def create_export_file(self, context):
        """ Create XLS file. """
        self.create_sheets(context)
        self.writer.header_title = self.get_title
        self.writer.save()
        self.stream.seek(0)
        return self.stream.read()

    def get_filename(self):
        """Get the filename used to name the generated file.

        Returns
        -------
        str
            The wanted filename.

        """

        if hasattr(self, 'object'):
            return str(self.object)

        if hasattr(self, 'model'):
            return self.model._meta.verbose_name

        return self.title

    def render_to_response(self, context, **response_kwargs):
        """Render the XSL file for downloading.

        Parameters
        ----------
        context : dict
            The context from ``get_context_data``
        **response_kwargs : dict
            Named arguments to pass to the response constructor

        """

        response = self.response_class(
            content_type='application/vnd.openxmlformats-officedocument.spreadsheetml.sheet',
            **response_kwargs
        )

        content = 'attachment; filename=%s.xlsx' % self.get_slugified_filename()
        response['Content-Disposition'] = content

        response.write(self.create_export_file(context))
        return response
