"""Query DSL (Q builder) for VibeDB - Pythonic query builder."""

from typing import Any, List, Union


class Q:
    """
    Query builder for fluent, Python-style queries.

    Supports both builder pattern (Q.gte(...)) and operator overloading (&, |, ~).
    Compiles to MongoDB-style query dict that can be parsed to SQLite SQL.

    Example:
        >>> # Comparison
        >>> Q.gte("age", 25)
        >>> Q.lt("price", 100)
        >>>
        >>> # Logical combinations
        >>> Q.gte("age", 25) & Q.contains("email", "@example.com")
        >>> Q.eq("status", "active") | Q.eq("vip", True)
        >>> ~Q.eq("deleted", True)
        >>>
        >>> # String operations
        >>> Q.prefix("name", "Ali")
        >>> Q.contains("bio", "engineer")
        >>>
        >>> # Array operations
        >>> Q.has("tags", "python")
        >>> Q.has_any("skills", ["python", "javascript"])
        >>> Q.has_all("roles", ["admin", "editor"])
        >>>
        >>> # Complex queries
        >>> query = (Q.gte("age", 25) & Q.lt("age", 40)) | Q.eq("vip", True)
    """

    def __init__(self, conditions: dict = None):
        """
        Initialize Q with conditions.

        Args:
            conditions: Query conditions as dict
        """
        self._conditions = conditions or {}

    def to_dict(self) -> dict:
        """
        Convert to dict representation.

        Returns:
            dict: Query as MongoDB-style dict
        """
        return self._conditions

    # ==================== Comparison Operators ====================

    @staticmethod
    def eq(field: str, value: Any) -> "Q":
        """
        Field equals value.

        Args:
            field: Field name
            value: Value to compare

        Returns:
            Q: Query object

        Example:
            >>> Q.eq("status", "active")
            >>> Q.eq("age", 28)
        """
        return Q({field: {"$eq": value}})

    @staticmethod
    def ne(field: str, value: Any) -> "Q":
        """
        Field not equals value.

        Args:
            field: Field name
            value: Value to compare

        Returns:
            Q: Query object

        Example:
            >>> Q.ne("status", "deleted")
        """
        return Q({field: {"$ne": value}})

    @staticmethod
    def gt(field: str, value: Any) -> "Q":
        """
        Field greater than value.

        Args:
            field: Field name
            value: Value to compare

        Returns:
            Q: Query object

        Example:
            >>> Q.gt("age", 18)
            >>> Q.gt("price", 100.0)
        """
        return Q({field: {"$gt": value}})

    @staticmethod
    def gte(field: str, value: Any) -> "Q":
        """
        Field greater than or equal to value.

        Args:
            field: Field name
            value: Value to compare

        Returns:
            Q: Query object

        Example:
            >>> Q.gte("age", 18)
        """
        return Q({field: {"$gte": value}})

    @staticmethod
    def lt(field: str, value: Any) -> "Q":
        """
        Field less than value.

        Args:
            field: Field name
            value: Value to compare

        Returns:
            Q: Query object

        Example:
            >>> Q.lt("age", 65)
        """
        return Q({field: {"$lt": value}})

    @staticmethod
    def lte(field: str, value: Any) -> "Q":
        """
        Field less than or equal to value.

        Args:
            field: Field name
            value: Value to compare

        Returns:
            Q: Query object

        Example:
            >>> Q.lte("age", 65)
        """
        return Q({field: {"$lte": value}})

    @staticmethod
    def in_(field: str, values: List[Any]) -> "Q":
        """
        Field value is in list.

        Args:
            field: Field name
            values: List of acceptable values

        Returns:
            Q: Query object

        Example:
            >>> Q.in_("status", ["active", "pending"])
            >>> Q.in_("age", [25, 30, 35])
        """
        return Q({field: {"$in": values}})

    @staticmethod
    def nin(field: str, values: List[Any]) -> "Q":
        """
        Field value is not in list.

        Args:
            field: Field name
            values: List of excluded values

        Returns:
            Q: Query object

        Example:
            >>> Q.nin("status", ["deleted", "archived"])
        """
        return Q({field: {"$nin": values}})

    @staticmethod
    def exists(field: str, exists: bool = True) -> "Q":
        """
        Field exists (or doesn't exist).

        Args:
            field: Field name
            exists: True to check existence, False to check non-existence

        Returns:
            Q: Query object

        Example:
            >>> Q.exists("email")  # Has email field
            >>> Q.exists("deleted_at", False)  # Doesn't have deleted_at
        """
        return Q({field: {"$exists": exists}})

    # ==================== String Operators ====================

    @staticmethod
    def prefix(field: str, prefix: str) -> "Q":
        """
        Field starts with prefix.

        Args:
            field: Field name
            prefix: Prefix string

        Returns:
            Q: Query object

        Example:
            >>> Q.prefix("name", "Ali")  # Matches "Alice", "Alicia"
            >>> Q.prefix("email", "admin")
        """
        return Q({field: {"$prefix": prefix}})

    @staticmethod
    def contains(field: str, substring: str) -> "Q":
        """
        Field contains substring.

        Args:
            field: Field name
            substring: Substring to search for

        Returns:
            Q: Query object

        Example:
            >>> Q.contains("email", "@example.com")
            >>> Q.contains("bio", "engineer")
        """
        return Q({field: {"$contains": substring}})

    @staticmethod
    def regex(field: str, pattern: str) -> "Q":
        """
        Field matches regex pattern (deferred to Phase 4).

        Args:
            field: Field name
            pattern: Regex pattern

        Returns:
            Q: Query object

        Example:
            >>> Q.regex("email", r"^[a-z]+@example\.com$")
        """
        return Q({field: {"$regex": pattern}})

    # ==================== Array Operators ====================

    @staticmethod
    def has(field: str, value: Any) -> "Q":
        """
        Array field contains value.

        Args:
            field: Array field name
            value: Value to search for

        Returns:
            Q: Query object

        Example:
            >>> Q.has("tags", "python")
            >>> Q.has("roles", "admin")
        """
        return Q({field: {"$has": value}})

    @staticmethod
    def has_any(field: str, values: List[Any]) -> "Q":
        """
        Array field contains any of the values.

        Args:
            field: Array field name
            values: List of values to search for

        Returns:
            Q: Query object

        Example:
            >>> Q.has_any("tags", ["python", "javascript"])
            >>> Q.has_any("skills", ["react", "vue", "angular"])
        """
        return Q({field: {"$hasAny": values}})

    @staticmethod
    def has_all(field: str, values: List[Any]) -> "Q":
        """
        Array field contains all of the values.

        Args:
            field: Array field name
            values: List of values that must all be present

        Returns:
            Q: Query object

        Example:
            >>> Q.has_all("roles", ["admin", "editor"])
            >>> Q.has_all("permissions", ["read", "write"])
        """
        return Q({field: {"$hasAll": values}})

    # ==================== Logical Operators ====================

    def and_(self, other: "Q") -> "Q":
        """
        Logical AND combination.

        Args:
            other: Another Q object

        Returns:
            Q: Combined query

        Example:
            >>> Q.gte("age", 25).and_(Q.lt("age", 40))
            >>> # Or use &:
            >>> Q.gte("age", 25) & Q.lt("age", 40)
        """
        return Q({"$and": [self._conditions, other._conditions]})

    def or_(self, other: "Q") -> "Q":
        """
        Logical OR combination.

        Args:
            other: Another Q object

        Returns:
            Q: Combined query

        Example:
            >>> Q.eq("status", "active").or_(Q.eq("vip", True))
            >>> # Or use |:
            >>> Q.eq("status", "active") | Q.eq("vip", True)
        """
        return Q({"$or": [self._conditions, other._conditions]})

    def not_(self) -> "Q":
        """
        Logical NOT negation.

        Returns:
            Q: Negated query

        Example:
            >>> Q.eq("deleted", True).not_()
            >>> # Or use ~:
            >>> ~Q.eq("deleted", True)
        """
        return Q({"$not": self._conditions})

    # ==================== Operator Overloading ====================

    def __and__(self, other: "Q") -> "Q":
        """AND combination using & operator."""
        return self.and_(other)

    def __or__(self, other: "Q") -> "Q":
        """OR combination using | operator."""
        return self.or_(other)

    def __invert__(self) -> "Q":
        """NOT negation using ~ operator."""
        return self.not_()

    def __repr__(self) -> str:
        """String representation."""
        return f"Q({self._conditions})"

    def __str__(self) -> str:
        """String representation."""
        return str(self._conditions)

    def __eq__(self, other) -> bool:
        """Equality comparison."""
        if isinstance(other, Q):
            return self._conditions == other._conditions
        return False
