from abc import ABC, abstractmethod
from typing import List, Any
from .state import QueryState

class SQLCompiler(ABC):
    """
    Abstract base class for converting QueryState into a SQL string.
    Handles dialect differences like placeholders (%s vs $1).
    """

    dialect: str = "generic"

    def __init__(self):
        self._placeholder_count = 0

    @property
    @abstractmethod
    def parameter_placeholder(self) -> str:
        """Return the placeholder string (e.g., %s, $1, ?)"""
        pass
    
    def reset(self):
        self._placeholder_count = 0

    def next_placeholder(self) -> str:
        """Generate the next parameter placeholder"""
        return self.parameter_placeholder

    def compile(self, state: QueryState) -> tuple[str, list]:
        """
        Main entry point. Orchestrates the generation of SQL parts
        in the correct order.
        """
        self.reset()
        
        compiled_parts = []
        bound_parameters = []
        
        # Compile chained previous states
        for prev_state in state.chain:
            sql, params = self._compile_single(prev_state)
            compiled_parts.append(sql)
            bound_parameters.extend(params)
            
        # Compile current state
        sql, params = self._compile_single(state)
        compiled_parts.append(sql)
        bound_parameters.extend(params)
        
        return "; ".join(compiled_parts), bound_parameters


    def _compile_single(self, state: QueryState) -> tuple[str, list]:
        # 0. Transaction management
        if state.statement_type == 'BEGIN':
            return "BEGIN", []
        if state.statement_type == 'COMMIT':
            return "COMMIT", []

        # 1. CTEs (WITH clause)
        sql_parts = []
        parameters = []
        
        if state.ctes:
            cte_parts = []
            has_recursive = False
            for alias, query, recursive in state.ctes:
                if recursive:
                    has_recursive = True
                if isinstance(query, str):
                    subquery_sql = query
                else:
                    # Compile the CTE sub-query through the *parent* compiler
                    # so placeholder numbering continues correctly and
                    # parameters are collected into the parent's list.
                    subquery_sql, subquery_params = self._compile_single(query._state)
                    parameters.extend(subquery_params)
                cte_parts.append(f"{alias} AS ({subquery_sql})")

            keyword = "WITH RECURSIVE" if has_recursive else "WITH"
            sql_parts.append(f"{keyword} {', '.join(cte_parts)}")

        # 2. Main Statement (SELECT / INSERT / UPDATE / DELETE)
        if state.statement_type == 'SELECT':
            sql, params = self._compile_select(state)
            sql_parts.append(sql)
            parameters.extend(params)
        elif state.statement_type == 'INSERT':
            sql, params = self._compile_insert(state)
            sql_parts.append(sql)
            parameters.extend(params)
        elif state.statement_type == 'UPDATE':
            sql, params = self._compile_update(state)
            sql_parts.append(sql)
            parameters.extend(params)
        elif state.statement_type == 'DELETE':
            sql, params = self._compile_delete(state)
            sql_parts.append(sql)
            parameters.extend(params)
            
        return "\n".join(part for part in sql_parts if part), parameters

    def _compile_conditions(self, conditions: List[Any]) -> tuple[str, list]:
        """
        Compile a list of WHERE conditions (strings or Condition objects).
        Returns (sql_string, parameters).
        """
        sql_parts = []
        params = []
        
        for cond in conditions:
            if hasattr(cond, 'compile'):
                sql, p = cond.compile(self)
                sql_parts.append(sql)
                params.extend(p)
            else:
                s = str(cond).strip()
                if s:
                    sql_parts.append(s)
                    
        return " AND ".join(sql_parts), params

    def _compile_select(self, state: QueryState) -> tuple[str, list]:
        parts = []
        parameters = []
        
        # SELECT
        selects = state.selects or ['*']
        # Convert list to string, handling objects if necessary
        select_parts = []
        for s in selects:
            if hasattr(s, 'compile'):
                # Window function or other compilable object
                sql, params = s.compile(self)
                select_parts.append(sql)
                parameters.extend(params)
            else:
                select_parts.append(str(s))
        
        select_str = ", ".join(select_parts)
        if state.distinct: parts.append(f"SELECT DISTINCT {select_str}")
        else: parts.append(f"SELECT {select_str}")
        
        # FROM
        if state.from_sources:
            from_str = ", ".join(str(f) for f in state.from_sources)
            parts.append(f"FROM {from_str}")
            
        # JOIN
        if state.joins:
            for join_entry in state.joins:
                if isinstance(join_entry, tuple):
                    join_sql, condition = join_entry
                    if condition is not None and hasattr(condition, 'compile'):
                        cond_sql, cond_params = condition.compile(self)
                        parts.append(f"{join_sql} ON {cond_sql}")
                        parameters.extend(cond_params)
                    elif condition is not None:
                        parts.append(f"{join_sql} ON {condition}")
                    else:
                        parts.append(join_sql)
                else:
                    parts.append(str(join_entry))
            
        # WHERE
        if state.wheres:
            where_sql, where_params = self._compile_conditions(state.wheres)
            if where_sql:
                parts.append(f"WHERE {where_sql}")
                parameters.extend(where_params)
            
        # GROUP BY
        if state.groups:
            group_str = ", ".join(str(g) for g in state.groups)
            parts.append(f"GROUP BY {group_str}")
        
        # HAVING
        if state.havings:
            having_sql, having_params = self._compile_conditions(state.havings)
            if having_sql:
                parts.append(f"HAVING {having_sql}")
                parameters.extend(having_params)

        # WINDOW clause (named windows) - comes after GROUP BY, before ORDER BY
        if state.window_definitions:
            window_parts = []
            for name, spec in state.window_definitions.items():
                spec_sql, spec_params = spec.compile(self)
                window_parts.append(f"{name} AS ({spec_sql})")
                parameters.extend(spec_params)
            parts.append(f"WINDOW {', '.join(window_parts)}")

        # Set operations (UNION, UNION ALL, INTERSECT, EXCEPT)
        # Compiled through the same compiler instance so placeholder
        # numbering continues correctly across all branches.
        if state.union_queries:
            for operator, other in state.union_queries:
                if isinstance(other, str):
                    parts.append(f"{operator} {other}")
                else:
                    other_sql, other_params = self._compile_select(other)
                    parts.append(f"{operator} {other_sql}")
                    parameters.extend(other_params)

        # ORDER BY — applies to the full result after any set operations
        if state.orders:
            order_str = ", ".join(str(o) for o in state.orders)
            parts.append(f"ORDER BY {order_str}")

        # LIMIT / OFFSET
        if state.limit is not None:
             parts.append(f"LIMIT {state.limit}")
        if state.offset is not None:
             parts.append(f"OFFSET {state.offset}")
        if state.returning:
            parts.append(f"RETURNING {', '.join(state.returning)}")
        return " ".join(parts), parameters

    def _compile_insert(self, state: QueryState) -> tuple[str, list]:
        parts = []
        parameters = []
        parts.append(f"INSERT INTO {state.insert_table}")
        
        if state.insert_columns:
             parts.append(f"({', '.join(str(c) for c in state.insert_columns)})")
        
        if state.insert_values:
            # Parameterized values
            values_placeholders = []
            for row in state.insert_values:
                row_placeholders = []
                for val in row:
                    row_placeholders.append(self.next_placeholder())
                    parameters.append(val)
                values_placeholders.append(f"({', '.join(row_placeholders)})")
            parts.append(f"VALUES {', '.join(values_placeholders)}")
            
        elif state.values:
             parts.append(f"VALUES {', '.join(state.values)}")
             
        elif state.selects:
             sql, params = self._compile_select(state)
             parts.append(sql)
             parameters.extend(params)
             
        if state.on_conflict_target:
             target = ", ".join(state.on_conflict_target)
             action = state.on_conflict_action
             conflict_clause = f"ON CONFLICT ({target})"
             if state.on_conflict_where:
                 where_sql, where_params = self._compile_conditions(state.on_conflict_where)
                 if where_sql:
                     conflict_clause += f" WHERE {where_sql}"
                     parameters.extend(where_params)
             parts.append(conflict_clause)
             if action == 'DO NOTHING':
                 parts.append("DO NOTHING")
             elif action == 'DO UPDATE':
                 compiled_updates = []
                 for u in state.on_conflict_updates:
                     if hasattr(u, 'compile'):
                         sql, params = u.compile(self, context='set')
                         compiled_updates.append(sql)
                         parameters.extend(params)
                     else:
                         compiled_updates.append(str(u))
                 parts.append(f"DO UPDATE SET {', '.join(compiled_updates)}")

        if state.returning:
            parts.append(f"RETURNING {', '.join(state.returning)}")
            
        return " ".join(parts), parameters

    def _compile_update(self, state: QueryState) -> tuple[str, list]:
        parts = []
        parameters = []
        parts.append(f"UPDATE {state.update_table}")
        
        if state.updates:
             compiled_updates = []
             for u in state.updates:
                 if hasattr(u, 'compile'):
                     sql, params = u.compile(self, context='set')
                     compiled_updates.append(sql)
                     parameters.extend(params)
                 else:
                     compiled_updates.append(str(u))

             parts.append(f"SET {', '.join(compiled_updates)}")
        
        if state.wheres:
             where_sql, where_params = self._compile_conditions(state.wheres)
             if where_sql:
                 parts.append(f"WHERE {where_sql}")
                 parameters.extend(where_params)
             
        if state.returning:
             parts.append(f"RETURNING {', '.join(state.returning)}")
             
        return " ".join(parts), parameters

    def _compile_delete(self, state: QueryState) -> tuple[str, list]:
        parts = []
        parameters = []
        parts.append("DELETE FROM")

        if state.from_sources:
             parts.append(str(state.from_sources[0]))
        elif state.delete_table:
             parts.append(state.delete_table)
             
        if state.wheres:
            where_sql, where_params = self._compile_conditions(state.wheres)
            if where_sql:
                parts.append(f"WHERE {where_sql}")
                parameters.extend(where_params)
            
        if state.returning:
            ret = ", ".join(state.returning)
            parts.append(f"RETURNING {ret}")
            
        return " ".join(parts), parameters

class PostgresCompiler(SQLCompiler):
    dialect = "postgres"

    @property
    def parameter_placeholder(self) -> str:
        return "$%d"

    def next_placeholder(self) -> str:
        self._placeholder_count += 1
        return f"${self._placeholder_count}"

class MySQLCompiler(SQLCompiler):
    dialect = "mysql"

    @property
    def parameter_placeholder(self) -> str:
        return "%s"

class SQLiteCompiler(SQLCompiler):
    dialect = "sqlite"

    @property
    def parameter_placeholder(self) -> str:
        return "?"

class MSSQLCompiler(SQLCompiler):
    dialect = "mssql"

    @property
    def parameter_placeholder(self) -> str:
        return "?"
