"""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 import translation
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.

        For the parameters, see ``django.forms.generic.edit.FormMixin``

        Notes
        -----
        We force the export with english translations to standardize template.
        Without this standardization, errors could be raised according to the activated language,
        because model verbose name are translatable.
        So we must define a standard language, so that each template works on each platform
        regardless of the activated language.
        """

        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(), translation.override('en'):
                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.

        For the parameters, see ``django.views.generic.View``

        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.

        Notes
        -----
        We force the export with english translations to standardize template.
        Without this standardization, errors could be raised according to the activated language,
        because model verbose name are translatable.
        So we must define a standard language, so that each template works on each platform
        regardless of the activated language.

        """

        with translation.override('en'):
            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
