from __future__ import annotations

import polars as pl

from ocf.schemas.validation import validate_canonical_underlying_daily
from ocf.features.ohlcv import build_ohlcv_features
from ocf.features.liquidity import build_liquidity_features
from ocf.features.iv import build_iv_features
from ocf.features.term_structure import build_term_structure_features
from ocf.features.skew_smile import build_skew_smile_features
from ocf.features.build import build_features


def ohlcv_features(
    underlying_daily: pl.DataFrame,
) -> pl.DataFrame:
    """
    Computes OHLCV-based features from canonical underlying daily data.
    """
    # validate input contract
    validate_canonical_underlying_daily(underlying_daily)

    # compute features
    features = build_ohlcv_features(underlying_daily)

    # basic output sanity
    required_keys = {"symbol", "date"}
    missing = required_keys - set(features.columns)
    if missing:
        raise RuntimeError(
            f"OHLCV features missing key columns: {missing}"
        )

    return features

def liquidity_features(
    underlying_daily: pl.DataFrame,
) -> pl.DataFrame:
    """
    Computes liquidity & positioning features from canonical underlying daily data.
    """
    # validate input contract
    validate_canonical_underlying_daily(underlying_daily)

    # compute features
    features = build_liquidity_features(underlying_daily)

    # basic output sanity
    required_keys = {"symbol", "date"}
    missing = required_keys - set(features.columns)
    if missing:
        raise RuntimeError(
            f"Liquidity features missing key columns: {missing}"
        )

    return features

def iv_features(
    underlying_daily: pl.DataFrame,
    *,
    window_days: int = 252,
) -> pl.DataFrame:
    """
    Computes implied-volatility-derived features from canonical underlying data.
    """
    # validate input contract
    validate_canonical_underlying_daily(underlying_daily)

    # compute features
    features = build_iv_features(
        underlying_daily,
        window_days=window_days,
    )

    # basic output sanity
    required_keys = {"symbol", "date"}
    missing = required_keys - set(features.columns)
    if missing:
        raise RuntimeError(
            f"IV features missing key columns: {missing}"
        )

    return features

def term_structure_features(
    underlying_daily: pl.DataFrame,
) -> pl.DataFrame:
    """
    Computes implied-volatility term structure features from canonical underlying data.
    """
    # validate input contract
    validate_canonical_underlying_daily(underlying_daily)

    # compute features
    features = build_term_structure_features(underlying_daily)

    # basic output sanity
    required_keys = {"symbol", "date"}
    missing = required_keys - set(features.columns)
    if missing:
        raise RuntimeError(
            f"Term structure features missing key columns: {missing}"
        )

    return features

def skew_smile_features(
    underlying_daily: pl.DataFrame,
) -> pl.DataFrame:
    """
    Computes smile & skew features from implied-volatility moneyness surfaces.
    """
    # validate input contract
    validate_canonical_underlying_daily(underlying_daily)

    # compute features
    features = build_skew_smile_features(underlying_daily)

    # basic output sanity
    required_keys = {"symbol", "date"}
    missing = required_keys - set(features.columns)
    if missing:
        raise RuntimeError(
            f"Skew/smile features missing key columns: {missing}"
        )

    return features

def underlying_features(
    underlying_daily: pl.DataFrame,
    *,
    include: Iterable[str] | None = None,
    exclude: Iterable[str] | None = None,
    config: Mapping[str, Mapping[str, Any]] | None = None,
) -> pl.DataFrame:
    """
    Computes a composite set of underlying features from canonical data.
    """
    # validate input contract
    validate_canonical_underlying_daily(underlying_daily)

    # delegate to internal orchestrator
    features = build_features(
        underlying_daily,
        include=include,
        exclude=exclude,
        config=config,
    )

    # output sanity
    for col in ("symbol", "date"):
        if col not in features.columns:
            raise RuntimeError(f"Missing key column: {col}")

    return features