"""
Унифицированный пайплайн подготовки DataFrame для обучения/инференса.

Заменяет 6 локальных копий _prepare_df / _prepare_ticker_df,
разбросанных по main.py, models/moe.py, train_*.py, run_walkforward.py.

Использование:
    from features.pipeline import prepare_df
    df = prepare_df('SBER', 'H1', with_mtf=True, limit=2000)
"""

from typing import Optional
import pandas as pd
import numpy as np

from data.loader import load_dataframe
from features.technical import engineer_features
from features.directional import add_directional_signals, add_short_specific_features
from features.context import add_curated_context
from features.targets import compute_dual_outcomes
from config import get_target_config


def has_mtf_context(df: pd.DataFrame) -> bool:
    """Проверяет, есть ли в df колонки MTF контекста."""
    from features.mtf_context import MTF_CONTEXT_COLS
    return any(c in df.columns for c in MTF_CONTEXT_COLS)


def prepare_df(
    ticker: str,
    timeframe: str = 'H1',
    *,
    with_mtf: bool = True,
    with_short_specific: bool = True,
    with_targets: bool = True,
    limit: Optional[int] = None,
    min_rows: int = 100,
) -> Optional[pd.DataFrame]:
    """
    Загружает и подготавливает DataFrame для одного тикера × timeframe.

    Pipeline:
        1. load_dataframe → сырые свечи
        2. engineer_features → технические индикаторы
        3. add_directional_signals → directional сигналы
        4. add_short_specific_features → short-специфичные признаки
        5. add_curated_context → свечные паттерны, streaks
        6. (если with_mtf) add_mtf_context → мультитаймфрейм контекст
        7. (если with_targets) compute_dual_outcomes → SL/TP исходы
        8. dropna → финальная очистка

    Args:
        ticker: Тикер инструмента.
        timeframe: Таймфрейм ('H1', 'D1', 'W1').
        with_mtf: Добавлять MTF контекст (H1+H4+D1+W1).
        with_short_specific: Добавлять short-специфичные признаки.
        with_targets: Вычислять outcome_long/outcome_short.
        limit: Ограничение количества строк.
        min_rows: Минимальное число строк (иначе None).

    Returns:
        DataFrame с признаками или None при ошибке/нехватке данных.
    """
    try:
        df = load_dataframe(ticker, timeframe, limit)
    except Exception as e:
        print(f'    [WARN] Не удалось загрузить {ticker}_{timeframe}: {e}')
        return None

    if df is None or len(df) < min_rows:
        return None

    # Технические индикаторы
    df = engineer_features(df)

    # Конвертация object-колонок
    for col in df.select_dtypes(include=['object']).columns:
        if col not in ['Date', 'Time']:
            df[col] = pd.to_numeric(df[col], errors='coerce')
    for col in ['Open', 'High', 'Low', 'Close']:
        df[col] = df[col].astype(float)

    # Сигналы
    df = add_directional_signals(df)
    if with_short_specific:
        df = add_short_specific_features(df)
    df = add_curated_context(df)

    # MTF контекст
    if with_mtf:
        try:
            from features.mtf_context import add_mtf_context
            df = add_mtf_context(ticker, df)
        except Exception as e:
            print(f'    [WARN] MTF контекст недоступен: {e}')

    # Таргеты (SL/TP outcome) — per-market config
    if with_targets:
        target_cfg = get_target_config(ticker)
        df = compute_dual_outcomes(df, **target_cfg)

    df = df.dropna()
    if len(df) < min_rows:
        return None

    return df
