"""BLCE Quality Expectation Builder — generate expectations from ProposedModel.

Produces NOT NULL, UNIQUE, REFERENTIAL_INTEGRITY, and BETWEEN expectations
for all proposed dimension and fact tables.
"""
from __future__ import annotations

import logging
from typing import Any, Dict, List

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

logger = logging.getLogger(__name__)


class QualityExpectationBuilder:
    """Build quality expectations from a ProposedModel."""

    def build_expectations(
        self, model: ProposedModel
    ) -> List[GeneratedExpectation]:
        """Generate expectations for all tables in the model.

        Rules applied:
        - NOT NULL on keys and required attributes
        - UNIQUE on surrogate + natural keys
        - REFERENTIAL_INTEGRITY for FK joins
        - BETWEEN on numeric measures (non-negative)
        """
        expectations: List[GeneratedExpectation] = []

        for dim in model.dimensions:
            expectations.extend(self._dim_expectations(dim))

        for fact in model.facts:
            expectations.extend(self._fact_expectations(fact, model.dimensions))

        return expectations

    # ------------------------------------------------------------------
    # Dimension expectations
    # ------------------------------------------------------------------

    def _dim_expectations(
        self, dim: ProposedDimension
    ) -> List[GeneratedExpectation]:
        """Generate expectations for a dimension table."""
        exps: List[GeneratedExpectation] = []
        table = dim.name

        # Surrogate key: NOT NULL + UNIQUE
        sk = dim.key_column or f"{dim.name}_SK"
        exps.append(
            GeneratedExpectation(
                table_name=table,
                column_name=sk,
                expectation_type="expect_column_values_to_not_be_null",
                severity="critical",
                rationale="Surrogate key must never be null",
            )
        )
        exps.append(
            GeneratedExpectation(
                table_name=table,
                column_name=sk,
                expectation_type="expect_column_values_to_be_unique",
                severity="critical",
                rationale="Surrogate key must be unique",
            )
        )

        # Natural key: NOT NULL + UNIQUE
        nk = dim.natural_key or f"{dim.name.replace('DIM_', '')}_ID"
        exps.append(
            GeneratedExpectation(
                table_name=table,
                column_name=nk,
                expectation_type="expect_column_values_to_not_be_null",
                severity="high",
                rationale="Natural key must never be null",
            )
        )
        exps.append(
            GeneratedExpectation(
                table_name=table,
                column_name=nk,
                expectation_type="expect_column_values_to_be_unique",
                severity="high",
                rationale="Natural key must be unique",
            )
        )

        # SCD2: IS_CURRENT should be boolean
        if dim.scd_type >= 2:
            exps.append(
                GeneratedExpectation(
                    table_name=table,
                    column_name="IS_CURRENT",
                    expectation_type="expect_column_values_to_be_in_set",
                    kwargs={"value_set": [True, False]},
                    severity="high",
                    rationale="SCD2 IS_CURRENT must be TRUE or FALSE",
                )
            )

        return exps

    # ------------------------------------------------------------------
    # Fact expectations
    # ------------------------------------------------------------------

    def _fact_expectations(
        self,
        fact: ProposedFact,
        dimensions: List[ProposedDimension],
    ) -> List[GeneratedExpectation]:
        """Generate expectations for a fact table."""
        exps: List[GeneratedExpectation] = []
        table = fact.name

        # Date key: NOT NULL
        exps.append(
            GeneratedExpectation(
                table_name=table,
                column_name="DATE_KEY",
                expectation_type="expect_column_values_to_not_be_null",
                severity="critical",
                rationale="Date key is required for all facts",
            )
        )

        # FK columns: NOT NULL
        for fk in fact.dimension_fks:
            exps.append(
                GeneratedExpectation(
                    table_name=table,
                    column_name=fk,
                    expectation_type="expect_column_values_to_not_be_null",
                    severity="high",
                    rationale=f"FK {fk} should not be null",
                )
            )

        # Referential integrity for FK → dimension
        dim_map = {d.name.upper(): d for d in dimensions}
        for fk in fact.dimension_fks:
            for dim_name, dim in dim_map.items():
                dim_short = dim_name.replace("DIM_", "")
                if dim_short.lower() in fk.lower():
                    nk = dim.natural_key or f"{dim_short}_ID"
                    exps.append(
                        GeneratedExpectation(
                            table_name=table,
                            column_name=fk,
                            expectation_type="expect_column_pair_values_to_be_in_set",
                            kwargs={
                                "column_A": fk,
                                "column_B": f"{dim_name}.{nk}",
                            },
                            severity="high",
                            rationale=f"FK integrity: {fk} → {dim_name}.{nk}",
                        )
                    )
                    break

        # Measures: non-negative (BETWEEN 0 and large number)
        for m in fact.measures:
            exps.append(
                GeneratedExpectation(
                    table_name=table,
                    column_name=m,
                    expectation_type="expect_column_values_to_be_between",
                    kwargs={"min_value": -1e12, "max_value": 1e12},
                    severity="medium",
                    rationale=f"Measure {m} should be within reasonable range",
                )
            )

        return exps
