from django.core.exceptions import ValidationError
from django.db.utils import DatabaseError
from django.utils.translation import gettext
from odin.mapping import FieldMapping, MappingBase, MappingMeta
from odin.resources import ResourceBase

from oscar.core.loading import get_model

ProductAttribute = get_model("catalogue", "ProductAttribute")


class MappingModelProductAttributesMapping(MappingBase, metaclass=MappingMeta):
    "Baseclass for dynamic mappings that need to map to ProductResource.attributes"

    register_mapping = False
    product_classes = None
    MAP_ATTRIBUTES_ACTION = "ocyan.plugin.oscar_odin.actions.to_product_attribute"
    action_choices = [
        (
            MAP_ATTRIBUTES_ACTION,
            gettext("To product attribute"),
        ),
    ]
    from_obj = ResourceBase
    to_obj = ResourceBase

    exclude_fields = []
    _subs = {}

    @classmethod
    def get_model_mapping_rules(cls, mapping_model):
        "Filter out the mapping that have to_product_attribute as the action, they are markers only"
        return [
            fm.as_fieldmapping()
            for fm in mapping_model.mappings.exclude(
                action_importstring=cls.MAP_ATTRIBUTES_ACTION
            )
        ]

    @classmethod
    def get_extra_to_obj_field_choices(cls):
        "Add some extra choices to to_fields based on the product attributes"
        if cls.product_classes is None:
            raise ValidationError(
                "%s needs to define product_classes attribute to eb able to determine attribute field choiced"
                % cls
            )

        try:
            return sorted(
                ProductAttribute.objects.values_list("code", "name")
                .filter(product_class__in=cls.product_classes)
                .distinct()
            )
        except DatabaseError:  # this can be called when the migration haven't completed
            return []

    @classmethod
    def clean_form(cls, form):
        "Clean the FieldMappingModel form when aditing the mapping in wagtail"
        attributes = set(dict(cls.get_extra_to_obj_field_choices()))
        to_fields = form.cleaned_data["to_fields"]
        action = form.cleaned_data["action_importstring"]
        for to_obj in to_fields:
            if to_obj in attributes and action != cls.MAP_ATTRIBUTES_ACTION:
                raise ValidationError(
                    gettext(
                        "Action must be set to product attibute when selecting a product attribute"
                    )
                )

    def get_mapping_model(self):
        "This method will be provided by MappingModel.mapping_class"
        return None

    def collect_mapped_product_attributes(self):
        "Get the full attributes dictionary with all the mapped attributes"
        result = {}

        # pylint: disable=assignment-from-none
        mapping_model = self.get_mapping_model()
        if mapping_model is not None:
            for field_mapping in mapping_model.mappings.filter(
                action_importstring="ocyan.plugin.oscar_odin.actions.to_product_attribute"
            ):
                from_values = tuple(
                    getattr(self.source, f) for f in field_mapping.from_fields
                )
                for to_field, from_value in zip(field_mapping.to_fields, from_values):
                    result[to_field] = from_value

        return result

    # add the rule for mapping the full attributes dictionary
    _mapping_rules = [
        FieldMapping(
            None, collect_mapped_product_attributes, ("attributes",), False, True, False
        )
    ]
