from django.core.exceptions import FieldDoesNotExist
from django.db import models
from wagtail.models import Page

# Field names declared by WPImportedPageMixin. Concrete models that mix it in
# get a manager that .defer()s these so they're never loaded by default — the
# columns stay in the database (no migration), and any code path that
# explicitly reads them still works via Django's lazy-load.
WP_IMPORT_FIELDS = (
    "wp_post_id",
    "wp_post_type",
    "wp_link",
    "wp_raw_content",
    "wp_processed_content",
    "wp_block_json",
    "wp_normalized_styles",
    "wp_post_meta",
)


# https://github.com/wagtail/wagtail-wordpress-import/blob/main/wagtail_wordpress_import/models.py
# DJ001 (null=True on string fields) is suppressed: the schema mirrors the
# upstream mixin and changing nullability would force a migration we avoid.
class WPImportedPageMixin(Page):
    wp_post_id = models.IntegerField(blank=True, null=True)
    wp_post_type = models.CharField(max_length=255, blank=True, null=True)  # noqa: DJ001
    wp_link = models.TextField(blank=True, null=True)  # noqa: DJ001
    wp_raw_content = models.TextField(blank=True, null=True)  # noqa: DJ001
    wp_processed_content = models.TextField(blank=True, null=True)  # noqa: DJ001
    wp_block_json = models.TextField(blank=True, null=True)  # noqa: DJ001
    wp_normalized_styles = models.TextField(blank=True, null=True)  # noqa: DJ001
    wp_post_meta = models.JSONField(blank=True, null=True)

    class Meta:
        abstract = True


class DeferWPFieldsManagerMixin:
    """
    Manager mixin that always .defer()s the wp_* import columns, so they are
    never SELECTed by default queries. The columns remain in the database;
    accessing one on an instance still works (Django lazy-loads it).
    """

    def get_queryset(self):
        return super().get_queryset().defer(*WP_IMPORT_FIELDS)


def _resolve_related_model(model, lookup_path):
    """Walk a ``foo__bar`` lookup path and return the final related model, or None."""
    current = model
    for part in lookup_path.split("__"):
        try:
            field = current._meta.get_field(part)
        except FieldDoesNotExist:
            return None
        current = getattr(field, "related_model", None)
        if current is None:
            return None
    return current


class WPAwareQuerySet(models.QuerySet):
    """
    QuerySet whose ``select_related()`` auto-defers wp_* columns when a join
    targets a WPImportedPageMixin model. Without this, ``select_related``
    builds a JOIN that ignores the related model's manager and SELECTs every
    column including the wp_* blobs. Apply via ``objects = WPAwareManager()``
    on any model that has a ForeignKey into a WPImported page.
    """

    def select_related(self, *fields):
        qs = super().select_related(*fields)
        defers = [
            f"{path}__{name}"
            for path in fields
            if (related := _resolve_related_model(qs.model, path)) is not None
            and issubclass(related, WPImportedPageMixin)
            for name in WP_IMPORT_FIELDS
        ]
        return qs.defer(*defers) if defers else qs


WPAwareManager = models.Manager.from_queryset(WPAwareQuerySet)
