"""Cursor class for lazy query result iteration."""

from typing import List, Optional, TYPE_CHECKING

from vibedb.core.document import Document
from vibedb.utils import deserialize_document, format_json_path
from vibedb.query.parser import parse_query

if TYPE_CHECKING:
    from vibedb.core.collection import Collection


class Cursor:
    """
    Lazy iterator over query results with chaining support.

    Uses QueryParser to convert query DSL to optimized SQLite SQL.
    Supports sorting, pagination, and field projection.

    Example:
        >>> users = db.collection("users")
        >>> cursor = users.find()
        >>> for user in cursor:
        ...     print(user.name)
        >>>
        >>> # With chaining
        >>> cursor = users.find({"age": {"$gte": 25}}).sort("name").limit(10)
        >>>
        >>> # Using Q builder
        >>> from vibedb import Q
        >>> cursor = users.find(Q.gte("age", 25) & Q.contains("email", "@example.com"))
    """

    def __init__(self, collection: "Collection", query: dict):
        """
        Initialize Cursor.

        Args:
            collection: Collection to query
            query: Query filter (dict or Q object)
        """
        self._collection = collection
        self._query = query
        self._sort = None
        self._limit = None
        self._skip = None
        self._projection = None

        # Results cache
        self._executed = False
        self._results: List[Document] = []
        self._index = 0

    def sort(self, field: str, direction: int = 1) -> "Cursor":
        """
        Sort results by field.

        Args:
            field: Field name to sort by
            direction: 1 for ascending, -1 for descending

        Returns:
            Cursor: self for chaining

        Example:
            >>> cursor.sort("age", -1)  # Descending by age
        """
        self._sort = (field, direction)
        return self

    def limit(self, n: int) -> "Cursor":
        """
        Limit number of results.

        Args:
            n: Maximum number of results

        Returns:
            Cursor: self for chaining

        Example:
            >>> cursor.limit(10)
        """
        self._limit = n
        return self

    def skip(self, n: int) -> "Cursor":
        """
        Skip first n results.

        Args:
            n: Number of results to skip

        Returns:
            Cursor: self for chaining

        Example:
            >>> cursor.skip(20).limit(10)  # Get results 21-30
        """
        self._skip = n
        return self

    def project(self, fields: List[str]) -> "Cursor":
        """
        Select specific fields to return.

        Args:
            fields: List of field names

        Returns:
            Cursor: self for chaining

        Example:
            >>> cursor.project(["name", "email"])
        """
        self._projection = fields
        return self

    def _execute(self):
        """Execute the query and cache results using optimized SQL."""
        if self._executed:
            return

        # Build SQL query with WHERE clause
        safe_table = self._collection.name.replace('"', '""')
        sql = f'SELECT _doc FROM "{safe_table}_docs"'

        # Parse query to SQL WHERE clause
        where_clause, params = parse_query(self._query)

        # Add WHERE clause
        if where_clause and where_clause != "1=1":
            sql += f" WHERE {where_clause}"

        # Add ORDER BY clause
        if self._sort:
            field, direction = self._sort
            json_path = format_json_path(field)
            order = "ASC" if direction == 1 else "DESC"
            sql += f" ORDER BY json_extract(_doc, '{json_path}') {order}"

        # Add LIMIT clause
        if self._limit is not None:
            sql += f" LIMIT {self._limit}"

        # Add OFFSET clause
        if self._skip is not None:
            sql += f" OFFSET {self._skip}"

        # Execute query
        rows = self._collection.database.engine.execute(sql, tuple(params), fetch='all')

        # Deserialize results
        self._results = []
        for row in rows:
            doc_data = deserialize_document(row['_doc'])

            # Apply projection if specified
            if self._projection:
                doc_data = {k: v for k, v in doc_data.items() if k in self._projection or k.startswith('_')}

            doc = Document(doc_data, self._collection)
            self._results.append(doc)

        self._executed = True

    def count(self) -> int:
        """
        Count results without fetching.

        Returns:
            int: Number of matching documents

        Example:
            >>> count = users.find({"age": {"$gte": 25}}).count()
        """
        self._execute()
        return len(self._results)

    def first(self) -> Optional[Document]:
        """
        Get first result.

        Returns:
            Document: First document or None if no results

        Example:
            >>> user = users.find({"email": "alice@example.com"}).first()
        """
        self._execute()
        return self._results[0] if self._results else None

    def to_list(self) -> List[Document]:
        """
        Fetch all results as list.

        Returns:
            list[Document]: All matching documents

        Example:
            >>> all_users = users.find().to_list()
            >>> print(len(all_users))
        """
        self._execute()
        return self._results.copy()

    def __iter__(self):
        """Iterate over results."""
        self._execute()
        self._index = 0
        return self

    def __next__(self):
        """Get next result."""
        if self._index >= len(self._results):
            raise StopIteration

        result = self._results[self._index]
        self._index += 1
        return result

    def __len__(self) -> int:
        """Number of results."""
        return self.count()

    def __repr__(self) -> str:
        """String representation."""
        return f"Cursor(collection='{self._collection.name}', query={self._query})"
