"""This module contains export mixins for the ``translation`` app."""

from openpyxl.formatting import CellIsRule

from django.contrib.contenttypes.models import ContentType
from django.core.exceptions import ObjectDoesNotExist

from mtp.apps.import_export.resources import get_resource
from mtp.apps.import_export.stylexlsx import StyleXlsx
from mtp.apps.import_export.views import XLSExportViewMixin
from mtp.apps.workshops.models import WorkshopStepQuestionAnswer

from ..constants import COLUMNS_TO_HIDE, FORBIDDEN, HEADERS, NO_TRANSLATION


class TranslationExportViewMixin(XLSExportViewMixin):
    """Mixin to export translation of translatable object.

    Notes
    -----
    To use this mixin, you would define a ``create_sheets`` method and this method must to call
    ``export_data`` which export an object.

    Example
    -------
    You can see ``TranslationExportDetailView`` or ``TranslationExportListView`` class like example.
    """

    style = None

    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)
        self.translated_object = []
        self.style = StyleXlsx()
        self.app_label = None
        self.current_model = None

    def create_sheets(self, context):
        """Create sheets of export file.

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

        Notes
        -----
        This method return a ``NotImplementedError`` error.
        It must be implemented in child class.

        """

        raise NotImplementedError

    @staticmethod
    def get_parent(obj):
        """Get the parent version of provided object.

        Parameters
        ----------
        obj : object
            The object which we seek the parent version.
        """

        try:
            parent = obj.parent_version
            return parent if parent else obj
        except AttributeError:
            return obj

    def generate_rows_of_object(self, obj):
        """Generate a data row of object when translatable fields are available.

        Parameters
        ----------
        obj : object
            The object which be parsed to extract translatable field.
        """
        app_label, model_name = obj._meta.app_label, obj._meta.model_name
        content_type = ContentType.objects.get(app_label=app_label, model=model_name)
        fields = self.get_fields_to_translate(obj)

        rows = []
        for field in fields:
            context = model_name + ' - ' + str(obj)
            parent = self.get_parent(obj)

            parent_text, obj_text = getattr(parent, field.name), getattr(obj, field.name)
            color_conditions = []
            if parent_text == obj_text:
                target_text = NO_TRANSLATION
                color_conditions += [CellIsRule(operator='equal', formula=['"%s"' % NO_TRANSLATION],
                                                stopIfTrue=True, font=self.style.RED_FONT)]
            else:
                target_text = obj_text.replace("\"", "\'")
                color_conditions += [CellIsRule(operator='equal',
                                                formula=['"%s"' % target_text],
                                                stopIfTrue=True, font=self.style.GREEN_FONT)]

            text_to_translate = getattr(parent, field.name)
            if text_to_translate == "":
                text_to_translate = " "

            if isinstance(obj, WorkshopStepQuestionAnswer) and obj.is_correct:
                cell_style = 'GREEN_PROTECTED_DEFAULT_TABLE'
            else:
                cell_style = 'PROTECTED_DEFAULT_TABLE'

            max_length = None
            if hasattr(field, 'max_length'):
                max_length = field.max_length

            row = [
                {'value': content_type.id, 'style': 'PROTECTED_DEFAULT_TABLE'},
                {'value': obj.id, 'style': 'PROTECTED_DEFAULT_TABLE'},
                {'value': context, 'style': cell_style},
                {'value': field.name, 'style': cell_style},
                {'value': text_to_translate, 'style': cell_style},
                {
                    'value': target_text,
                    'style': 'EDITABLE_DEFAULT_TABLE',
                    'conditions': color_conditions,
                    'max_length': max_length
                }
            ]
            rows.append(row)
        return rows

    def check_context_object(self, obj):
        """Check if object is already in the context.

        With the generic parsing, there are risks that the objects are treated be outside the
        context of the parent object.
        This method check provided object is already in the good scope.

        Parameters
        ----------
        obj : object
            The analysed object.
        """
        app_label_check = self.app_label == obj._meta.app_label
        current_model_check = self.current_model != obj._meta.model_name
        return app_label_check and current_model_check and not obj.is_deleted

    def parse_objects_to_export_translation(self, sheet_name, obj):
        """Parse sub-objects of main object.

        Parameters
        ----------
        obj : object
            The object which be parsed other object to extract.
        sheet_name : str
            The name of sheet where etracted data will be writing.
        """
        for fk_to_translate in self.get_fk_to_translate(obj):
            if fk_to_translate.one_to_one or fk_to_translate.many_to_one:
                try:
                    obj_to_translate = getattr(obj, fk_to_translate.name)
                    if self.check_context_object(obj_to_translate):
                        self.process_to_export_obj(sheet_name, obj_to_translate)
                except (ObjectDoesNotExist, AttributeError):  # Not object available or None
                    pass
            else:
                try:
                    queryset = getattr(obj, fk_to_translate.name)
                except AttributeError:
                    queryset = getattr(obj, fk_to_translate.name + "_set")
                for obj_to_translate in queryset.all():
                    if self.check_context_object(obj_to_translate):
                        self.process_to_export_obj(sheet_name, obj_to_translate)

    def process_to_export_obj(self, sheet_name, obj, enable_nested=True):
        """Process to export data of an object.

        Parameters
        ----------
        obj : object
            The object which will be exported.
        sheet_name : str
            The name of sheet where etracted data will be writing.
        enable_nested : Optiona[bool]
            Enable if we seek nested objects in main object.
            By default, it's True.

        Notes
        -----
        And this sub-objects too if enable_nested is activate.
        """
        if obj not in self.translated_object:
            rows = self.generate_rows_of_object(obj)
            self.translated_object.append(obj)
            self.writer.write_rows(rows, sheet_name)
            if enable_nested:
                self.parse_objects_to_export_translation(sheet_name, obj)

    def export_data(self, sheet_name, obj, enable_nested=True):
        """Export translation for an object, an object is an activity in this context.

        Parameters
        ----------
        obj : object
            The object which will be exported.
        sheet_name : str
            The name of sheet where etracted data will be writing.
        enable_nested : Optiona[bool]
            Enable if we seek nested objects in main object.
            By default, it's True.
        """
        row = self.writer.create_headers(HEADERS)
        self.writer.write_row(row, sheet_name)

        self.app_label, self.current_model = obj._meta.app_label, obj._meta.model_name
        self.process_to_export_obj(sheet_name, obj, enable_nested)

        sheet = self.writer.workbook[sheet_name]
        self.style.hide_columns(sheet, COLUMNS_TO_HIDE)

    def get_fields_to_translate(self, obj):
        """Get translatable fields of an object.

        Parameters
        ----------
        obj : object
            The object we look for translatable fields.
        """
        fields_to_translate = []
        try:
            resource = get_resource(obj._meta.model)
        except KeyError:  # If resource not exists for this model
            return []
        for field in obj._meta.get_fields():
            if field.name in resource.fields_to_translate:
                fields_to_translate.append(field)
        return fields_to_translate

    def get_fk_to_translate(self, obj):
        """Get fields which allows to get sub-objects.

        Parameters
        ----------
        obj : object
            The object we look for fields containing child objects.
        """

        model = obj._meta.model
        model_fields = obj._meta.get_fields()

        fk_to_translate = []

        # old "get_all_related_objects"
        # cf https://docs.djangoproject.com/en/1.11/ref/models/meta/#migrating-old-meta-api
        rel_objs = [
            field for field in model_fields
            if (field.one_to_many or field.one_to_one)
            and field.auto_created and not field.concrete
        ]
        fields = [field for field in model_fields if field not in rel_objs]

        for field in fields:
            resource = get_resource(model)
            if field.is_relation and field.name in resource.fk_fields_to_translate:
                fk_to_translate.append(field)

        for rel_obj in rel_objs:
            rel_resource = get_resource(rel_obj.related_model)
            translatable = rel_resource.fk_fields_to_translate or rel_resource.fields_to_translate
            if rel_obj.name not in FORBIDDEN and (rel_obj.one_to_one or translatable):
                fk_to_translate.append(rel_obj)
        return fk_to_translate
