"""SQL generation utilities for Duckalog views."""

from __future__ import annotations

from typing import Any, Dict

from .config import Config, ViewConfig


def quote_ident(value: str) -> str:
    """Quote a SQL identifier using double quotes.

    This helper wraps a string in double quotes and escapes any embedded
    double quotes according to SQL rules.

    Args:
        value: Identifier to quote (for example, a view or column name).

    Returns:
        The identifier wrapped in double quotes.

    Example:
        >>> quote_ident("events")
        '"events"'
    """

    escaped = value.replace('"', '""')
    return f'"{escaped}"'


def _quote_literal(value: str) -> str:
    escaped = value.replace("'", "''")
    return f"'{escaped}'"


def render_options(options: Dict[str, Any]) -> str:
    """Render a mapping of options into scan-function arguments.

    The resulting string is suitable for appending to a ``*_scan`` function
    call. Keys are sorted alphabetically to keep output deterministic.

    Args:
        options: Mapping of option name to value (str, bool, int, or float).

    Returns:
        A string that starts with ``, `` when options are present (for example,
        ``", hive_partitioning=TRUE"``) or an empty string when no options
        are provided.

    Raises:
        TypeError: If a value has a type that cannot be rendered safely.
    """

    if not options:
        return ""

    parts = []
    for key in sorted(options):
        value = options[key]
        if isinstance(value, bool):
            rendered = "TRUE" if value else "FALSE"
        elif isinstance(value, (int, float)) and not isinstance(value, bool):
            rendered = str(value)
        elif isinstance(value, str):
            rendered = _quote_literal(value)
        else:
            raise TypeError(
                f"Unsupported option value for '{key}': {value!r}. Expected str, bool, int, or float."
            )
        parts.append(f"{key}={rendered}")

    return ", " + ", ".join(parts)


def generate_view_sql(view: ViewConfig) -> str:
    """Generate a ``CREATE OR REPLACE VIEW`` statement for a single view.

    Args:
        view: The :class:`ViewConfig` to generate SQL for.

    Returns:
        A single SQL statement that creates or replaces the view.
    """

    view_name = quote_ident(view.name)
    if view.sql:
        body = view.sql
    else:
        body = _render_view_body(view)

    return f"CREATE OR REPLACE VIEW {view_name} AS\n{body};"


def _render_view_body(view: ViewConfig) -> str:
    source = view.source
    if source is None:
        raise ValueError("View must define 'sql' or 'source'")

    if source in {"parquet", "delta"}:
        func = f"{source}_scan"
        assert view.uri is not None  # enforced by schema
        scan_call = f"{func}({_quote_literal(view.uri)}{render_options(view.options)})"
        return f"SELECT * FROM {scan_call}"

    if source == "iceberg":
        if view.uri:
            scan_call = f"iceberg_scan({_quote_literal(view.uri)}{render_options(view.options)})"
        else:
            assert view.catalog and view.table  # enforced by schema
            scan_call = (
                "iceberg_scan("
                f"{_quote_literal(view.catalog)}, {_quote_literal(view.table)}"
                f"{render_options(view.options)})"
            )
        return f"SELECT * FROM {scan_call}"

    if source in {"duckdb", "sqlite", "postgres"}:
        assert view.database and view.table  # enforced by schema
        return f"SELECT * FROM {view.database}.{view.table}"

    raise ValueError(f"Unsupported view source '{source}'")


def generate_all_views_sql(config: Config) -> str:
    """Generate SQL for all views in a configuration.

    The output includes a descriptive header with the config version followed
    by a ``CREATE OR REPLACE VIEW`` statement for each view in the order they
    appear in the configuration.

    Args:
        config: The validated :class:`Config` instance to render.

    Returns:
        A multi-statement SQL script suitable for use as a catalog definition.
    """

    lines = [
        "-- Generated by Duckalog",
        f"-- Config version: {config.version}",
        "",
    ]

    for index, view in enumerate(config.views):
        lines.append(generate_view_sql(view))
        if index != len(config.views) - 1:
            lines.append("")

    return "\n".join(lines)


__all__ = [
    "quote_ident",
    "render_options",
    "generate_view_sql",
    "generate_all_views_sql",
]
