"""Table configuration section for :func:`relationalai.config.Config`.

Defines the ``tables:`` section in ``raiconfig.yaml``, which lets users set
table-type defaults and per-table overrides (type, external volume, FQN remapping,
and schema prefix).

Example ``raiconfig.yaml``::

    tables:
      default_type: iceberg        # optional — defaults to native if omitted
      default_volume: "my_volume"  # default external_volume for iceberg tables

      db.schema.table1:            # per-table override
        type: iceberg
        volume: "special_volume"

      db.schema.table2:
        type: native            # explicit opt-out of iceberg default

      foo:
        fqn: "customer.model.foo_special"  # remap logical name to physical FQN
"""

from __future__ import annotations

from typing import Any, Literal

from pydantic import BaseModel, Field, model_validator

# Supported table types
TableType = Literal["iceberg", "native"]

# Keys that belong to TablesConfig itself, not per-table entries
_TABLES_TOP_LEVEL_KEYS = frozenset({"default_type", "default_volume", "default_schema"})


class TableEntryConfig(BaseModel):
    """Per-table configuration entry under ``tables:`` in ``raiconfig.yaml``.

    Attributes
    ----------
    type : "iceberg" or "native", optional
        Override the table type for this specific table.
    volume : str, optional
        External volume name for this iceberg table. Overrides ``default_volume``.
    fqn : str, optional
        Physical fully-qualified name to use instead of the logical table name.
    """

    type: TableType | None = Field(default=None, description="Table type override for this table")
    volume: str | None = Field(default=None, description="External volume override for this iceberg table")
    fqn: str | None = Field(default=None, description="Physical FQN override for this logical table name")


class TablesConfig(BaseModel):
    """Configure table defaults and per-table overrides.

    The ``tables:`` section in ``raiconfig.yaml`` controls how ``model.Table()``
    output tables are created. It supports a default type (iceberg or native),
    a default external volume for iceberg tables, a default schema prefix for
    unqualified table names, and per-table overrides keyed by logical table name
    or FQN.

    Attributes
    ----------
    default_type : "iceberg" or "native"
        Default table type applied to all tables unless overridden. Defaults to
        ``"native"``.
    default_volume : str, optional
        Default ``EXTERNAL_VOLUME`` for iceberg tables. Used when no per-table
        volume is set. If ``None``, Snowflake manages the volume.
    default_schema : str, optional
        Schema prefix prepended to unqualified table names (e.g. ``"mydb.public"``).
        Useful in profiles to namespace tables per environment.
    tables : dict
        Per-table overrides keyed by logical table name or FQN. Each entry is a
        :class:`TableEntryConfig`.

    Example
    -------
    Configure via ``raiconfig.yaml``:

    .. code-block:: yaml

        tables:
          default_type: iceberg
          default_volume: "my_volume"

          db.schema.table1:
            type: iceberg
            volume: "special_volume"

          db.schema.table2:
            type: native

          foo:
            fqn: "customer.model.foo_special"

    Configure in code:

    >>> from relationalai.config import create_config
    >>> cfg = create_config(
    ...     connections={"sf": {...}},
    ...     tables={
    ...         "default_type": "iceberg",
    ...         "default_volume": "my_volume",
    ...     },
    ... )
    """

    default_type: TableType = Field(
        default="native",
        description="Default table type; set to 'iceberg' to opt all tables in",
    )
    default_volume: str | None = Field(
        default=None,
        description="Default external volume for iceberg tables",
    )
    default_schema: str | None = Field(
        default=None,
        description="Schema prefix prepended to unqualified table names",
    )

    # Per-table overrides, populated by the model_validator below
    tables: dict[str, TableEntryConfig] = Field(default_factory=dict)

    @model_validator(mode="before")
    @classmethod
    def extract_table_entries(cls, data: Any) -> Any:
        """Separate known top-level keys from per-table entry keys.

        Any key in the raw dict that is not in ``_TABLES_TOP_LEVEL_KEYS`` is
        treated as a per-table override and moved into ``tables``.
        """
        if not isinstance(data, dict):
            return data

        top_level = {}
        per_table: dict[str, Any] = {}

        for key, value in data.items():
            if key in _TABLES_TOP_LEVEL_KEYS or key == "tables":
                top_level[key] = value
            else:
                per_table[key] = value

        if per_table:
            existing = top_level.get("tables", {})
            top_level["tables"] = {**existing, **per_table}

        return top_level
