"""BLCE DDL Generator — generate Snowflake CREATE TABLE DDL from ProposedModel.

Generates dimension tables with surrogate keys and SCD2 columns,
and fact tables with FK columns, measures, and metadata.
"""
from __future__ import annotations

import logging
from typing import Any, Dict, List

from .contracts import (
    GeneratedDDL,
    ProposedDimension,
    ProposedFact,
    ProposedModel,
)

logger = logging.getLogger(__name__)

# SCD2 boilerplate columns
_SCD2_COLUMNS = [
    ("SOURCE_SYSTEM", "STRING", "Source ERP system identifier"),
    ("LOAD_RUN_ID", "STRING", "ETL run identifier"),
    ("EFFECTIVE_FROM_TS", "TIMESTAMP_NTZ", "Row effective start timestamp"),
    ("EFFECTIVE_TO_TS", "TIMESTAMP_NTZ", "Row effective end timestamp (NULL = current)"),
    ("IS_CURRENT", "BOOLEAN", "Whether this is the current version of the row"),
]

# Standard metadata columns for facts
_FACT_META_COLUMNS = [
    ("SOURCE_SYSTEM", "STRING", "Source ERP system identifier"),
    ("LOAD_RUN_ID", "STRING", "ETL run identifier"),
    ("LOADED_AT", "TIMESTAMP_NTZ", "Row load timestamp"),
]


class DDLGenerator:
    """Generate Snowflake DDL from a ProposedModel."""

    def generate_dimension_ddl(
        self,
        dim: ProposedDimension,
        schema: str = "EDW",
    ) -> GeneratedDDL:
        """Generate CREATE TABLE DDL for a dimension.

        Args:
            dim: Proposed dimension specification.
            schema: Target Snowflake schema.

        Returns:
            GeneratedDDL with the SQL statement.
        """
        cols: List[str] = []
        comments: Dict[str, str] = {}

        # Surrogate key
        sk_name = dim.key_column or f"{dim.name}_SK"
        cols.append(f"    {sk_name} NUMBER AUTOINCREMENT PRIMARY KEY")
        comments[sk_name] = "Surrogate key (auto-increment)"

        # Natural key
        nk_name = dim.natural_key or f"{dim.name.replace('DIM_', '')}_ID"
        cols.append(f"    {nk_name} STRING NOT NULL")
        comments[nk_name] = "Natural/business key"

        # Attributes
        for attr in dim.attributes:
            attr_upper = attr.upper()
            dtype = self._infer_data_type(attr_upper)
            cols.append(f"    {attr_upper} {dtype}")
            comments[attr_upper] = f"Dimension attribute"

        # Hierarchy levels (if not already in attributes)
        attr_set = {a.upper() for a in dim.attributes}
        for level in dim.hierarchy_levels:
            level_upper = level.upper()
            if level_upper not in attr_set:
                cols.append(f"    {level_upper} STRING")
                comments[level_upper] = "Hierarchy level"

        # SCD2 columns (if applicable)
        if dim.scd_type >= 2:
            for col_name, col_type, col_comment in _SCD2_COLUMNS:
                cols.append(f"    {col_name} {col_type}")
                comments[col_name] = col_comment
        else:
            # Still add source system and load metadata
            cols.append("    SOURCE_SYSTEM STRING")
            cols.append("    LOAD_RUN_ID STRING")
            comments["SOURCE_SYSTEM"] = "Source ERP system identifier"
            comments["LOAD_RUN_ID"] = "ETL run identifier"

        col_block = ",\n".join(cols)
        ddl = (
            f"CREATE TABLE IF NOT EXISTS {schema}.{dim.name} (\n"
            f"{col_block}\n"
            f");"
        )

        # Add column comments
        comment_stmts = []
        for col, desc in comments.items():
            comment_stmts.append(
                f"COMMENT ON COLUMN {schema}.{dim.name}.{col} IS '{desc}';"
            )

        full_sql = ddl + "\n\n" + "\n".join(comment_stmts)

        return GeneratedDDL(
            object_name=dim.name,
            object_type="dimension",
            ddl_sql=full_sql,
            column_comments=comments,
            source_object=", ".join(dim.source_tables),
        )

    def generate_fact_ddl(
        self,
        fact: ProposedFact,
        dimensions: List[ProposedDimension],
        schema: str = "EDW",
    ) -> GeneratedDDL:
        """Generate CREATE TABLE DDL for a fact table.

        Args:
            fact: Proposed fact specification.
            dimensions: All proposed dimensions (for FK references).
            schema: Target Snowflake schema.

        Returns:
            GeneratedDDL with the SQL statement.
        """
        cols: List[str] = []
        comments: Dict[str, str] = {}

        # Surrogate key
        sk = f"{fact.name}_SK"
        cols.append(f"    {sk} NUMBER AUTOINCREMENT PRIMARY KEY")
        comments[sk] = "Surrogate key"

        # Date key (standard)
        cols.append("    DATE_KEY NUMBER NOT NULL")
        comments["DATE_KEY"] = "FK to DIM_DATE"

        # Dimension FKs
        dim_map = {d.name: d for d in dimensions}
        for fk in fact.dimension_fks:
            fk_upper = fk.upper()
            if fk_upper not in ("DATE_KEY",):
                cols.append(f"    {fk_upper} NUMBER")
                comments[fk_upper] = f"FK to dimension"

        # Grain columns not already covered by FKs
        fk_set = {fk.upper() for fk in fact.dimension_fks} | {"DATE_KEY"}
        for gc in fact.grain_columns:
            gc_upper = gc.upper()
            if gc_upper not in fk_set:
                dtype = self._infer_data_type(gc_upper)
                cols.append(f"    {gc_upper} {dtype}")
                comments[gc_upper] = "Grain column"

        # Measures
        for m in fact.measures:
            m_upper = m.upper()
            cols.append(f"    {m_upper} NUMBER(18,4)")
            comments[m_upper] = "Measure"

        # Metadata columns
        for col_name, col_type, col_comment in _FACT_META_COLUMNS:
            cols.append(f"    {col_name} {col_type}")
            comments[col_name] = col_comment

        col_block = ",\n".join(cols)
        ddl = (
            f"CREATE TABLE IF NOT EXISTS {schema}.{fact.name} (\n"
            f"{col_block}\n"
            f");"
        )

        comment_stmts = []
        for col, desc in comments.items():
            comment_stmts.append(
                f"COMMENT ON COLUMN {schema}.{fact.name}.{col} IS '{desc}';"
            )

        full_sql = ddl + "\n\n" + "\n".join(comment_stmts)

        return GeneratedDDL(
            object_name=fact.name,
            object_type="fact",
            ddl_sql=full_sql,
            column_comments=comments,
            source_object=", ".join(fact.source_tables),
        )

    def generate_all(
        self,
        model: ProposedModel,
        schema: str = "EDW",
    ) -> List[GeneratedDDL]:
        """Generate DDL for all dimensions and facts in a model.

        Dimensions are generated first (for FK references), then facts.
        """
        ddls: List[GeneratedDDL] = []

        for dim in model.dimensions:
            ddls.append(self.generate_dimension_ddl(dim, schema))

        for fact in model.facts:
            ddls.append(self.generate_fact_ddl(fact, model.dimensions, schema))

        return ddls

    # ------------------------------------------------------------------
    # Helpers
    # ------------------------------------------------------------------

    @staticmethod
    def _infer_data_type(col_name: str) -> str:
        """Infer Snowflake data type from column name patterns."""
        upper = col_name.upper()

        if upper.endswith(("_SK", "_KEY", "_HID", "_TID")):
            return "NUMBER"
        if upper.endswith(("_ID", "_CODE", "_NUM")):
            return "STRING"
        if any(p in upper for p in ("DATE", "_DT", "_TS", "TIMESTAMP")):
            return "DATE"
        if any(p in upper for p in ("AMOUNT", "AMT", "QTY", "PRICE", "RATE", "COST", "VOLUME", "BALANCE")):
            return "NUMBER(18,4)"
        if upper.endswith(("_FLAG", "_IND")):
            return "BOOLEAN"
        if upper.endswith(("_PCT", "_PERCENT")):
            return "NUMBER(5,2)"

        return "STRING"
