"""Core API filter backends — tenant scoping enforced at the queryset level.

DRF filter backends are the correct place to enforce tenant isolation
because they run on every request automatically, regardless of which view
class or viewset is used.

Usage::

    # settings.py (already done in base settings if desired)
    REST_FRAMEWORK = {
        "DEFAULT_FILTER_BACKENDS": [
            "simorgh.core.api.filters.TenantFilterBackend",
            "django_filters.rest_framework.DjangoFilterBackend",
            "rest_framework.filters.OrderingFilter",
        ]
    }

    # Or per-view:
    class DocumentList(TenantListCreateView):
        filter_backends = [TenantFilterBackend, SearchFilter]
"""
from __future__ import annotations

from typing import Any

from rest_framework.filters import BaseFilterBackend, SearchFilter
from rest_framework.request import Request

__all__ = [
    "TenantFilterBackend",
    "ActiveRecordFilterBackend",
    "SoftDeleteFilterBackend",
    "TenantSearchFilter",
]


class TenantFilterBackend(BaseFilterBackend):
    """Filter backend that restricts querysets to the current tenant.

    Expects ``request.tenant`` to be set by
    :class:`~simorgh.core.middleware.TenantMiddleware`.  If the tenant is
    absent the queryset is returned **empty** (fail-safe) rather than
    leaking cross-tenant data.

    Models that do not have a ``tenant`` field are passed through unchanged.
    The check is: does the model have a ``tenant_id`` column?
    """

    def filter_queryset(self, request: Request, queryset: Any, view: Any) -> Any:
        tenant = getattr(request, "tenant", None)
        if tenant is None:
            # No tenant context — return empty queryset to avoid data leaks.
            return queryset.none() if hasattr(queryset, "none") else queryset

        model = getattr(queryset, "model", None)
        if model is None:
            return queryset

        # Only apply the filter when the model actually has a tenant_id column.
        field_names = {f.attname for f in model._meta.get_fields() if hasattr(f, "attname")}
        if "tenant_id" in field_names:
            return queryset.filter(tenant_id=tenant.pk)

        return queryset


class ActiveRecordFilterBackend(BaseFilterBackend):
    """Optionally filter out ``is_active=False`` records.

    Activated by the ``active_only`` query parameter (``?active_only=1``).
    Skipped if the model has no ``is_active`` field.
    """

    def filter_queryset(self, request: Request, queryset: Any, view: Any) -> Any:
        if request.query_params.get("active_only") != "1":
            return queryset

        model = getattr(queryset, "model", None)
        if model is None:
            return queryset

        field_names = {f.name for f in model._meta.get_fields()}
        if "is_active" in field_names:
            return queryset.filter(is_active=True)

        return queryset


class SoftDeleteFilterBackend(BaseFilterBackend):
    """Exclude soft-deleted records (``is_deleted=True``) by default.

    Pass ``?include_deleted=1`` to include them (staff only — returns 403
    for non-staff requests).
    """

    def filter_queryset(self, request: Request, queryset: Any, view: Any) -> Any:
        model = getattr(queryset, "model", None)
        if model is None:
            return queryset

        field_names = {f.name for f in model._meta.get_fields()}
        if "is_deleted" not in field_names:
            return queryset

        include_deleted = request.query_params.get("include_deleted") == "1"
        if include_deleted:
            if not getattr(request.user, "is_staff", False):
                # Non-staff cannot see deleted records — silently ignore param.
                return queryset.filter(is_deleted=False)
            return queryset  # staff sees all including deleted

        return queryset.filter(is_deleted=False)


class TenantSearchFilter(SearchFilter):
    """SearchFilter subclass that sets a sensible default search parameter.

    Reads ``?q=`` (instead of DRF's default ``?search=``) to align with the
    frontend convention used throughout this platform.
    """

    search_param = "q"
