import logging

from django.db import models
from django.db.models.signals import post_save, pre_delete
from django.dispatch import receiver
from django.utils.functional import cached_property
from django.utils.module_loading import import_string
from django.utils.translation import gettext_lazy as _
from odin.mapping import FieldMapping

from .validators import validate_importstring, validate_list

logger = logging.getLogger(__name__)

_mapping_classes = {}


class MappingModel(models.Model):
    contenttype = models.ForeignKey(
        "contenttypes.ContentType", on_delete=models.CASCADE, unique=True
    )

    date_created = models.DateTimeField(_("Date Created"), auto_now_add=True)
    date_updated = models.DateTimeField(_("Date Updated"), auto_now=True, db_index=True)

    def mapping_class(self, base_class=None, **attrs):
        if base_class is None:
            # oscar_odin.mappings.__init__.py causes models being imported before django is ready
            # so that's why we import it here instead.
            from oscar_odin.mappings.common import OscarBaseMapping

            base_class = OscarBaseMapping

        attrs.setdefault("from_obj", base_class.from_obj)
        attrs.setdefault("to_obj", base_class.to_obj)

        if self.contenttype.pk not in _mapping_classes:
            if hasattr(base_class, "get_model_mapping_rules"):
                dynamic_mappings = base_class.get_model_mapping_rules(self)
            else:
                dynamic_mappings = [fm.as_fieldmapping() for fm in self.mappings.all()]

            classname = "%sDynamic%s" % (
                self.contenttype.model.title(),
                base_class.__name__,
            )

            # pylint: disable=unused-argument
            def get_mapping_model(other):
                self.refresh_from_db()
                return self

            mapping_meta_class = base_class.__class__
            _mapping_classes[self.contenttype.pk] = mapping_meta_class(
                classname,
                tuple([base_class]),
                dict(
                    mappings=dynamic_mappings,
                    get_mapping_model=get_mapping_model,
                    **attrs,
                ),
            )

        return _mapping_classes[self.contenttype.pk]


class FieldMappingModel(models.Model):
    mapping = models.ForeignKey(
        MappingModel, on_delete=models.CASCADE, related_name="mappings"
    )
    from_fields = models.JSONField(validators=[validate_list])
    to_fields = models.JSONField(validators=[validate_list])
    action_importstring = models.CharField(
        _("Action"),
        max_length=255,
        null=True,
        blank=True,
        validators=[validate_importstring],
    )
    to_list = models.BooleanField(default=False)
    bind = models.BooleanField(default=False)
    skip_if_none = models.BooleanField(default=True)

    def __str__(self):
        return "(%s -> %s)" % (self.from_fields, self.to_fields)

    @classmethod
    def simple_mapping(cls, from_field, to_field, **kwargs):
        return cls(from_fields=[from_field], to_fields=[to_field], **kwargs)

    @cached_property
    def action(self):
        if self.action_importstring:
            try:
                return import_string(self.action_importstring)
            except ImportError as e:
                logger.exception(e)

    def as_fieldmapping(self):
        return FieldMapping(
            self.from_fields,
            self.action,
            self.to_fields,
            self.to_list,
            self.bind,
            self.skip_if_none,
        )


# pylint: disable=unused-argument
@receiver([post_save, pre_delete], sender=FieldMappingModel)
def force_renew_mapping_class(sender, instance, **kwargs):
    try:
        del _mapping_classes[instance.mapping.contenttype.pk]
    except KeyError:
        pass
