import logging

from django.contrib.contenttypes.fields import GenericRelation
from django.db import router
from django.db.models.deletion import CASCADE, Collector
from django.utils.translation import gettext as _

logger = logging.getLogger("netbox.models.deletion")


class CustomCollector(Collector):
    """
    Override Django's stock Collector to handle GenericRelations and ensure proper ordering of cascading deletions.
    """

    def collect(
        self,
        objs,
        source=None,
        nullable=False,
        collect_related=True,
        source_attr=None,
        reverse_dependency=False,
        keep_parents=False,
        fail_on_restricted=True,
    ):
        # By default, Django will force the deletion of dependent objects before the parent only if the ForeignKey field
        # is not nullable. We want to ensure proper ordering regardless, so if the ForeignKey has `on_delete=CASCADE`
        # applied, we set `nullable` to False when calling `collect()`.
        if objs and source and source_attr:
            model = objs[0].__class__
            field = model._meta.get_field(source_attr)
            if field.remote_field.on_delete == CASCADE:
                nullable = False

        super().collect(
            objs,
            source=source,
            nullable=nullable,
            collect_related=collect_related,
            source_attr=source_attr,
            reverse_dependency=reverse_dependency,
            keep_parents=keep_parents,
            fail_on_restricted=fail_on_restricted,
        )

        # Add GenericRelations to the dependency graph
        processed_relations = set()
        for _model, instances in list(self.data.items()):
            for instance in instances:
                # Get all GenericRelations for this model
                for field in instance._meta.private_fields:
                    if isinstance(field, GenericRelation):
                        # Create a unique key for this relation
                        relation_key = f"{instance._meta.model_name}.{field.name}"
                        if relation_key in processed_relations:
                            continue
                        processed_relations.add(relation_key)

                        # Add the model that the generic relation points to as a dependency
                        self.add_dependency(field.related_model, instance, reverse_dependency=True)


class DeleteMixin:
    """
    Mixin to override the model delete function to use our custom collector.
    """

    def delete(self, using=None, keep_parents=False):
        """
        Override delete to use our custom collector.
        """
        using = using or router.db_for_write(self.__class__, instance=self)
        if self._get_pk_val() is None:
            raise ValueError(
                _("{object_name} object can't be deleted because its {pk_attname} attribute is set to None.").format(
                    object_name=self._meta.object_name,
                    pk_attname=self._meta.pk.attname,
                )
            )

        # Pass origin=self (matching Django's Model.delete) so signal receivers can tell that
        # cascaded child objects are being deleted as part of deleting this object.
        collector = CustomCollector(using=using, origin=self)
        collector.collect([self], keep_parents=keep_parents)

        return collector.delete()

    delete.alters_data = True

    @classmethod
    def verify_mro(cls, instance):
        """
        Verify that this mixin is first in the MRO.
        """
        mro = instance.__class__.__mro__
        if mro.index(cls) != 0:
            raise RuntimeError(f"{cls.__name__} must be first in the MRO. Current MRO: {mro}")
