
class Condition(object):
    def __init__(self, left, operator, right):
        self.left = left
        self.operator = operator
        self.right = right
    
    def compile(self, compiler, context=None):
        # Left is always a Field, so use str(left) -> "table"."column"
        # In SET context (UPDATE/UPSERT), left side must be unqualified: "column" not "table"."column"
        if context == 'set' and isinstance(self.left, Field):
            left_sql = f'"{self.left.name}"'
        else:
            left_sql = str(self.left)
        # If right is also a Field (e.g. JOIN ON), render as identifier, not parameter
        if isinstance(self.right, Field):
            sql = f"{left_sql} {self.operator} {self.right}"
            return sql, []
        placeholder = compiler.next_placeholder()
        sql = f"{left_sql} {self.operator} {placeholder}"
        return sql, [self.right]

    def __str__(self):
        # Return string representation with literal value (quoted)
        # Use simple quoting logic since we don't have access to Field._quote easily unless we duplicate
        val = self.right
        if isinstance(val, str): qval = f"'{val}'"
        elif val is None: qval = "NULL"
        elif val is True: qval = "TRUE"
        elif val is False: qval = "FALSE"
        else: qval = str(val)
        return f"{self.left} {self.operator} {qval}"

    def __and__(self, other):
        return BooleanCondition(self, "AND", other)
    
    def __or__(self, other):
        return BooleanCondition(self, "OR", other)


class BooleanCondition(object):
    def __init__(self, left, operator, right):
        self.left = left
        self.operator = operator
        self.right = right
        
    def compile(self, compiler):
        l_sql, l_params = self.left.compile(compiler) if hasattr(self.left, 'compile') else (str(self.left), [])
        r_sql, r_params = self.right.compile(compiler) if hasattr(self.right, 'compile') else (str(self.right), [])
        
        return f"({l_sql}) {self.operator} ({r_sql})", l_params + r_params

    def __str__(self):
        return f"({self.left}) {self.operator} ({self.right})"

    def __and__(self, other):
        return BooleanCondition(self, "AND", other)

    def __or__(self, other):
        return BooleanCondition(self, "OR", other)


class InCondition(Condition):
    def compile(self, compiler):
        if isinstance(self.right, (list, tuple)):
            placeholders = []
            params = []
            for val in self.right:
                placeholders.append(compiler.next_placeholder())
                params.append(val)
            sql = f"{self.left} {self.operator} ({', '.join(placeholders)})"
            return sql, params
        elif hasattr(self.right, '_state'):
            # Query object — compile as inline subquery
            subquery_sql, subquery_params = compiler._compile_single(self.right._state)
            return f"{self.left} {self.operator} ({subquery_sql})", subquery_params
        else:
            p = compiler.next_placeholder()
            return f"{self.left} {self.operator} ({p})", [self.right]

    def __str__(self):
        if isinstance(self.right, (list, tuple)):
            values = []
            for x in self.right:
                if isinstance(x, str): values.append(f"'{x}'")
                else: values.append(str(x))
            val_str = ", ".join(values)
            return f"{self.left} {self.operator} ({val_str})"
        elif hasattr(self.right, '_state'):
            return f"{self.left} {self.operator} ({self.right})"
        return f"{self.left} {self.operator} ({self.right})"


class BetweenCondition(object):
    def __init__(self, field, lower, upper):
        self.field = field
        self.lower = lower
        self.upper = upper

    def compile(self, compiler):
        p1 = compiler.next_placeholder()
        p2 = compiler.next_placeholder()
        return f"{self.field} BETWEEN {p1} AND {p2}", [self.lower, self.upper]

    def __str__(self):
        # Quote lower/upper if strings
        l = f"'{self.lower}'" if isinstance(self.lower, str) else str(self.lower)
        u = f"'{self.upper}'" if isinstance(self.upper, str) else str(self.upper)
        return f"{self.field} BETWEEN {l} AND {u}"

    def __and__(self, other):
        return BooleanCondition(self, "AND", other)

    def __or__(self, other):
        return BooleanCondition(self, "OR", other)


class NullCondition(object):
    """Represents an IS NULL or IS NOT NULL condition."""
    def __init__(self, field, is_null=True):
        self.field = field
        self.is_null = is_null

    def compile(self, compiler, context=None):
        if context == 'set':
            col = f'"{self.field.name}"' if isinstance(self.field, Field) else str(self.field)
            return f"{col} = NULL", []
        op = "IS NULL" if self.is_null else "IS NOT NULL"
        return f"{self.field} {op}", []

    def __str__(self):
        op = "IS NULL" if self.is_null else "IS NOT NULL"
        return f"{self.field} {op}"

    def __and__(self, other):
        return BooleanCondition(self, "AND", other)

    def __or__(self, other):
        return BooleanCondition(self, "OR", other)


class JsonbCondition(object):
    """Condition for JSONB operators: ?, ?|, ?&, @>, <@."""

    def __init__(self, field, operator, value):
        self.field = field
        self.operator = operator
        self.value = value

    def _prepare(self, value):
        """Prepare the value for binding.

        @> and <@ expect a JSON string when the input is a dict or list.
        ?| and ?& expect a raw list (the driver casts it to a PG array).
        ? expects a plain scalar.
        """
        if self.operator in ('@>', '<@') and isinstance(value, (dict, list)):
            import json
            return json.dumps(value)
        return value

    def compile(self, compiler):
        placeholder = compiler.next_placeholder()
        sql = f"{self.field} {self.operator} {placeholder}"
        return sql, [self._prepare(self.value)]

    def __str__(self):
        val = self._prepare(self.value)
        if isinstance(val, str):
            return f"{self.field} {self.operator} '{val}'"
        return f"{self.field} {self.operator} {val}"

    def __and__(self, other):
        return BooleanCondition(self, "AND", other)

    def __or__(self, other):
        return BooleanCondition(self, "OR", other)


class NotCondition(object):
    """Represents a NOT condition."""
    def __init__(self, condition):
        self.condition = condition

    def compile(self, compiler):
        if hasattr(self.condition, 'compile'):
            sql, params = self.condition.compile(compiler)
            return f"NOT ({sql})", params
        return f"NOT ({self.condition})", []

    def __str__(self):
        return f"NOT ({self.condition})"

    def __and__(self, other):
        return BooleanCondition(self, "AND", other)

    def __or__(self, other):
        return BooleanCondition(self, "OR", other)


class ConflictSetClause(object):
    """Compilable SET clause for ON CONFLICT DO UPDATE."""
    def __init__(self, column, value):
        self.column = column
        self.value = value

    def compile(self, compiler, context=None):
        placeholder = compiler.next_placeholder()
        return f'"{self.column}" = {placeholder}', [self.value]

    def __str__(self):
        if isinstance(self.value, str):
            return f'"{self.column}" = \'{self.value}\''
        return f'"{self.column}" = {self.value}'


class OrderedField:
    """Wrapper for a Field with ordering direction (ASC/DESC)."""
    
    def __init__(self, field, direction='ASC'):
        self.field = field
        self.direction = direction
    
    def __str__(self):
        return f"{self.field} {self.direction}"
    
    def __repr__(self):
        return str(self)


class Field(object):
    def __init__(self, name, table=None):
        self.name = name
        self.table = table

    def __str__(self):
        if self.table:
            t_name = self.table._alias or self.table._name
            return f'"{t_name}"."{self.name}"'
        return f'"{self.name}"'

    def __repr__(self):
        return str(self)

    def __hash__(self):
        table_name = self.table._name if self.table else None
        return hash((self.name, table_name))

    def __eq__(self, other):
        if other is None: return self.IS_NULL()
        return Condition(self, '=', other)

    def __ne__(self, other):
        if other is None: return self.IS_NOT_NULL()
        return Condition(self, '<>', other)

    def __gt__(self, other):
        return Condition(self, '>', other)

    def __ge__(self, other):
        return Condition(self, '>=', other)

    def __lt__(self, other):
        return Condition(self, '<', other)

    def __le__(self, other):
        return Condition(self, '<=', other)

    # For logical operators on Fields (e.g. boolean fields), we need to be careful.
    # But usually & | are used on Conditions.
    # If used on Field directly (e.g. field1 & field2), it implies boolean columns?
    # Or bitwise? Assuming boolean logic for now as per previous behavior.
    
    def __and__(self, other):
        # This creates a BooleanCondition? Or just string?
        # If we want parameterization, everything must be Condition.
        # But Field & Field is weird unless they are boolean.
        # Let's assume Condition.
        return BooleanCondition(self, "AND", other)

    def __or__(self, other):
        return BooleanCondition(self, "OR", other)

    def __invert__(self):
        return NotCondition(self)
        
    def IN(self, other):
        # IN takes a list of values.
        # We need a special Condition for IN that takes a list and generates multiple placeholders.
        return InCondition(self, 'IN', other)

    def NOT_IN(self, other):
        return InCondition(self, 'NOT IN', other)

    def LIKE(self, other):
        return Condition(self, 'LIKE', other)

    def ILIKE(self, other):
        return Condition(self, 'ILIKE', other)

    def BETWEEN(self, lower, upper):
        return BetweenCondition(self, lower, upper)

    def IS_NULL(self):
        return NullCondition(self, is_null=True)

    def IS_NOT_NULL(self):
        return NullCondition(self, is_null=False)

    def HAS_KEY(self, key):
        """JSONB key existence: field ? key"""
        return JsonbCondition(self, '?', key)

    def HAS_ANY(self, keys):
        """JSONB any key existence: field ?| array[keys]"""
        return JsonbCondition(self, '?|', keys)

    def HAS_ALL(self, keys):
        """JSONB all keys existence: field ?& array[keys]"""
        return JsonbCondition(self, '?&', keys)

    def CONTAINS(self, value):
        """JSONB containment: field @> value"""
        return JsonbCondition(self, '@>', value)

    def CONTAINED_BY(self, value):
        """JSONB reverse containment: field <@ value"""
        return JsonbCondition(self, '<@', value)

    def AS(self, alias):
        return AliasedField(self, alias)

    def __neg__(self):
        """Unary minus operator for descending order in ORDER BY."""
        return OrderedField(self, 'DESC')

    def __pos__(self):
        """Unary plus operator for ascending order in ORDER BY."""
        return OrderedField(self, 'ASC')


class AliasedField:
    """A Field with an alias, preserving the original Field for conditions."""

    def __init__(self, field, alias):
        self._field = field
        self._alias = alias

    def __str__(self):
        return f'{self._field} AS "{self._alias}"'

    def __repr__(self):
        return str(self)

    def compile(self, compiler):
        return str(self), []


class Table(object):
    def __init__(self, name, schema=None, alias=None):
        self._name = name
        self._schema = schema
        self._alias = alias

    def __getattr__(self, name):
        if name.startswith('__') and name.endswith('__'):
            raise AttributeError(name)
        return Field(name, table=self)

    def __str__(self):
        name = f'"{self._name}"'
        if self._schema:
            name = f'"{self._schema}".{name}'
        
        if self._alias:
            return f'{name} AS "{self._alias}"'
        return name
    
    def __tn__(self):
        if self._schema:
            return f'"{self._schema}"."{self._name}"'
        return f'"{self._name}"'

    def AS(self, alias):
        return Table(self._name, self._schema, alias)
    
    def alias(self, alias):
        return self.AS(alias)
