"""Build parameterized SQL queries from filter entries."""

from __future__ import annotations

from .dialect import Dialect, PostgresDialect

_default_dialect = PostgresDialect()


def parse_filter_entries(text: str) -> list[dict]:
    """Parse 'name:juli%, status:active' into [{"column": "name", "value": "juli%"}, ...]"""
    if not text or not text.strip():
        return []
    entries = []
    for part in text.split(","):
        part = part.strip()
        if ":" not in part:
            continue
        col, _, val = part.partition(":")
        col = col.strip()
        val = val.strip()
        if col and val:
            entries.append({"column": col, "value": val})
    return entries


def _wrap_wildcards(value: str) -> str:
    """Auto-wrap value with % wildcards if user didn't provide them."""
    if "%" not in value:
        return f"%{value}%"
    return value


def _parse_comparison(value: str) -> tuple[str, str] | None:
    """Check if value starts with a comparison operator. Returns (sql_op, numeric_value) or None."""
    for prefix, op in [(">=", ">="), ("<=", "<="), (">", ">"), ("<", "<"), ("=", "=")]:
        if value.startswith(prefix):
            num = value[len(prefix):].strip()
            if num:
                try:
                    float(num)
                    return op, num
                except ValueError:
                    return None
    return None


def _build_where_clause(
    valid_columns: list[str],
    include: str = "",
    exclude: str = "",
    param_offset: int = 0,
    dialect: Dialect | None = None,
) -> tuple[str, list]:
    """Build a WHERE clause from include/exclude filters. Returns (where_sql, params)."""
    dialect = dialect or _default_dialect
    params: list = []
    param_idx = param_offset

    conditions = []

    for entry in parse_filter_entries(include):
        if entry["column"] not in valid_columns:
            raise ValueError(f"Invalid column: {entry['column']}")
        param_idx += 1
        cmp = _parse_comparison(entry["value"])
        if cmp:
            op, num = cmp
            col_expr = dialect.cast(dialect.quote(entry["column"]), "numeric")
            conditions.append(f"{col_expr} {op} {dialect.param(param_idx)}")
            params.append(float(num))
        elif entry["value"].startswith("="):
            # Exact match: =value
            exact_val = entry["value"][1:]
            col_expr = dialect.cast(dialect.quote(entry["column"]), "text")
            conditions.append(f"{col_expr} = {dialect.param(param_idx)}")
            params.append(exact_val)
        else:
            col_expr = dialect.cast(dialect.quote(entry["column"]), "text")
            conditions.append(f"LOWER({col_expr}) LIKE {dialect.param(param_idx)}")
            params.append(_wrap_wildcards(entry["value"].lower()))

    for entry in parse_filter_entries(exclude):
        if entry["column"] not in valid_columns:
            raise ValueError(f"Invalid column: {entry['column']}")
        param_idx += 1
        cmp = _parse_comparison(entry["value"])
        if cmp:
            op, num = cmp
            negate = {">" : "<=", "<" : ">=", ">=" : "<", "<=" : ">", "=" : "!="}
            col_expr = dialect.cast(dialect.quote(entry["column"]), "numeric")
            conditions.append(f"{col_expr} {negate[op]} {dialect.param(param_idx)}")
            params.append(float(num))
        elif entry["value"].startswith("="):
            # Exact match negation: =value
            exact_val = entry["value"][1:]
            col_expr = dialect.cast(dialect.quote(entry["column"]), "text")
            conditions.append(f"{col_expr} != {dialect.param(param_idx)}")
            params.append(exact_val)
        else:
            col_expr = dialect.cast(dialect.quote(entry["column"]), "text")
            conditions.append(f"LOWER({col_expr}) NOT LIKE {dialect.param(param_idx)}")
            params.append(_wrap_wildcards(entry["value"].lower()))

    where_clause = ""
    if conditions:
        where_clause = " WHERE " + " AND ".join(conditions)

    return where_clause, params


def build_query(
    schema: str,
    table: str,
    valid_columns: list[str],
    include: str = "",
    exclude: str = "",
    columns: str = "",
    limit: int = 500,
    offset: int = 0,
    dialect: Dialect | None = None,
) -> tuple[str, list]:
    """Returns (data_sql, count_sql, param_values_list)."""
    dialect = dialect or _default_dialect

    # SELECT clause
    if columns and columns.strip():
        selected = []
        for c in columns.split(","):
            c = c.strip()
            if not c:
                continue
            if c not in valid_columns:
                raise ValueError(f"Invalid column: {c}")
            selected.append(dialect.quote(c))
        select_clause = ", ".join(selected) if selected else "*"
    else:
        select_clause = "*"

    from_clause = f"{dialect.quote(schema)}.{dialect.quote(table)}"
    where_clause, params = _build_where_clause(valid_columns, include, exclude, dialect=dialect)

    count_sql = f"SELECT COUNT(*) FROM {from_clause}{where_clause}"
    sql = f"SELECT {select_clause} FROM {from_clause}{where_clause} LIMIT {int(limit)} OFFSET {int(offset)}"

    return sql, count_sql, params


_VALID_AGGREGATIONS = {"count", "sum", "avg", "min", "max"}


def build_aggregation_query(
    schema: str,
    table: str,
    valid_columns: list[str],
    aggregation: str,
    column: str | None = None,
    group_by_column: str | None = None,
    include: str = "",
    exclude: str = "",
    dialect: Dialect | None = None,
) -> tuple[str, list]:
    """Build an aggregation query. Returns (sql, params)."""
    dialect = dialect or _default_dialect

    if aggregation not in _VALID_AGGREGATIONS:
        raise ValueError(f"Invalid aggregation: {aggregation}")
    if column and column not in valid_columns:
        raise ValueError(f"Invalid column: {column}")
    if group_by_column and group_by_column not in valid_columns:
        raise ValueError(f"Invalid column: {group_by_column}")

    from_clause = f"{dialect.quote(schema)}.{dialect.quote(table)}"
    where_clause, params = _build_where_clause(valid_columns, include, exclude, dialect=dialect)

    if aggregation == "count":
        agg_expr = "COUNT(*)"
    else:
        if not column:
            raise ValueError(f"Column required for {aggregation} aggregation")
        col_expr = dialect.cast(dialect.quote(column), "numeric")
        agg_expr = f"{aggregation.upper()}({col_expr})"

    if group_by_column:
        gb = dialect.quote(group_by_column)
        sql = (
            f"SELECT {gb} AS {dialect.quote('group')}, {agg_expr} AS value "
            f"FROM {from_clause}{where_clause} GROUP BY {gb} ORDER BY value DESC"
        )
    else:
        sql = f"SELECT {agg_expr} AS value FROM {from_clause}{where_clause}"

    return sql, params
