"""SQLAlchemy filter compiler — converts FilterExpression to SQLAlchemy filter clauses.

Usage:
    from pyauthz.filtering.sql import to_sqlalchemy_filter

    filter_expr = rp.authorized_filter(user, "documents:read")
    query = session.query(Document).filter(
        to_sqlalchemy_filter(filter_expr.expression, Document)
    )

Requires SQLAlchemy 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 SQLAlchemy method names
_OP_MAP = {
    "==": "__eq__",
    "!=": "__ne__",
    ">": "__gt__",
    "<": "__lt__",
    ">=": "__ge__",
    "<=": "__le__",
}


def to_sqlalchemy_filter(expression: FilterExpression, model: Any) -> Any:
    """Convert a FilterExpression AST into a SQLAlchemy filter clause.

    Args:
        expression: The FilterExpression to compile.
        model: The SQLAlchemy model class (used to resolve field names to columns).

    Returns:
        A SQLAlchemy filter clause suitable for `.filter()`.

    Raises:
        ValueError: If a field referenced in the expression doesn't exist on the model.
        ImportError: If SQLAlchemy is not installed.

    Example:
        from pyauthz.filtering.sql import to_sqlalchemy_filter
        clause = to_sqlalchemy_filter(filter_expr, DocumentModel)
        results = session.query(DocumentModel).filter(clause).all()
    """
    try:
        from sqlalchemy import and_, false, not_, or_, true
    except ImportError as e:
        raise ImportError(
            "SQLAlchemy is required for to_sqlalchemy_filter(). "
            "Install it with: pip install sqlalchemy"
        ) from e

    def compile_node(node: FilterExpression) -> Any:
        if isinstance(node, AllowAll):
            return true()
        if isinstance(node, DenyAll):
            return false()
        if isinstance(node, Compare):
            column = _resolve_column(model, node.field)
            return _compile_compare(column, node.op, node.value)
        if isinstance(node, And):
            return and_(*[compile_node(c) for c in node.children])
        if isinstance(node, Or):
            return or_(*[compile_node(c) for c in node.children])
        if isinstance(node, Not):
            return not_(compile_node(node.child))
        raise ValueError(f"Unknown FilterExpression node: {type(node).__name__}")

    return compile_node(expression)


def _resolve_column(model: Any, field: str) -> Any:
    """Resolve a dot-notation field path to a SQLAlchemy column.

    For simple fields like "owner_id", returns model.owner_id.
    For nested fields like "attributes.classification", uses JSON access
    if the model has an 'attributes' JSON column.
    """
    parts = field.split(".")

    # Direct column access
    if len(parts) == 1:
        if not hasattr(model, field):
            raise ValueError(
                f"Model {model.__name__} has no column '{field}'. "
                f"Available columns: check your model definition."
            )
        return getattr(model, field)

    # Nested access (e.g., attributes.classification)
    # Try to resolve as a JSON column path
    root = parts[0]
    if not hasattr(model, root):
        raise ValueError(
            f"Model {model.__name__} has no column '{root}'. "
            f"Available columns: check your model definition."
        )
    column = getattr(model, root)
    # Navigate JSON path
    for part in parts[1:]:
        column = column[part]
    return column


def _compile_compare(column: Any, op: str, value: Any) -> Any:
    """Compile a single comparison to a SQLAlchemy clause."""
    if op in _OP_MAP:
        method = getattr(column, _OP_MAP[op])
        return method(value)
    if op == "in":
        return column.in_(value)
    if op == "not_in":
        return column.notin_(value)
    if op == "contains":
        return column.contains(value)
    if op == "matches":
        # Use REGEXP for dialects that support it, LIKE as fallback
        if hasattr(column, "regexp_match"):
            return column.regexp_match(value)
        return column.like(f"%{value}%")
    raise ValueError(f"Unsupported operator for SQLAlchemy: '{op}'")
