import json

from django.core.serializers.json import DjangoJSONEncoder
from django.db import models


class CastOnAssignDescriptor:
    """
    A property descriptor which ensures that `field.to_python()` is called on _every_ assignment to the field.
    This used to be provided by the `django.db.models.subclassing.Creator` class, which in turn
    was used by the deprecated-in-Django-1.10 `SubfieldBase` class, hence the reimplementation here.
    """

    def __init__(self, field):
        self.field = field

    def __get__(self, obj, type=None):
        if obj is None:
            return self
        return obj.__dict__[self.field.name]

    def __set__(self, obj, value):
        obj.__dict__[self.field.name] = self.field.to_python(value)


class JSONField(models.TextField):
    # Used so to_python() is called
    def __init__(self, *args, **kwargs):
        self.dump_kwargs = kwargs.pop("dump_kwargs", {"cls": DjangoJSONEncoder})
        self.load_kwargs = kwargs.pop("load_kwargs", {})

        super().__init__(*args, **kwargs)

    def to_python(self, value):
        if value is None or value == "":
            return {}
        elif isinstance(value, str):
            return json.loads(value, **self.load_kwargs)
        else:
            return value

    def get_db_prep_value(self, value, connection, prepared=False):
        """Convert JSON object to a string"""
        if isinstance(value, str):
            return value
        return json.dumps(value, **self.dump_kwargs)

    def contribute_to_class(self, cls, name):
        super().contribute_to_class(cls, name)
        setattr(cls, name, CastOnAssignDescriptor(self))
