"""Django ORM filter compiler — converts FilterExpression to Django Q objects.

Usage:
    from pyauthz.filtering.django import to_django_q

    filter_expr = rp.authorized_filter(user, "documents:read")
    queryset = Document.objects.filter(to_django_q(filter_expr.expression))

Requires Django to be installed (optional dependency).
"""

from __future__ import annotations

from typing import Any

from pyauthz.filtering.expressions import (
    AllowAll,
    And,
    Compare,
    DenyAll,
    FilterExpression,
    Not,
    Or,
)

# Mapping from PyAuthz operators to Django field lookups
_OP_MAP = {
    "==": "",  # exact (default)
    "!=": "",  # exact, then negate
    ">": "__gt",
    "<": "__lt",
    ">=": "__gte",
    "<=": "__lte",
    "in": "__in",
    "contains": "__contains",
    "matches": "__regex",
}


def to_django_q(expression: FilterExpression) -> Any:
    """Convert a FilterExpression AST into a Django Q object.

    Args:
        expression: The FilterExpression to compile.

    Returns:
        A Django Q object suitable for `.filter()`.

    Raises:
        ImportError: If Django is not installed.

    Example:
        from pyauthz.filtering.django import to_django_q
        q = to_django_q(filter_expr)
        results = Document.objects.filter(q)
    """
    try:
        from django.db.models import Q
    except ImportError as e:
        raise ImportError(
            "Django is required for to_django_q(). Install it with: pip install django"
        ) from e

    def compile_node(node: FilterExpression) -> Q:
        if isinstance(node, AllowAll):
            return Q()  # Empty Q matches everything
        if isinstance(node, DenyAll):
            return Q(pk__isnull=True) & Q(pk__isnull=False)  # Always false
        if isinstance(node, Compare):
            return _compile_compare_q(node)
        if isinstance(node, And):
            result = Q()
            for child in node.children:
                result &= compile_node(child)
            return result
        if isinstance(node, Or):
            result = Q()
            first = True
            for child in node.children:
                if first:
                    result = compile_node(child)
                    first = False
                else:
                    result |= compile_node(child)
            return result
        if isinstance(node, Not):
            return ~compile_node(node.child)
        raise ValueError(f"Unknown FilterExpression node: {type(node).__name__}")

    return compile_node(expression)


def _compile_compare_q(node: Compare) -> Any:
    """Compile a Compare node to a Django Q object."""
    from django.db.models import Q

    # Convert dot-notation to Django's double-underscore lookup
    django_field = node.field.replace(".", "__")

    if node.op == "!=":
        return ~Q(**{django_field: node.value})
    if node.op == "not_in":
        return ~Q(**{f"{django_field}__in": node.value})

    suffix = _OP_MAP.get(node.op)
    if suffix is None:
        raise ValueError(f"Unsupported operator for Django ORM: '{node.op}'")

    lookup = f"{django_field}{suffix}"
    return Q(**{lookup: node.value})
