"""
Preference models, queryset and managers that handle the logic for persisting preferences.
"""

from django.db import models
from django.db.models.query import QuerySet
from django.conf import settings
from django.utils.functional import cached_property
from django.utils.translation import gettext_lazy as _
from dynamic_preferences.registries import (
    preference_models,
    global_preferences_registry,
)
from .utils import update


class BasePreferenceModel(models.Model):

    """
    A base model with common logic for all preferences models.
    """

    #: The section under which the preference is declared
    section = models.CharField(
        max_length=150,
        db_index=True,
        blank=True,
        null=True,
        default=None,
        verbose_name=_("Section Name"),
    )

    #: a name for the preference
    name = models.CharField(_("Name"), max_length=150, db_index=True)

    #: a value, serialized to a string. This field should not be accessed directly, use :py:attr:`BasePreferenceModel.value` instead
    raw_value = models.TextField(_("Raw Value"), null=True, blank=True)

    class Meta:
        abstract = True
        app_label = "dynamic_preferences"

    @cached_property
    def preference(self):
        return self.registry.get(section=self.section, name=self.name, fallback=True)

    @property
    def verbose_name(self):
        return self.preference.get("verbose_name", self.preference.identifier)

    verbose_name.fget.short_description = _("Verbose Name")

    @property
    def help_text(self):
        return self.preference.get("help_text", "")

    help_text.fget.short_description = _("Help Text")

    def set_value(self, value):
        """
        Save serialized self.value to self.raw_value
        """
        self.raw_value = self.preference.serializer.serialize(value)

    def get_value(self):
        """
        Return deserialized self.raw_value
        """
        return self.preference.serializer.deserialize(self.raw_value)

    value = property(get_value, set_value)

    def save(self, **kwargs):

        if self.pk is None and not self.raw_value:
            self.value = self.preference.get("default")
        super(BasePreferenceModel, self).save(**kwargs)

    def __str__(self):
        return self.__repr__()

    def __repr__(self):
        return "{0} - {1}/{2}".format(self.__class__.__name__, self.section, self.name)


class GlobalPreferenceModel(BasePreferenceModel):

    registry = global_preferences_registry

    class Meta:
        unique_together = ("section", "name")
        app_label = "dynamic_preferences"

        verbose_name = _("Global preference")
        verbose_name_plural = _("Global preferences")


class PerInstancePreferenceModel(BasePreferenceModel):

    """For preferences that are tied to a specific model instance"""

    #: the instance which is concerned by the preference
    #: use a ForeignKey pointing to the model of your choice
    instance = None

    class Meta(BasePreferenceModel.Meta):
        unique_together = ("instance", "section", "name")
        abstract = True

    @classmethod
    def get_instance_model(cls):
        return cls._meta.get_field("instance").remote_field.model


global_preferences_registry.preference_model = GlobalPreferenceModel

# Create default preferences for new instances

from django.db.models.signals import post_save


def invalidate_cache(sender, created, instance, **kwargs):
    if not isinstance(instance, BasePreferenceModel):
        return
    registry = preference_models.get_by_preference(instance)
    linked_instance = getattr(instance, "instance", None)
    kwargs = {}
    if linked_instance:
        kwargs["instance"] = linked_instance

    manager = registry.manager(**kwargs)
    manager.to_cache(instance)


post_save.connect(invalidate_cache)
