"""Query parser - converts query DSL to SQLite SQL."""

import json
from typing import Any, Dict, List, Tuple, Union

from vibedb.errors import QueryError
from vibedb.utils import format_json_path, safe_field_name
from vibedb.query.dsl import Q


class QueryParser:
    """
    Parses query DSL (dict or Q object) and generates SQLite SQL.

    Supports MongoDB-style operators and converts them to SQLite queries
    using json_extract() for JSON field access.

    Example:
        >>> parser = QueryParser()
        >>> sql, params = parser.parse({"age": {"$gte": 25}})
        >>> # Returns: ("json_extract(_doc, '$.age') >= ?", [25])
    """

    # Comparison operators mapping
    COMPARISON_OPS = {
        "$eq": "=",
        "$ne": "!=",
        "$gt": ">",
        "$gte": ">=",
        "$lt": "<",
        "$lte": "<=",
    }

    def __init__(self):
        """Initialize query parser."""
        self.params: List[Any] = []

    def parse(self, query: Union[Dict, Q, None]) -> Tuple[str, List[Any]]:
        """
        Parse query and generate SQL WHERE clause.

        Args:
            query: Query as dict, Q object, or None

        Returns:
            tuple: (sql_where_clause, parameters)

        Raises:
            QueryError: If query syntax is invalid

        Example:
            >>> parser = QueryParser()
            >>> sql, params = parser.parse({"age": {"$gte": 25}})
            >>> print(sql)
            json_extract(_doc, '$.age') >= ?
            >>> print(params)
            [25]
        """
        # Reset parameters
        self.params = []

        # Handle empty query
        if query is None or (isinstance(query, dict) and not query):
            return ("1=1", [])  # Always true (match all)

        # Convert Q object to dict
        if isinstance(query, Q):
            query = query.to_dict()

        # Validate query is dict
        if not isinstance(query, dict):
            raise QueryError(f"Query must be dict or Q object, got {type(query).__name__}")

        # Parse query dict
        try:
            sql = self._parse_conditions(query)
            return (sql, self.params)
        except Exception as e:
            raise QueryError(f"Invalid query syntax: {e}", query)

    def _parse_conditions(self, conditions: Dict) -> str:
        """
        Parse query conditions recursively.

        Args:
            conditions: Query conditions dict

        Returns:
            str: SQL WHERE clause

        Raises:
            QueryError: If syntax is invalid
        """
        if not conditions:
            return "1=1"

        clauses = []

        for key, value in conditions.items():
            if key.startswith("$"):
                # Logical operator
                clause = self._parse_logical_operator(key, value)
            else:
                # Field condition
                clause = self._parse_field_condition(key, value)

            clauses.append(clause)

        # Combine clauses with AND
        if len(clauses) == 1:
            return clauses[0]
        else:
            return " AND ".join(f"({c})" for c in clauses)

    def _parse_logical_operator(self, operator: str, value: Any) -> str:
        """
        Parse logical operators ($and, $or, $not).

        Args:
            operator: Logical operator ($and, $or, $not)
            value: Operator value

        Returns:
            str: SQL clause

        Raises:
            QueryError: If operator is invalid
        """
        if operator == "$and":
            if not isinstance(value, list):
                raise QueryError("$and requires a list of conditions")

            sub_clauses = [self._parse_conditions(cond) for cond in value]
            return " AND ".join(f"({c})" for c in sub_clauses)

        elif operator == "$or":
            if not isinstance(value, list):
                raise QueryError("$or requires a list of conditions")

            sub_clauses = [self._parse_conditions(cond) for cond in value]
            return " OR ".join(f"({c})" for c in sub_clauses)

        elif operator == "$not":
            if not isinstance(value, dict):
                raise QueryError("$not requires a dict of conditions")

            sub_clause = self._parse_conditions(value)
            return f"NOT ({sub_clause})"

        else:
            raise QueryError(f"Unknown logical operator: {operator}")

    def _parse_field_condition(self, field: str, value: Any) -> str:
        """
        Parse field condition.

        Args:
            field: Field name (can be nested like "address.city")
            value: Field value or operator dict

        Returns:
            str: SQL clause

        Raises:
            QueryError: If condition is invalid
        """
        # Validate field name
        safe_field_name(field)

        # If value is a dict, it contains operators
        if isinstance(value, dict):
            return self._parse_field_operators(field, value)
        else:
            # Simple equality check
            return self._parse_equality(field, value)

    def _parse_field_operators(self, field: str, operators: Dict) -> str:
        """
        Parse operators for a field.

        Args:
            field: Field name
            operators: Dict of operators and values

        Returns:
            str: SQL clause

        Raises:
            QueryError: If operator is invalid
        """
        clauses = []

        for op, op_value in operators.items():
            if op in self.COMPARISON_OPS:
                clause = self._parse_comparison(field, op, op_value)
            elif op == "$in":
                clause = self._parse_in(field, op_value)
            elif op == "$nin":
                clause = self._parse_nin(field, op_value)
            elif op == "$exists":
                clause = self._parse_exists(field, op_value)
            elif op == "$prefix":
                clause = self._parse_prefix(field, op_value)
            elif op == "$contains":
                clause = self._parse_contains(field, op_value)
            elif op == "$regex":
                clause = self._parse_regex(field, op_value)
            elif op == "$has":
                clause = self._parse_has(field, op_value)
            elif op == "$hasAny":
                clause = self._parse_has_any(field, op_value)
            elif op == "$hasAll":
                clause = self._parse_has_all(field, op_value)
            else:
                raise QueryError(f"Unknown operator: {op}")

            clauses.append(clause)

        # Combine multiple operators with AND
        if len(clauses) == 1:
            return clauses[0]
        else:
            return " AND ".join(f"({c})" for c in clauses)

    def _get_json_extract(self, field: str) -> str:
        """
        Get json_extract SQL for a field.

        Args:
            field: Field name (possibly nested)

        Returns:
            str: json_extract(...) SQL

        Example:
            >>> _get_json_extract("name")
            "json_extract(_doc, '$.name')"
            >>> _get_json_extract("address.city")
            "json_extract(_doc, '$.address.city')"
        """
        json_path = format_json_path(field)
        return f"json_extract(_doc, '{json_path}')"

    def _parse_equality(self, field: str, value: Any) -> str:
        """Parse simple equality: {field: value}"""
        json_expr = self._get_json_extract(field)
        self.params.append(value)
        return f"{json_expr} = ?"

    def _parse_comparison(self, field: str, operator: str, value: Any) -> str:
        """Parse comparison operators: $gt, $gte, $lt, $lte, $ne"""
        sql_op = self.COMPARISON_OPS[operator]
        json_expr = self._get_json_extract(field)
        self.params.append(value)
        return f"{json_expr} {sql_op} ?"

    def _parse_in(self, field: str, values: List[Any]) -> str:
        """
        Parse $in operator.

        Example:
            {"status": {"$in": ["active", "pending"]}}
            -> json_extract(_doc, '$.status') IN (?, ?)
        """
        if not isinstance(values, list):
            raise QueryError("$in requires a list of values")

        if not values:
            return "0=1"  # Empty list = no matches

        json_expr = self._get_json_extract(field)
        placeholders = ", ".join("?" * len(values))
        self.params.extend(values)
        return f"{json_expr} IN ({placeholders})"

    def _parse_nin(self, field: str, values: List[Any]) -> str:
        """
        Parse $nin operator.

        Example:
            {"status": {"$nin": ["deleted", "archived"]}}
            -> json_extract(_doc, '$.status') NOT IN (?, ?)
        """
        if not isinstance(values, list):
            raise QueryError("$nin requires a list of values")

        if not values:
            return "1=1"  # Empty list = all match

        json_expr = self._get_json_extract(field)
        placeholders = ", ".join("?" * len(values))
        self.params.extend(values)
        return f"{json_expr} NOT IN ({placeholders})"

    def _parse_exists(self, field: str, exists: bool) -> str:
        """
        Parse $exists operator.

        Example:
            {"email": {"$exists": True}}
            -> json_extract(_doc, '$.email') IS NOT NULL
        """
        json_expr = self._get_json_extract(field)
        if exists:
            return f"{json_expr} IS NOT NULL"
        else:
            return f"{json_expr} IS NULL"

    def _parse_prefix(self, field: str, prefix: str) -> str:
        """
        Parse $prefix operator (LIKE 'prefix%').

        Example:
            {"name": {"$prefix": "Ali"}}
            -> json_extract(_doc, '$.name') LIKE ?
        """
        json_expr = self._get_json_extract(field)
        self.params.append(f"{prefix}%")
        return f"{json_expr} LIKE ?"

    def _parse_contains(self, field: str, substring: str) -> str:
        """
        Parse $contains operator (LIKE '%substring%').

        Example:
            {"email": {"$contains": "@example.com"}}
            -> json_extract(_doc, '$.email') LIKE ?
        """
        json_expr = self._get_json_extract(field)
        self.params.append(f"%{substring}%")
        return f"{json_expr} LIKE ?"

    def _parse_regex(self, field: str, pattern: str) -> str:
        """
        Parse $regex operator (SQLite REGEXP).

        Note: Requires custom REGEXP function (deferred to Phase 4).

        Example:
            {"email": {"$regex": "^[a-z]+@example\\.com$"}}
        """
        json_expr = self._get_json_extract(field)
        self.params.append(pattern)
        return f"{json_expr} REGEXP ?"

    def _parse_has(self, field: str, value: Any) -> str:
        """
        Parse $has operator (array contains value).

        Uses json_each to search within JSON array.

        Example:
            {"tags": {"$has": "python"}}
            -> EXISTS(SELECT 1 FROM json_each(_doc, '$.tags') WHERE value = ?)
        """
        json_path = format_json_path(field)
        self.params.append(value)
        return f"""EXISTS(
            SELECT 1 FROM json_each(_doc, '{json_path}')
            WHERE json_each.value = ?
        )"""

    def _parse_has_any(self, field: str, values: List[Any]) -> str:
        """
        Parse $hasAny operator (array contains any of values).

        Example:
            {"tags": {"$hasAny": ["python", "javascript"]}}
        """
        if not isinstance(values, list):
            raise QueryError("$hasAny requires a list of values")

        if not values:
            return "0=1"  # Empty list = no matches

        json_path = format_json_path(field)
        placeholders = ", ".join("?" * len(values))
        self.params.extend(values)
        return f"""EXISTS(
            SELECT 1 FROM json_each(_doc, '{json_path}')
            WHERE json_each.value IN ({placeholders})
        )"""

    def _parse_has_all(self, field: str, values: List[Any]) -> str:
        """
        Parse $hasAll operator (array contains all of values).

        Example:
            {"roles": {"$hasAll": ["admin", "editor"]}}
        """
        if not isinstance(values, list):
            raise QueryError("$hasAll requires a list of values")

        if not values:
            return "1=1"  # Empty list = all match

        # Check that each required value exists in the array
        json_path = format_json_path(field)
        clauses = []

        for value in values:
            self.params.append(value)
            clause = f"""EXISTS(
                SELECT 1 FROM json_each(_doc, '{json_path}')
                WHERE json_each.value = ?
            )"""
            clauses.append(clause)

        return " AND ".join(f"({c})" for c in clauses)


def parse_query(query: Union[Dict, Q, None]) -> Tuple[str, List[Any]]:
    """
    Convenience function to parse a query.

    Args:
        query: Query dict, Q object, or None

    Returns:
        tuple: (sql_where_clause, parameters)

    Example:
        >>> sql, params = parse_query({"age": {"$gte": 25}})
        >>> sql, params = parse_query(Q.gte("age", 25) & Q.lt("age", 40))
    """
    parser = QueryParser()
    return parser.parse(query)
