"""Database utilities for Django.

This module provides utility functions for working with Django models,
including hashing, topological sorting, and database operations.
These utilities help with efficient and safe database interactions.
"""

from datetime import datetime
from graphlib import TopologicalSorter
from typing import TYPE_CHECKING, Any, Self, cast

from django.db.models import DateTimeField, Field, Model
from django.db.models.fields.related import ForeignKey, ForeignObjectRel
from django.forms.models import model_to_dict

from winidjango.src.db.fields import get_field_names, get_fields

if TYPE_CHECKING:
    from django.contrib.contenttypes.fields import GenericForeignKey
    from django.db.models.options import Options

import logging

logger = logging.getLogger(__name__)


def topological_sort_models[TModel: Model](
    models: list[type[TModel]],
) -> list[type[TModel]]:
    """Sort Django models in dependency order using topological sorting.

    Analyzes foreign key relationships between Django models and returns them
    in an order where dependencies come before dependents. This ensures that
    when performing operations like bulk creation or deletion, models are
    processed in the correct order to avoid foreign key constraint violations.

    The function uses Python's graphlib.TopologicalSorter to perform the sorting
    based on ForeignKey relationships between the provided models. Only
    relationships between models in the input list are considered.

    Args:
        models (list[type[Model]]): A list of Django model classes to sort
            based on their foreign key dependencies.

    Returns:
        list[type[Model]]: The input models sorted in dependency order, where
            models that are referenced by foreign keys appear before models
            that reference them. Self-referential relationships are ignored.

    Raises:
        graphlib.CycleError: If there are circular dependencies between models
            that cannot be resolved.

    Example:
        >>> # Assuming Author model has no dependencies
        >>> # and Book model has ForeignKey to Author
        >>> models = [Book, Author]
        >>> sorted_models = topological_sort_models(models)
        >>> sorted_models
        [<class 'Author'>, <class 'Book'>]

    Note:
        - Only considers ForeignKey relationships, not other field types
        - Self-referential foreign keys are ignored to avoid self-loops
        - Only relationships between models in the input list are considered
    """
    ts: TopologicalSorter[type[TModel]] = TopologicalSorter()

    for model in models:
        deps = {
            cast("type[TModel]", field.related_model)
            for field in get_fields(model)
            if isinstance(field, ForeignKey)
            and isinstance(field.related_model, type)
            and field.related_model in models
            and field.related_model is not model
        }
        ts.add(model, *deps)

    return list(ts.static_order())


def hash_model_instance(
    instance: Model,
    fields: "list[Field[Any, Any] | ForeignObjectRel | GenericForeignKey]",
) -> int:
    """Hash a model instance based on its field values.

    Generates a hash for a Django model instance by considering the values
    of its fields. This can be useful for comparing instances, especially
    when dealing with related objects or complex data structures. The hash
    is generated by recursively hashing related objects up to a specified
    depth.
    This is not very reliable, use with caution.
    Only use if working with unsafed objects or bulks, as with safed

    Args:
        instance (Model): The Django model instance to hash
        fields (list[str]): The fields to hash

    Returns:
        int: The hash value representing the instance's data

    """
    if instance.pk:
        return hash(instance.pk)

    field_names = get_field_names(fields)
    model_dict = model_to_dict(instance, fields=field_names)
    sorted_dict = dict(sorted(model_dict.items()))
    values = (type(instance), tuple(sorted_dict.items()))
    return hash(values)


class BaseModel(Model):
    """Base model for all models in the project.

    Provides common fields and methods for all models.
    """

    created_at: DateTimeField[datetime, datetime] = DateTimeField(auto_now_add=True)
    updated_at: DateTimeField[datetime, datetime] = DateTimeField(auto_now=True)

    class Meta:
        """Mark the model as abstract."""

        # abstract does not inherit in children
        abstract = True

    def __str__(self) -> str:
        """Base string representation of a model.

        Returns:
            str: The string representation of the model as all fields and their values.
        """
        return f"{self.__class__.__name__}({self.pk})"

    def __repr__(self) -> str:
        """Base representation of a model."""
        return str(self)

    @property
    def meta(self) -> "Options[Self]":
        """Get the meta options for the model."""
        return self._meta
