"""
Модуль признаков для определения фаз Вайкоффа (VSA + объёмный анализ).

Содержит функции для расчёта VSA-метрик, необходимых для идентификации
фаз рынка по методу Вайкоффа:

    - Spread (High - Low) и его аномалии
    - Volume Ratio и аномалии объёма
    - Close Position (позиция закрытия в свече)
    - VSA-сигналы: Stopping Volume, Selling/Buying Climax, No Demand, Upthrust
    - Wyckoff-специфичные индикаторы: Springs, Shakeouts, Tests, SOS, LPS

Использует pandas_ta для базовых индикаторов и собственные расчёты для VSA.

Usage:
    from src.ml.features.wyckoff_features import add_wyckoff_features

    df = add_wyckoff_features(df)
"""

from typing import Optional

import numpy as np
import pandas as pd


def add_vsa_metrics(df: pd.DataFrame) -> pd.DataFrame:
    """
    Добавить VSA (Volume Spread Analysis) метрики.

    Рассчитываются:
        - spread: High - Low (абсолютный)
        - spread_pct: (High - Low) / Open * 100
        - volume_sma_5, volume_sma_20: скользящие средние объёма
        - volume_ratio_5, volume_ratio_20: отношение объёма к среднему
        - close_position: (Close - Low) / (High - Low), позиция закрытия (0..1)
        - body_size: |Close - Open| — размер тела свечи
        - upper_wick: High - max(Open, Close) — верхняя тень
        - lower_wick: min(Open, Close) - Low — нижняя тень
        - wick_to_body_ratio: отношение тени к телу

    Args:
        df: DataFrame с колонками Open, High, Low, Close, Volume.

    Returns:
        DataFrame с добавленными VSA-метриками.
    """
    df = df.copy()
    eps = 1e-8

    # Spread
    df['spread'] = df['High'] - df['Low']
    df['spread_pct'] = df['spread'] / (df['Open'] + eps) * 100
    df['spread_sma_10'] = df['spread'].rolling(10).mean()
    df['spread_ratio'] = df['spread'] / (df['spread_sma_10'] + eps)

    # Volume
    df['volume_sma_5'] = df['Volume'].rolling(5).mean()
    df['volume_sma_20'] = df['Volume'].rolling(20).mean()
    df['volume_ratio_5'] = df['Volume'] / (df['volume_sma_5'] + eps)
    df['volume_ratio_20'] = df['Volume'] / (df['volume_sma_20'] + eps)

    # Close position
    df['close_position'] = (df['Close'] - df['Low']) / (df['spread'] + eps)

    # Candle parts
    df['body_size'] = abs(df['Close'] - df['Open'])
    df['upper_wick'] = df['High'] - np.maximum(df['Open'], df['Close'])
    df['lower_wick'] = np.minimum(df['Open'], df['Close']) - df['Low']
    df['wick_to_body'] = (df['upper_wick'] + df['lower_wick']) / (df['body_size'] + eps)
    df['upper_wick_pct'] = df['upper_wick'] / (df['spread'] + eps)
    df['lower_wick_pct'] = df['lower_wick'] / (df['spread'] + eps)

    # Is bullish / bearish candle
    df['is_bullish'] = (df['Close'] > df['Open']).astype(int)
    df['is_bearish'] = (df['Close'] < df['Open']).astype(int)
    df['body_pct'] = df['body_size'] / (df['spread'] + eps)

    return df


def add_vsa_signals(df: pd.DataFrame) -> pd.DataFrame:
    """
    Добавить VSA-сигналы (событийный анализ).

    Сигналы:
        - stopping_volume: широкий спред + высокий объём + close в середине
          (признак остановки тренда)
        - selling_climax: очень широкий спред + экстремальный объём + close
          near low (кульминация продаж)
        - buying_climax: очень широкий спред + экстремальный объём + close
          near high (кульминация покупок)
        - no_demand: узкий спред + низкий объём + close near low
          (отсутствие спроса после подъёма)
        - no_supply: узкий спред + низкий объём + close near high
          (отсутствие предложения после падения)
        - upthrust: широкий спред + High выше предыдущих + close near low
          (ловушка для быков)
        - test_volume: узкий спред + объём ниже среднего + close в ключевой
          зоне (тест поддержки/сопротивления)

    Args:
        df: DataFrame с колонками Open, High, Low, Close, Volume
            и VSA-метриками (spread_ratio, volume_ratio_20, close_position).

    Returns:
        DataFrame с добавленными колонками VSA-сигналов (0/1).
    """
    df = df.copy()
    eps = 1e-8

    # Stopping Volume
    # Широкий спред (spread_ratio > 1.5) + высокий объём (vol_ratio > 1.5)
    # + close в средней трети (0.3 < close_position < 0.7)
    df['stopping_volume'] = (
        (df['spread_ratio'] > 1.5) &
        (df['volume_ratio_20'] > 1.5) &
        (df['close_position'] > 0.3) &
        (df['close_position'] < 0.7)
    ).astype(float)

    # Selling Climax (SC)
    # Экстремальный спред + экстремальный объём + close near low
    df['selling_climax'] = (
        (df['spread_ratio'] > 2.0) &
        (df['volume_ratio_20'] > 2.0) &
        (df['close_position'] < 0.3)
    ).astype(float)

    # Buying Climax (BC)
    # Экстремальный спред + экстремальный объём + close near high
    df['buying_climax'] = (
        (df['spread_ratio'] > 2.0) &
        (df['volume_ratio_20'] > 2.0) &
        (df['close_position'] > 0.7)
    ).astype(float)

    # No Demand
    # Узкий спред + низкий объём + close near low
    df['no_demand'] = (
        (df['spread_ratio'] < 0.7) &
        (df['volume_ratio_20'] < 0.7) &
        (df['close_position'] < 0.3)
    ).astype(float)

    # No Supply
    # Узкий спред + низкий объём + close near high
    df['no_supply'] = (
        (df['spread_ratio'] < 0.7) &
        (df['volume_ratio_20'] < 0.7) &
        (df['close_position'] > 0.7)
    ).astype(float)

    # Upthrust (UT)
    # Широкий спред + close near low + upper wick большой
    # + цена тестировала новые максимумы
    df['upthrust'] = (
        (df['spread_ratio'] > 1.5) &
        (df['close_position'] < 0.3) &
        (df['upper_wick_pct'] > 0.5)
    ).astype(float)

    # Test (низкий объём в ключевой зоне — упрощённо)
    # Узкий спред + объём значительно ниже среднего + close в центре
    df['test_volume'] = (
        (df['spread_ratio'] < 0.8) &
        (df['volume_ratio_20'] < 0.6) &
        (df['close_position'] > 0.3) &
        (df['close_position'] < 0.7)
    ).astype(float)

    # VSA score: сумма всех сигналов (для взвешивания)
    vsa_cols = ['stopping_volume', 'selling_climax', 'buying_climax',
                'no_demand', 'no_supply', 'upthrust', 'test_volume']
    df['vsa_score'] = df[vsa_cols].sum(axis=1)

    return df


def add_wyckoff_trend_metrics(df: pd.DataFrame) -> pd.DataFrame:
    """
    Добавить трендовые метрики для Вайкофф-анализа.

    Рассчитываются:
        - higher_high / lower_low: экстремумы за N свечей
        - trend_strength: сила тренда (ADX-подобная)
        - trend_direction: направление тренда (+1 / -1 / 0)
        - price_vs_ma: положение цены относительно скользящих средних
        - pivot_high / pivot_low: локальные экстремумы

    Args:
        df: DataFrame с колонками High, Low, Close.

    Returns:
        DataFrame с добавленными трендовыми метриками.
    """
    df = df.copy()
    window = 10

    # Экстремумы
    df['hh_window'] = df['High'].rolling(window, center=False).max()
    df['ll_window'] = df['Low'].rolling(window, center=False).min()

    # По сравнению с предыдущим окном
    df['hh_higher'] = (df['High'] > df['High'].shift(1)).rolling(window).sum() > window // 2
    df['ll_lower'] = (df['Low'] < df['Low'].shift(1)).rolling(window).sum() > window // 2

    # Pivot highs / lows (простая версия)
    df['pivot_high'] = (
        (df['High'] > df['High'].shift(1)) &
        (df['High'] > df['High'].shift(-1)) &
        (df['High'] > df['High'].shift(2)) &
        (df['High'] > df['High'].shift(-2))
    ).astype(float)

    df['pivot_low'] = (
        (df['Low'] < df['Low'].shift(1)) &
        (df['Low'] < df['Low'].shift(-1)) &
        (df['Low'] < df['Low'].shift(2)) &
        (df['Low'] < df['Low'].shift(-2))
    ).astype(float)

    # Тренд: сравнение EMA
    df['ema_20'] = df['Close'].ewm(span=20, adjust=False).mean()
    df['ema_50'] = df['Close'].ewm(span=50, adjust=False).mean()

    df['price_above_ema20'] = (df['Close'] > df['ema_20']).astype(int)
    df['price_above_ema50'] = (df['Close'] > df['ema_50']).astype(int)
    df['ema20_above_ema50'] = (df['ema_20'] > df['ema_50']).astype(int)

    # Скользящий тренд (+1 uptrend, -1 downtrend, 0 neutral)
    df['ma_trend'] = np.where(
        (df['price_above_ema20'] == 1) & (df['ema20_above_ema50'] == 1), 1,
        np.where(
            (df['price_above_ema20'] == 0) & (df['ema20_above_ema50'] == 0), -1, 0
        )
    )

    return df


def add_wyckoff_oscillator_signals(df: pd.DataFrame) -> pd.DataFrame:
    """
    Добавить сигналы осцилляторов в контексте Вайкоффа.

    Рассчитываются:
        - rsi_divergence: бычья/медвежья дивергенция RSI
        - rsi_zone: зона RSI (oversold/overbought/neutral)
        - obv_trend: тренд OBV (подтверждение объёма)

    Args:
        df: DataFrame с колонками Close и уже рассчитанными
            индикаторами (rsi_14, obv).

    Returns:
        DataFrame с добавленными сигналами.
    """
    df = df.copy()

    # RSI зоны
    if 'rsi_14' in df.columns:
        df['rsi_oversold'] = (df['rsi_14'] < 30).astype(float)
        df['rsi_overbought'] = (df['rsi_14'] > 70).astype(float)
        df['rsi_extreme_oversold'] = (df['rsi_14'] < 20).astype(float)
        df['rsi_extreme_overbought'] = (df['rsi_14'] > 80).astype(float)

        # Простая дивергенция RSI (за 5 свечей)
        df['rsi_higher_low'] = (
            (df['Low'] < df['Low'].shift(1)) &
            (df['Low'] < df['Low'].shift(2)) &
            (df['rsi_14'] > df['rsi_14'].shift(1)) &
            (df['rsi_14'] > df['rsi_14'].shift(2))
        ).astype(float)

        df['rsi_lower_high'] = (
            (df['High'] > df['High'].shift(1)) &
            (df['High'] > df['High'].shift(2)) &
            (df['rsi_14'] < df['rsi_14'].shift(1)) &
            (df['rsi_14'] < df['rsi_14'].shift(2))
        ).astype(float)

    # OBV тренд (подтверждение)
    if 'obv' in df.columns:
        df['obv_ema_20'] = df['obv'].ewm(span=20, adjust=False).mean()
        df['obv_above_ema'] = (df['obv'] > df['obv_ema_20']).astype(float)

        # OBV divergence: цена растёт, OBV падает (медвежий)
        # и наоборот
        df['obv_bullish_div'] = (
            (df['Low'] < df['Low'].shift(5)) &
            (df['obv'] > df['obv'].shift(5))
        ).astype(float)

        df['obv_bearish_div'] = (
            (df['High'] > df['High'].shift(5)) &
            (df['obv'] < df['obv'].shift(5))
        ).astype(float)

    return df


def add_volume_profile_features(df: pd.DataFrame) -> pd.DataFrame:
    """
    Добавить признаки Volume Profile (VPVR).

    Рассчитываются:
        - volume_by_price: распределение объёма по ценовым уровням
        - poc (Point of Control): уровень с максимальным объёмом
        - vah / val: верхняя/нижняя граница зоны стоимости (70%)
        - volume_clusters: кластеры высокого объёма

    Args:
        df: DataFrame с колонками High, Low, Volume.

    Returns:
        DataFrame с добавленными признаками Volume Profile.
    """
    df = df.copy()
    lookback = 20  # период для расчёта Volume Profile

    for i in range(len(df)):
        if i < lookback:
            continue

        window = df.iloc[i - lookback:i]
        high_max = window['High'].max()
        low_min = window['Low'].min()
        price_range = max(high_max - low_min, 1e-8)

        # Дискретизация цены в 10 уровней
        n_bins = 10
        bin_size = price_range / n_bins

        volume_by_bin = np.zeros(n_bins)
        for _, row in window.iterrows():
            # Распределяем объём свечи по ценовым уровням
            candle_range = max(row['High'] - row['Low'], 1e-8)
            vol_per_unit = row['Volume'] / candle_range
            low_bin = max(0, int((row['Low'] - low_min) / bin_size))
            high_bin = min(n_bins - 1, int((row['High'] - low_min) / bin_size))
            for b in range(low_bin, high_bin + 1):
                bin_low = low_min + b * bin_size
                bin_high = bin_low + bin_size
                overlap = max(0, min(row['High'], bin_high) - max(row['Low'], bin_low))
                volume_by_bin[b] += vol_per_unit * overlap

        if volume_by_bin.sum() > 0:
            # POC (Point of Control)
            poc_bin = np.argmax(volume_by_bin)
            df.loc[df.index[i], 'poc_level'] = low_min + (poc_bin + 0.5) * bin_size

            # Value Area (70% of volume)
            sorted_bins = np.argsort(volume_by_bin)[::-1]
            cum_vol = 0
            total_vol = volume_by_bin.sum()
            va_bins = set()
            for b in sorted_bins:
                va_bins.add(b)
                cum_vol += volume_by_bin[b]
                if cum_vol / total_vol >= 0.70:
                    break

            if va_bins:
                val_bin = min(va_bins)
                vah_bin = max(va_bins)
                df.loc[df.index[i], 'val_level'] = low_min + (val_bin + 0.5) * bin_size
                df.loc[df.index[i], 'vah_level'] = low_min + (vah_bin + 0.5) * bin_size
                df.loc[df.index[i], 'va_width'] = (vah_bin - val_bin) / n_bins

    # Forward fill
    df['poc_level'] = df['poc_level'].ffill()
    df['val_level'] = df['val_level'].ffill()
    df['vah_level'] = df['vah_level'].ffill()
    df['va_width'] = df['va_width'].ffill()

    # Позиция цены относительно Volume Profile
    eps = 1e-8
    df['price_vs_vah'] = (df['Close'] > df['vah_level']).astype(float)
    df['price_vs_val'] = (df['Close'] < df['val_level']).astype(float)
    df['price_vs_poc'] = (df['Close'] - df['poc_level']) / (df['spread'] + df['spread'].rolling(20).mean() + eps)
    df['price_in_va'] = (
        (df['Close'] >= df['val_level']) & (df['Close'] <= df['vah_level'])
    ).astype(float)

    return df


def add_wyckoff_features(df: pd.DataFrame) -> pd.DataFrame:
    """
    Главная функция: добавить все Wyckoff/VSA признаки в DataFrame.

    Включает:
        1. VSA метрики (спред, объём, close position, тени)
        2. VSA сигналы (Stopping Volume, SC, BC, UT, Test)
        3. Трендовые метрики (экстремумы, пивоты, EMA)
        4. Осцилляторные сигналы (дивергенции RSI/OBV)
        5. Volume Profile (POC, VAH, VAL)

    Args:
        df: DataFrame с колонками Open, High, Low, Close, Volume
            и стандартными индикаторами (rsi_14, obv, ema_* — опционально).

    Returns:
        DataFrame со всеми Wyckoff-признаками.
    """
    df = add_vsa_metrics(df)
    df = add_vsa_signals(df)
    df = add_wyckoff_trend_metrics(df)
    df = add_wyckoff_oscillator_signals(df)
    df = add_volume_profile_features(df)

    return df


if __name__ == '__main__':
    # Тестовый запуск
    import sys
    from pathlib import Path
    PROJECT_ROOT = Path(__file__).resolve().parent.parent.parent.parent
    sys.path.insert(0, str(PROJECT_ROOT))

    from src.db.connection import fetch_ohlcv_combined
    from src.ml.features.indicator_features import add_indicator_features
    from src.ml.features.price_features import add_price_features

    print("Загрузка данных X5 D1...")
    df = fetch_ohlcv_combined('X5', 'D1', limit=500)

    print(f"Загружено {len(df)} свечей")
    df = add_price_features(df)
    df = add_indicator_features(df)
    df = add_wyckoff_features(df)

    print(f"Итоговый размер: {df.shape}")
    print(f"Колонки: {list(df.columns)}")

    # Вывод VSA сигналов
    vsa_cols = ['stopping_volume', 'selling_climax', 'buying_climax',
                'no_demand', 'no_supply', 'upthrust', 'test_volume']
    print(f"\nVSA сигналы (последние 10 свечей):")
    print(df[vsa_cols].tail(10).to_string())

    print("\n✅ Wyckoff features успешно добавлены!")
