from __future__ import annotations
from ..raw_querybuilders import build_select_query
from ..raw_querybuilders import JoinQuery
from ..raw_querybuilders.formatters import SQLOrderBy, column_string
from typing import List, Optional, Union, Any, Iterable
from ..base import RecordQuery
from ..types import SQLCol
from ..dependencies import SQLCondition, no_condition, SQLExpression
from ..validators import validate_name, validate_table_name
from .utils import normalize_args, enlist
from ..utils import ensure_bracketed


class SelectQuery(RecordQuery):
    def __init__(
        self,
        columns: Union[str, list] = "*",
        table_name: str = None,
        condition: Any = None,
        order_by: Optional[List[Any]] = None,
        criteria: List[str] = None,
        limit: Optional[Union[int, str]] = None,
        offset: Optional[Union[int, str]] = None,
        group_by: Union[Any, list, None] = None,
        having: Optional[Any] = None,
        *,
        ignore_forbidden_chars: bool = False,
        alias: Optional[str] = None,
        joins: List[Any] = None,
        withs: Optional[List[Any]] = None,
    ) -> None:
        self._initialized = False  # Block __setattr__ during init

        self.columns = columns
        self.condition = condition
        self.order_by = order_by

        # Normalize criteria
        if criteria is None:
            criteria = ["DESC"]
        elif isinstance(criteria, str) and criteria in {"ASC", "DESC"}:
            criteria = [criteria]
        elif set(criteria) - {"ASC", "DESC"}:
            raise ValueError("criteria must be 'ASC' or 'DESC'")
        self.criteria = criteria

        self.limit = limit
        self.offset = offset
        self.group_by = group_by
        self.having = having
        self.ignore_forbidden_chars = ignore_forbidden_chars
        self.alias = alias
        self._joins = joins or []
        self._withs = withs or []

        # Internal state
        self._cached_placeholder_str = None
        self._cached_placeholder_params = None
        self._up_to_date = False
        super().__init__(
            table_name=table_name,
            validate_table_name=not ignore_forbidden_chars,
            alias=alias,
            expression_value=None,
            expression_type="query",
            positive=True,
            inverted=False,
        )
        self._initialized = True

        # Call parent constructor last to avoid premature tracking

    def __setattr__(self, name: str, value: Any) -> None:
        if getattr(self, "_initialized", False):
            self._on_attribute_change(name, value)
        super().__setattr__(name, value)

    def _on_attribute_change(self, attribute: str, value: Any) -> None:
        tracked = {
            "columns",
            "table_name",
            "condition",
            "order_by",
            "criteria",
            "limit",
            "offset",
            "group_by",
            "having",
            "joins",
            "withs",
            "alias",
        }

        if attribute in tracked:
            self._up_to_date = False

    @property
    def criteria(self) -> List[str]:
        """
        Returns the sorting criteria.
        Returns:
            List[str]: The sorting criteria, either "ASC" or "DESC".
        """
        return self._criteria if self._criteria else None

    @criteria.setter
    def criteria(self, value: List[str]) -> None:
        """
        Sets the sorting criteria.
        Args:
            value (List[str]): The sorting criteria, either "ASC" or "DESC".
        """
        if value in ["ASC", "DESC"]:
            self._criteria = [value]
        elif set(value) - {"ASC", "DESC"}:
            raise ValueError("criteria must be a list of 'ASC' or 'DESC'")
        else:
            self._criteria = value

    @property
    def joins(self) -> List[JoinQuery]:
        """
        Returns the list of joins.
        Returns:
            List[JoinQuery]: The list of joins.
        """
        return self._joins

    @joins.setter
    def joins(self, value: List[JoinQuery]) -> None:
        """
        Sets the list of joins.
        Args:
            value (List[JoinQuery]): The list of joins.
        """
        value = enlist(value)
        self.validate_join_list(value)
        self._joins = value or []

    @property
    def alias(self) -> Optional[str]:
        """
        Returns the alias of the query.
        Returns:
            Optional[str]: The alias of the query.
        """
        return self._alias

    @alias.setter
    def alias(self, value: Optional[str]) -> None:
        """
        Sets the alias of the query.
        Args:
            value (Optional[str]): The alias of the query.
        """
        if value is not None:
            if isinstance(value, SQLExpression):
                value = value.expression_value
            validate_name(value, validate_chars=not self.ignore_forbidden_chars)
        self._alias = value

    @property
    def withs(self) -> List[WithQuery]:
        """
        Returns the list of withs.
        Returns:
            List[WithQuery]: The list of withs.
        """
        return self._withs

    @withs.setter
    def withs(self, value: List[WithQuery]) -> None:
        """
        Sets the list of withs.
        Args:
            value (List[WithQuery]): The list of withs.
        """
        value = enlist(value)
        self.validate_with_list(value)
        self._withs = value or []

    def _add_join(self, join: JoinQuery) -> None:
        """
        Adds a join to the list of joins.
        Args:
            join (JoinQuery): The join to add.
        """
        my_joins = self.joins
        self.joins.append(join)
        self.joins = my_joins

    @normalize_args(skip=1)
    def SELECT(self, *columns: SQLCol) -> SelectQuery:
        """
        Sets the columns to select.
        Args:
            *columns (SQLCol): The columns to select.
        Returns:
            SelectQuery: The current instance of SelectQuery.
        """
        self.columns = list(columns)  # enforce normalized form
        return self

    def FROM(self, table_name: str = None):
        self.table_name = table_name
        return self

    def WHERE(self, condition: SQLCondition = no_condition):
        self.condition = condition
        return self

    def ORDER_BY(self, *items: Union[SQLOrderBy, List[SQLOrderBy]]):
        if not items:
            self.order_by = None
            self.criteria = None
            return self
        collected_order_by = []
        collected_criteria = []
        for item in items:
            if isinstance(item, (list, tuple)) and len(item) == 2:
                collected_order_by.append(item[0])
                collected_criteria.append(item[1])
            elif isinstance(item, str):
                if item.upper() in ["ASC", "DESC"]:
                    collected_criteria.append(item.upper())
                else:
                    collected_order_by.append(item)
            elif isinstance(item, SQLOrderBy):
                collected_order_by.append(item)
        ob_len = len(collected_order_by)
        if ob_len == 0:
            return self
        for _ in range(len(collected_criteria), ob_len):
            collected_criteria.append("DESC")
        collected_criteria = collected_criteria[:ob_len]
        self.order_by = collected_order_by
        self.criteria = collected_criteria
        return self

    def LIMIT(self, limit: Optional[Union[int, str]] = None):
        self.limit = limit
        return self

    def OFFSET(self, offset: Optional[Union[int, str]] = None):
        self.offset = offset
        return self

    def GROUP_BY(self, group_by: Union[SQLCol, list[SQLCol], None] = None):
        if not isinstance(group_by, (SQLCol, list, tuple)):
            raise TypeError("group_by must be an instance of SQLCol or list")
        self.group_by = group_by
        return self

    def HAVING(self, having: Optional[SQLCondition] = None):
        self.having = having
        return self

    def SET(self, *args, **kwargs) -> None:
        raise NotImplementedError("SET clause is not applicable for SELECT queries.")

    def VALUES(self, *args, **kwargs) -> None:
        raise NotImplementedError("VALUES clause is not applicable for SELECT queries.")

    def _placeholder_pair(self) -> tuple[str, Any]:
        """
        Returns a placeholder pair for the query.
        Returns:
            tuple[str, Any]: A tuple containing the query string and parameters.
        """
        string, params = build_select_query(
            table_name=self.table_name,
            columns=self.columns,
            condition=self.condition,
            order_by=self.order_by,
            criteria=self.criteria,
            limit=self.limit,
            offset=self.offset,
            group_by=self.group_by,
            having=self.having,
            joins=self.joins,
            ignore_forbidden_chars=self.ignore_forbidden_chars,
        )

        if self.withs:
            first, first_params = self.withs[0].placeholder_pair(include_with=True)
            withs = [first]
            with_params = first_params
            for with_ in self.withs[1:]:
                with_string, with_param = with_.placeholder_pair(include_with=False)
                withs.append(with_string)
                with_params.extend(with_param)
            withs = ", ".join(withs)
            withs += " "
            string = f"{withs}{string}"
            params = with_params + params
        return string, params

    def placeholder_pair(self, include_alias: bool = True) -> tuple[str, Any]:
        """
        Returns a placeholder pair for the query.
        Args:
            include_alias (bool): Whether to include the alias in the placeholder pair.
        Returns:
            tuple[str, Any]: A tuple containing the query string and parameters.
        """
        if not self._up_to_date:
            (
                self._cached_placeholder_str,
                self._cached_placeholder_params,
            ) = self._placeholder_pair()
            self._up_to_date = True
        string = self._cached_placeholder_str
        params = self._cached_placeholder_params
        if include_alias and self.alias:
            string = ensure_bracketed(string)
            string = f"{string} AS {self.alias}"
        return string, params

    def placeholder_str(self, *args, include_alias: bool = True, **kwargs) -> str:
        """
        Returns the placeholder string for the query.
        Args:
            *args: Additional arguments.
            include_alias (bool): Whether to include the alias in the placeholder string.
            **kwargs: Additional keyword arguments.
        Returns:
            str: The placeholder string for the query.
        """
        string, _ = self.placeholder_pair(include_alias=include_alias)
        return string

    def AS(self, alias=None):
        """
        Sets the alias for the query.
        Args:
            alias (str): The alias to set.
        Returns:
            SelectQuery: The current instance of SelectQuery.
        """

        self.alias = alias
        return self

    def __str__(self) -> str:
        start = "SelectQuery("
        mid, args = self.placeholder_pair()
        end = ")"
        args = ", ".join(str(arg) for arg in args)
        return f"{start}{mid}, <{args}> {end}"

    def __repr__(self) -> str:
        start = "SelectQuery("
        mid = column_string(self.columns)
        end = ")"
        order_by = f"order_by={column_string(self.order_by) if self.order_by else None}"
        criteria = f"({self.criteria})"
        limit = f"limit={self.limit}"
        offset = f"offset={self.offset}"
        group_by = f"group_by={column_string(self.group_by) if self.group_by else None}"
        having = self.having.sql_string() if self.having else None
        having = f"having={having}"
        table_name = f"table_name={self.table_name}"
        condition = self.condition.sql_string() if self.condition else None
        condition = f"condition={condition}"
        ignore_forbidden_chars = f"ifb={self.ignore_forbidden_chars}"
        all = f"{start}{mid}, {table_name}, {condition}, {order_by}, {criteria}, {limit}, {offset}, {group_by}, {having}, {ignore_forbidden_chars}"
        return f"{all}{end}"

    def copy_with(
        self,
        columns: Union[SQLCol, list[SQLCol]] = None,
        table_name: str = None,
        condition: SQLCondition = None,
        order_by: Optional[List[SQLOrderBy]] = None,
        criteria: List[str] = None,
        limit: Optional[Union[int, str]] = None,
        offset: Optional[Union[int, str]] = None,
        group_by: Union[SQLCol, list[SQLCol], None] = None,
        having: Optional[SQLCondition] = None,
        joins: Optional[List[JoinQuery]] = None,
        alias: Optional[str] = None,
        ignore_forbidden_chars: bool = False,
    ) -> SelectQuery:
        """
        Creates a copy of the current query with the specified modifications.
        Args:
            columns (Union[SQLCol, list[SQLCol]], optional): The columns to select. Defaults to None.
            table_name (str, optional): The name of the table to query. Defaults to None.
            condition (SQLCondition, optional): The condition to apply to the query. Defaults to None.
            order_by (Optional[List[SQLOrderBy]], optional): The column(s) to order the results by. Defaults to None.
            criteria (List[str], optional): The sorting criteria, either "ASC" or "DESC". Defaults to None.
            limit (Optional[Union[int, str]], optional): The maximum number of rows to return. Defaults to None.
            offset (Optional[Union[int, str]], optional): The number of rows to skip before starting to return rows. Defaults to None.
            group_by (Union[SQLCol, list[SQLCol], None], optional): The column(s) to group the results by. Defaults to None.
            having (SQLCondition, optional): The condition to apply to the grouped results. Defaults to None.
        Returns:
            SelectQuery: A new instance of SelectQuery with the specified modifications.
        """
        return SelectQuery(
            columns=columns if columns is not None else self.columns,
            table_name=table_name if table_name is not None else self.table_name,
            condition=condition if condition is not None else self.condition,
            order_by=order_by if order_by is not None else self.order_by,
            criteria=criteria if criteria is not None else self.criteria,
            limit=limit if limit is not None else self.limit,
            offset=offset if offset is not None else self.offset,
            group_by=group_by if group_by is not None else self.group_by,
            having=having if having is not None else self.having,
            alias=alias if alias is not None else self.alias,
            ignore_forbidden_chars=ignore_forbidden_chars
            if ignore_forbidden_chars is not None
            else self.ignore_forbidden_chars,
            joins=joins if joins is not None else self.joins,
        )

    def copy(self) -> SelectQuery:
        """
        Creates a copy of the current query.
        Returns:
            SelectQuery: A new instance of SelectQuery with the same attributes as the current instance.
        """
        return self.copy_with()

    @classmethod
    def _SELECT(
        cls, column_list: Union[SQLCol, list[SQLCol]] = "*", *args: SQLCol, **kwargs
    ) -> SelectQuery:
        """
        Constructs a SELECT SQL query.
        Args:
            column_list (Union[SQLCol, list[SQLCol]], optional): A single column,
                a list of columns, or "*" to select all columns. Defaults to "*".
            *args (SQLCol): Additional columns to include in the SELECT query.
        Returns:
            SelectQuery: An object representing the constructed SELECT query.
        Example:
            >>> query = SELECT(col("id"), col("name")).FROM("users").WHERE(col("id") == 1)
            >>> print(query.placeholder_pair())
            SELECT id, name FROM users WHERE id = ?, [1]
        """

        if not isinstance(column_list, list):
            column_list = [column_list]
        column_list_ = column_list + list(args)

        return cls(columns=column_list_, **kwargs)

    @normalize_args(skip=1)
    def JOINS(self, *joins: JoinQuery, join_list=None, **kwargs) -> SelectQuery:
        """
        Adds JOIN clauses to the query.
        Args:
            *joins (JoinQuery): The JOIN clauses to add.
        Returns:
            SelectQuery: The current instance of SelectQuery.
        """
        if join_list is not None:
            if isinstance(join_list, JoinQuery):
                join_list = [join_list]
            elif isinstance(join_list, Iterable) and not isinstance(
                join_list, (str, bytes)
            ):
                join_list = list(join_list)
            else:
                raise TypeError(
                    "join_list must be a JoinQuery or an iterable of JoinQuery"
                )
        else:
            join_list = []

        if not joins and not kwargs and not join_list:
            self.joins = None
            return self
        joins = list(joins) + list(kwargs.values()) + join_list
        self.validate_join_list(joins)
        my_joins = self.joins
        my_joins.extend(joins)
        self.joins = my_joins
        return self

    def INNER_JOIN(
        self, table_name: str, on: SQLCondition, alias: Optional[str] = None
    ) -> SelectQuery:
        """
        Adds an INNER JOIN clause to the query.
        Args:
            table_name (str): The name of the table to join.
            on (SQLCondition): The condition for the join.
            alias (Optional[str]): An optional alias for the joined table.
        Returns:
            SelectQuery: The current instance of SelectQuery.
        """
        join = JoinQuery(table_name=table_name, on=on, join_type="INNER", alias=alias)
        self._add_join(join)
        return self

    def LEFT_JOIN(
        self, table_name: str, on: SQLCondition, alias: Optional[str] = None
    ) -> SelectQuery:
        """
        Adds a LEFT JOIN clause to the query.
        Args:
            table_name (str): The name of the table to join.
            on (SQLCondition): The condition for the join.
            alias (Optional[str]): An optional alias for the joined table.
        Returns:
            SelectQuery: The current instance of SelectQuery.
        """
        join = JoinQuery(table_name=table_name, on=on, join_type="LEFT", alias=alias)
        self._add_join(join)
        return self

    def RIGHT_JOIN(
        self, table_name: str, on: SQLCondition, alias: Optional[str] = None
    ) -> SelectQuery:
        """
        Adds a RIGHT JOIN clause to the query.
        Args:
            table_name (str): The name of the table to join.
            on (SQLCondition): The condition for the join.
            alias (Optional[str]): An optional alias for the joined table.
        Returns:
            SelectQuery: The current instance of SelectQuery.
        """
        join = JoinQuery(table_name=table_name, on=on, join_type="RIGHT", alias=alias)
        self._add_join(join)
        return self

    def FULL_JOIN(
        self, table_name: str, on: SQLCondition, alias: Optional[str] = None
    ) -> SelectQuery:
        """
        Adds a FULL JOIN clause to the query.
        Args:
            table_name (str): The name of the table to join.
            on (SQLCondition): The condition for the join.
            alias (Optional[str]): An optional alias for the joined table.
        Returns:
            SelectQuery: The current instance of SelectQuery.
        """
        join = JoinQuery(table_name=table_name, on=on, join_type="FULL", alias=alias)
        self._add_join(join)
        return self

    def CROSS_JOIN(
        self, table_name: str, on: SQLCondition, alias: Optional[str] = None
    ) -> SelectQuery:
        """
        Adds a CROSS JOIN clause to the query.
        Args:
            table_name (str): The name of the table to join.
            on (SQLCondition): The condition for the join.
            alias (Optional[str]): An optional alias for the joined table.
        Returns:
            SelectQuery: The current instance of SelectQuery.
        """
        join = JoinQuery(table_name=table_name, on=on, join_type="CROSS", alias=alias)
        self._add_join(join)
        return self

    def OUTER_JOIN(
        self, table_name: str, on: SQLCondition, alias: Optional[str] = None
    ) -> SelectQuery:
        """
        Adds an OUTER JOIN clause to the query.
        Args:
            table_name (str): The name of the table to join.
            on (SQLCondition): The condition for the join.
            alias (Optional[str]): An optional alias for the joined table.
        Returns:
            SelectQuery: The current instance of SelectQuery.
        """
        join = JoinQuery(table_name=table_name, on=on, join_type="OUTER", alias=alias)
        self._add_join(join)
        return self

    def JOIN(self, *joins: JoinQuery, join_list=None, **kwargs) -> SelectQuery:
        """
        Adds JOIN clauses to the query.
        Args:
            *joins (JoinQuery): The JOIN clauses to add.
            join_list (Optional[List[JoinQuery]]): An optional list of JOIN clauses.
            **kwargs: Additional JOIN clauses as keyword arguments.
        Returns:
            SelectQuery: The current instance of SelectQuery.
        """
        former_joins = self.joins
        try:
            self.joins = []
            return self.JOINS(*joins, join_list=join_list, **kwargs)
        except Exception as e:
            self.joins = former_joins
            raise e
        return self

    def WITH_alias_as_self(self, alias: SQLCol = None) -> WithQuery:
        """
        Creates a WithQuery with the current SelectQuery as its query.
        Args:
            alias (SQLCol, optional): The alias for the WithQuery. Defaults to None.
        Returns:
            WithQuery: A new instance of WithQuery with the current SelectQuery as its query.
        """
        return WithQuery(
            self, alias=alias, ignore_forbidden_chars=self.ignore_forbidden_chars
        )

    def __eq__(self, other):
        return False

    def __ne__(self, other):
        return True

    @staticmethod
    def validate_join_list(joins: List[JoinQuery]) -> None:
        """
        Validates the list of joins.
        Args:
            joins (List[JoinQuery]): The list of joins to validate.
        Raises:
            TypeError: If any join is not an instance of JoinQuery.
        """
        if joins is None:
            return
        if not isinstance(joins, Iterable):
            raise TypeError("joins must be a Iterable")
        if not all(isinstance(join, JoinQuery) for join in joins):
            raise TypeError("All joins must be instances of JoinQuery")

    @staticmethod
    def validate_with_list(withs: List[WithQuery]) -> None:
        """
        Validates the list of withs.
        Args:
            withs (List[WithQuery]): The list of withs to validate.
        Raises:
            TypeError: If any with is not an instance of WithQuery.
        """
        if withs is None:
            return
        if not isinstance(withs, Iterable):
            raise TypeError("withs must be a Iterable")
        if not all(isinstance(with_, WithQuery) for with_ in withs):
            raise TypeError("All withs must be instances of WithQuery")

    def with_queries_as(
        self, *with_queries: WithQuery, _new_withs=True, **as_with_dict
    ) -> SelectQuery:
        """
        Adds one or more WITH queries to the current SelectQuery instance.
        This method allows you to define Common Table Expressions (CTEs) using
        the provided `WithQuery` objects or keyword arguments. The resulting
        query will include the specified WITH queries.
        Args:
            *with_queries (WithQuery): One or more `WithQuery` objects to be
                included in the WITH clause.
            _new_withs (bool, optional): If True (default), replaces the existing
                WITH queries with the new ones. If False, appends the new WITH
                queries to the existing ones.
            **as_with_dict: Additional WITH queries specified as keyword arguments,
                where the key is the alias and the value is the query.
        Returns:
            SelectQuery: A new `SelectQuery` instance with the updated WITH queries.
        """

        withs = WITH(*with_queries, **as_with_dict).withs
        if not _new_withs:
            withs = self.withs + withs
        my_copy = self.copy()
        my_copy.withs = withs
        return my_copy


select_alias = [
    "set_select",
    "set_selection",
    "set_columns",
    "change_selection",
    "change_columns",
    "select_columns",
]

for alias in select_alias:
    setattr(SelectQuery, alias, SelectQuery.SELECT)


def SELECT(
    column_list: Union[SQLCol, list[SQLCol]] = "*",
    *args: SQLCol,
    ignore_forbidden_characters: bool = False,
    **kwargs: Any,
) -> SelectQuery:
    return SelectQuery._SELECT(
        column_list, *args, ignore_forbidden_chars=ignore_forbidden_characters, **kwargs
    )


SELECT.__doc__ = SelectQuery.SELECT.__doc__


class WithQuery:
    def __new__(
        cls,
        query: SelectQuery,
        alias: SQLCol = None,
        ignore_forbidden_chars: bool = False,
    ):
        if isinstance(query, WithQuery):
            return query
        if not isinstance(query, SelectQuery):
            raise TypeError("query must be an instance of SelectQuery")
        if not isinstance(alias, (SQLCol, type(None))):
            raise TypeError("alias must be an instance of SQLCol or None")
        query = query.copy()
        return super().__new__(cls)

    def __init__(
        self,
        query: SelectQuery,
        alias: SQLCol = None,
        ignore_forbidden_chars: bool = False,
    ):
        self.ignore_forbidden_chars = ignore_forbidden_chars
        self.query = query
        self.alias = alias

    @property
    def alias(self) -> SQLCol:
        if self._alias is None:
            return self.query.alias
        return self._alias

    @alias.setter
    def alias(self, value: SQLCol) -> None:
        if value is not None:
            if isinstance(value, SQLExpression):
                value = value.expression_value
            validate_name(value, validate_chars=not self.ignore_forbidden_chars)
        self._alias = value

    def placeholder_pair(self, include_with=True) -> tuple[str, Any]:
        alias = self.alias
        if alias is None:
            raise ValueError("Alias must be set for the WithQuery")
        string, params = self.query.placeholder_pair(include_alias=False)
        string = f"{alias} AS {ensure_bracketed(string)}"
        if include_with:
            string = f"WITH {string}"
        return string, params

    def AS(self, alias: SQLCol) -> WithQuery:
        """
        Sets the alias for the WithQuery.
        Args:
            alias (SQLCol): The alias to set.
        Returns:
            WithQuery: The current instance of WithQuery.
        """
        self.alias = alias
        return self

    def copy(self) -> WithQuery:
        """
        Creates a copy of the current WithQuery.
        Returns:
            WithQuery: A new instance of WithQuery with the same attributes as the current instance.
        """
        return WithQuery(
            self.query.copy(),
            alias=self.alias,
            ignore_forbidden_chars=self.ignore_forbidden_chars,
        )


@normalize_args()
def WITH(
    *with_queries: WithQuery, base: Optional[SelectQuery] = None, **as_with_dict
) -> SelectQuery:
    """
    Constructs a WITH SQL query.
    Args:
        *with_queries (WithQuery): The WITH queries to include.
        **as_with_dict (dict): Additional WITH queries as keyword arguments.
    Returns:
        SelectQuery: A SelectQuery containing the WITH queries.
    Example:
        >>> query = WITH(WithQuery(SELECT("id").FROM("users")), WithQuery(SELECT("name").FROM("products")))
        >>> print(query.placeholder_pair())
        WITH id AS (SELECT id FROM users), name AS (SELECT name FROM products)
    """
    with_queries = list(with_queries)
    if not with_queries and not as_with_dict:
        raise ValueError("At least one with_query or as_with_dict must be provided")
    for key, value in as_with_dict.items():
        if not isinstance(key, SQLCol):
            raise TypeError("Key must be an instance of SQLCol")
        if not isinstance(value, SelectQuery):
            raise TypeError("Value must be an instance of SelectQuery")
        new_with = WithQuery(value, alias=key)
        with_queries.append(new_with)
    for i, with_query in enumerate(with_queries):
        if isinstance(with_query, SelectQuery):
            with_queries[i] = WithQuery(with_query)
    base: SelectQuery = base or SelectQuery()

    base.withs = with_queries
    return base
