from typing import List, Tuple, Any, Optional, Union, Dict
from ..dependencies import SQLExpression, SQLCondition, ensure_sql_expression, no_condition
from .formatters import (
    SQLCol, _normalize_column, _format_table_name,
    normalize_update_values, format_set_clause, _format_or_clause,
    _format_returning, ensure_list, _validate_col_names
)
from expressQL.base import ensure_col
from .utils import validate_monolist
from .formatters import format_conditions
from .formatters import quote_sandwich
from .formatters import _all_have_same_keys
from .formatters import _normalize_column
from ..utils import All



def build_insert_query(
    table_name: str,
    values: Union[Dict[str, Any], List[Dict[str, Any]]],
    or_action: Optional[str] = None,
    on_conflict: Optional["OnConflictQuery"] = None,
    returning: Optional[Union[SQLCol, List[SQLCol]]] = None,
    ignore_forbidden_chars: bool = False
) -> Tuple[str, List[Any]]:
    """
    Builds an INSERT SQL query with optional conflict handling and returning clause.

    Args:
        table_name: The name of the table.
        values: A dict of column-value pairs or a list of such dicts.
        or_action: Optional OR action like "REPLACE" or "IGNORE".
        on_conflict: Optional OnConflictQuery instance.
        returning: Optional returning clause.

    Returns:
        A tuple of (query string, parameters list).
    """
    # --- Normalize table ---
    table_name, table_params = _format_table_name(table_name, validate=not ignore_forbidden_chars)

    # --- Normalize values ---
    if isinstance(values, dict):
        values = [values]
    if not values:
        raise ValueError("At least one row of values must be provided.")
    if not _all_have_same_keys(values):
        raise ValueError("All value dictionaries must have the same keys.")

    # --- Extract columns ---
    col_names = list(values[0].keys())
    _validate_col_names(col_names)  # Reuse your validation
    col_list = [_normalize_column(col, ignore_forbidden_chars=ignore_forbidden_chars) for col in col_names]
    column_str = ", ".join(col_list)

    # --- Prepare placeholders and parameters ---
    rows_placeholder = []
    all_params = table_params
    for row in values:
        row_exprs = [ensure_sql_expression(row[col]) for col in col_names]
        ph_strings = []
        row_params = []
        for expr in row_exprs:
            ph, params = expr.placeholder_pair()
            ph_strings.append(ph)
            row_params.extend(params)
        rows_placeholder.append(f"({', '.join(ph_strings)})")
        all_params.extend(row_params)

    values_clause = ", ".join(rows_placeholder)

    # --- OR action ---
    or_clause = _format_or_clause(or_action)

    # --- ON CONFLICT ---
    if on_conflict:
        conflict_clause, conflict_params = on_conflict.placeholder_pair()
    else:
        conflict_clause, conflict_params = "", []
    all_params.extend(conflict_params)

    # --- RETURNING ---
    returning_clause = _format_returning(returning, ignore_forbidden_chars)

    # --- Final query ---
    query = f"INSERT{or_clause} INTO {table_name} ({column_str}) VALUES {values_clause} {conflict_clause}{returning_clause}"

    return query, all_params

class OnConflictQuery:
    """
    Class to handle ON CONFLICT clauses in SQL queries.
    
    Example usage:
        OnConflictQuery("Nothing")

    """
    valid_do_what = {"UPDATE", "NOTHING"}
    def __init__(self, do_what:str, conflict_cols:list[str], set_clauses:Union[List[Tuple], Dict] = None, condition: SQLCondition = None) -> None:
        self.DO(do_what)
        self.SET(set_clauses)
        self.WHERE(condition)
        conflict_cols = [ensure_col(col) for col in conflict_cols]
        self._conflict_cols = conflict_cols
    @property
    def conflict_cols(self) -> List[SQLCol]:
        return self._conflict_cols
    @conflict_cols.setter
    def conflict_cols(self, value: List[SQLCol]) -> None:
        if isinstance(value, SQLCol):
            value = [value]
            return 
        elif not isinstance(value, list):
            raise TypeError("Conflict columns must be a list of SQLCol objects.")
        validate_monolist(*value, monotype=SQLCol)
        conflict_cols = [ensure_col(col) for col in value]
        self._conflict_cols = conflict_cols
    def __repr__(self):
        return f"<OnConflictQuery do_what='{self.do_what}'>"

    def WHERE(self, condition: SQLCondition) -> "OnConflictQuery":
        if not isinstance(condition, SQLCondition) and condition is not None:
            raise TypeError("Condition must be an instance of SQLCondition.")
        self.condition = condition
        return self
    def DO(self, do_what:str) -> "OnConflictQuery":
        if do_what.upper() not in self.valid_do_what:
            raise ValueError(f"Invalid action: {do_what}. Valid actions are: {self.valid_do_what}")
        self.do_what = do_what.upper()
        return self
    
    def SET(self, *args, **kwargs) -> "OnConflictQuery":
        if not args and not kwargs:
            return self
        if len(args) == 1 and isinstance(args[0], list):
            args = args[0]
        set_dict = {}
        for arg in args:
            if isinstance(arg, dict):
                set_dict.update(arg)
            elif isinstance(arg, (tuple,list)) and len(arg) == 2:
                if isinstance(arg[0], SQLCol):
                    set_dict[arg[0].expression_value] = arg[1]
                    continue
                set_dict[arg[0]] = arg[1]
        set_dict.update(kwargs)
        self.set_clauses = set_dict
        print(set_dict)
        return self
    
    def placeholder_pair(self) -> str:
        """
        Returns a string representation of the ON CONFLICT clause.
        """
        if self.do_what == "NOTHING":
            return "ON CONFLICT DO NOTHING", []
        elif self.do_what == "UPDATE":
            if not self.condition:
                condition = no_condition
            else:
                condition = self.condition
            set_clause, injections = format_set_clause(self.set_clauses)
            condition_clause, condition_injections = format_conditions(condition)
            injections.extend(condition_injections)
            on_conflict_clause = f'ON CONFLICT ({", ".join([col.expression_value for col in self.conflict_cols])}) DO UPDATE {set_clause}{condition_clause}'
            return on_conflict_clause, injections
        else:
            raise ValueError(f"Unknown conflict action: {self.do_what}") 
        

