from django.core.exceptions import ObjectDoesNotExist
from django.db.models import ForeignKey


def clone_object(obj, attrs=None):
    """
    Copied from https://stackoverflow.com/questions/61584535/django-clone-the-recursive-objects
    :param obj:
    :param attrs:
    :return:
    """
    if not attrs:
        attrs = {}

    # we start by building a "flat" clone
    clone = obj._meta.model.objects.get(pk=obj.pk)
    clone.pk = None

    # if caller specified some attributes to be overridden,
    # use them
    for key, value in attrs.items():
        setattr(clone, key, value)

    # save the partial clone to have a valid ID assigned
    clone.save()

    # Scan field to further investigate relations
    fields = clone._meta.get_fields()
    for field in fields:
        # Manage M2M fields by replicating all related records
        # found on parent "obj" into "clone"
        if not field.auto_created and field.many_to_many:
            for row in getattr(obj, field.name).all():
                getattr(clone, field.name).add(row)

        # Manage 1-N and 1-1 relations by cloning child objects
        if field.auto_created and field.is_relation:
            if field.many_to_many:
                # do nothing
                pass
            else:
                # provide "clone" object to replace "obj"
                # on remote field
                attrs = {field.remote_field.name: clone}
                children = field.related_model.objects.filter(**{field.remote_field.name: obj})
                for child in children:
                    clone_object(child, attrs)

    return clone


def clone_object_only_foreign_keys(obj, attrs=None):
    if not attrs:
        attrs = {}
    # we start by building a "flat" clone
    clone = obj._meta.model.objects.get(pk=obj.pk)
    clone.pk = None

    # if caller specified some attributes to be overridden,
    # use them
    for key, value in attrs.items():
        setattr(clone, key, value)

    # save the partial clone to have a valid ID assigned
    clone.save()

    # Scan field to further investigate relations
    fields = clone._meta.get_fields()
    for field in fields:
        if isinstance(field, ForeignKey):
            child = getattr(clone, field.name)
            setattr(clone, field.name, clone_object_only_foreign_keys(child))
    clone.save()
    return clone


def get_or_none(queryset):
    try:
        return queryset.get()
    except ObjectDoesNotExist:
        return None


def simple_clone(obj, attrs=None, existing_clone=None):
    if not attrs:
        attrs = {}
    # we start by building a "flat" clone
    clone = obj._meta.model.objects.get(pk=obj.pk)
    clone.pk = existing_clone.pk if existing_clone else None

    # if caller specified some attributes to be overridden,
    # use them
    for key, value in attrs.items():
        setattr(clone, key, value)

    # save the partial clone to have a valid ID assigned
    clone.save()
    return clone
