"""
Générateur de signaux ultra court terme pour trading degen
Plusieurs stratégies à backtester pour trouver la meilleure
"""

import pandas as pd
import numpy as np
from typing import Dict, List, Optional, Tuple
import logging
from dataclasses import dataclass

import sys
sys.path.append('..')
import config


@dataclass
class Signal:
    """Signal de trading"""
    symbol: str
    side: str  # LONG ou SHORT
    score: float  # 0-1
    strategy: str  # Nom de la stratégie
    entry_price: float
    stop_loss: float
    take_profit: float
    confluences: List[str]  # Liste des indicateurs concordants
    timeframe: str
    timestamp: pd.Timestamp
    # Strategy-provided parameters (optional for backward compatibility)
    leverage: Optional[int] = None
    risk_pct: Optional[float] = None


class TechnicalIndicators:
    """Calcul d'indicateurs techniques ultra-réactifs"""

    @staticmethod
    def rsi(series: pd.Series, period: int = 7) -> pd.Series:
        """RSI ultra-réactif (période courte)"""
        delta = series.diff()
        gain = (delta.where(delta > 0, 0)).rolling(window=period).mean()
        loss = (-delta.where(delta < 0, 0)).rolling(window=period).mean()
        rs = gain / loss
        rsi = 100 - (100 / (1 + rs))
        return rsi

    @staticmethod
    def ema(series: pd.Series, period: int) -> pd.Series:
        """EMA - Exponential Moving Average"""
        return series.ewm(span=period, adjust=False).mean()

    @staticmethod
    def macd(series: pd.Series, fast: int = 8, slow: int = 17, signal: int = 9) -> Tuple[pd.Series, pd.Series, pd.Series]:
        """MACD rapide pour court terme"""
        ema_fast = series.ewm(span=fast, adjust=False).mean()
        ema_slow = series.ewm(span=slow, adjust=False).mean()
        macd_line = ema_fast - ema_slow
        signal_line = macd_line.ewm(span=signal, adjust=False).mean()
        histogram = macd_line - signal_line
        return macd_line, signal_line, histogram

    @staticmethod
    def bollinger_bands(series: pd.Series, period: int = 14, std: float = 2.0) -> Tuple[pd.Series, pd.Series, pd.Series]:
        """Bollinger Bands"""
        sma = series.rolling(window=period).mean()
        rolling_std = series.rolling(window=period).std()
        upper_band = sma + (rolling_std * std)
        lower_band = sma - (rolling_std * std)
        return upper_band, sma, lower_band

    @staticmethod
    def atr(high: pd.Series, low: pd.Series, close: pd.Series, period: int = 14) -> pd.Series:
        """Average True Range"""
        tr1 = high - low
        tr2 = abs(high - close.shift())
        tr3 = abs(low - close.shift())
        tr = pd.concat([tr1, tr2, tr3], axis=1).max(axis=1)
        atr = tr.rolling(window=period).mean()
        return atr

    @staticmethod
    def volume_profile(volume: pd.Series, period: int = 20) -> pd.Series:
        """Volume moving average"""
        return volume.rolling(window=period).mean()


class ScalpingStrategy:
    """Stratégie 1: Scalping RSI + Volume ultra court terme"""

    def __init__(self):
        self.name = "SCALPING_RSI_VOLUME"
        self.logger = logging.getLogger(self.name)

    def analyze(self, df: pd.DataFrame, symbol: str) -> Optional[Signal]:
        """
        Analyse ultra court terme basée sur RSI et volume spikes

        Logique:
        - LONG: RSI < 30 + volume spike + price bounce
        - SHORT: RSI > 70 + volume spike + price rejection
        """
        if len(df) < 30:
            return None

        # Calculer indicateurs
        df = df.copy()
        df['rsi'] = TechnicalIndicators.rsi(df['close'], period=config.RSI_PERIOD)
        df['volume_ma'] = TechnicalIndicators.volume_profile(df['volume'], period=config.VOLUME_MA_PERIOD)
        df['volume_ratio'] = df['volume'] / df['volume_ma']

        # Dernières valeurs
        current = df.iloc[-1]
        prev = df.iloc[-2]

        rsi = current['rsi']
        volume_ratio = current['volume_ratio']
        close = current['close']
        prev_close = prev['close']

        confluences = []
        score = 0.0
        side = None

        # ========== LONG SETUP ==========
        if rsi < config.RSI_OVERSOLD:
            confluences.append('RSI_OVERSOLD')
            score += 0.35

            # Volume spike
            if volume_ratio > config.VOLUME_SPIKE_THRESHOLD:
                confluences.append('VOLUME_SPIKE')
                score += 0.25

            # Price bounce (close > open sur dernière bougie)
            if close > current['open']:
                confluences.append('BULLISH_CANDLE')
                score += 0.20

            # Momentum shifting (RSI croissant)
            if rsi > prev['rsi']:
                confluences.append('RSI_RISING')
                score += 0.20

            if len(confluences) >= config.MIN_CONFLUENCES_AGGRESSIVE:
                side = 'LONG'

        # ========== SHORT SETUP ==========
        elif rsi > config.RSI_OVERBOUGHT:
            confluences.append('RSI_OVERBOUGHT')
            score += 0.35

            # Volume spike
            if volume_ratio > config.VOLUME_SPIKE_THRESHOLD:
                confluences.append('VOLUME_SPIKE')
                score += 0.25

            # Price rejection (close < open)
            if close < current['open']:
                confluences.append('BEARISH_CANDLE')
                score += 0.20

            # Momentum shifting (RSI descendant)
            if rsi < prev['rsi']:
                confluences.append('RSI_FALLING')
                score += 0.20

            if len(confluences) >= config.MIN_CONFLUENCES_AGGRESSIVE:
                side = 'SHORT'

        # Pas de signal valide
        if not side or score < config.MIN_SIGNAL_SCORE_AGGRESSIVE:
            return None

        # Calculer stop-loss et take-profit
        leverage = config.LEVERAGE_AGGRESSIVE
        sl_pct = config.STOP_LOSS_PCT[250]  # 0.3%
        tp_pct = sl_pct * config.TAKE_PROFIT_RATIO

        if side == 'LONG':
            stop_loss = close * (1 - sl_pct)
            take_profit = close * (1 + tp_pct)
        else:
            stop_loss = close * (1 + sl_pct)
            take_profit = close * (1 - tp_pct)

        return Signal(
            symbol=symbol,
            side=side,
            score=score,
            strategy=self.name,
            entry_price=close,
            stop_loss=stop_loss,
            take_profit=take_profit,
            confluences=confluences,
            timeframe=config.TIMEFRAME_PRIMARY,
            timestamp=current['timestamp']
        )


class MACDMomentumStrategy:
    """Stratégie 2: MACD ultra-rapide + EMA crossover"""

    def __init__(self):
        self.name = "MACD_MOMENTUM"
        self.logger = logging.getLogger(self.name)

    def analyze(self, df: pd.DataFrame, symbol: str) -> Optional[Signal]:
        """
        Détection de momentum via MACD + EMA crossover

        Logique:
        - LONG: MACD cross bullish + price > EMA fast + histogram croissant
        - SHORT: MACD cross bearish + price < EMA fast + histogram décroissant
        """
        if len(df) < 30:
            return None

        df = df.copy()

        # Indicateurs
        macd, signal, histogram = TechnicalIndicators.macd(
            df['close'],
            fast=config.MACD_FAST,
            slow=config.MACD_SLOW,
            signal=config.MACD_SIGNAL
        )
        df['macd'] = macd
        df['macd_signal'] = signal
        df['macd_hist'] = histogram

        df['ema_fast'] = TechnicalIndicators.ema(df['close'], period=config.EMA_FAST)
        df['ema_slow'] = TechnicalIndicators.ema(df['close'], period=config.EMA_SLOW)

        current = df.iloc[-1]
        prev = df.iloc[-2]
        prev2 = df.iloc[-3]

        close = current['close']
        macd_val = current['macd']
        signal_val = current['macd_signal']
        hist = current['macd_hist']
        prev_hist = prev['macd_hist']

        confluences = []
        score = 0.0
        side = None

        # ========== LONG SETUP ==========
        # MACD bullish cross
        if macd_val > signal_val and prev['macd'] <= prev['macd_signal']:
            confluences.append('MACD_BULLISH_CROSS')
            score += 0.40

            # Price au-dessus EMA rapide
            if close > current['ema_fast']:
                confluences.append('PRICE_ABOVE_EMA')
                score += 0.20

            # Histogram croissant
            if hist > prev_hist > prev2['macd_hist']:
                confluences.append('HISTOGRAM_RISING')
                score += 0.25

            # EMA crossover
            if current['ema_fast'] > current['ema_slow']:
                confluences.append('EMA_BULLISH')
                score += 0.15

            if len(confluences) >= config.MIN_CONFLUENCES_AGGRESSIVE:
                side = 'LONG'

        # ========== SHORT SETUP ==========
        # MACD bearish cross
        elif macd_val < signal_val and prev['macd'] >= prev['macd_signal']:
            confluences.append('MACD_BEARISH_CROSS')
            score += 0.40

            # Price en-dessous EMA rapide
            if close < current['ema_fast']:
                confluences.append('PRICE_BELOW_EMA')
                score += 0.20

            # Histogram décroissant
            if hist < prev_hist < prev2['macd_hist']:
                confluences.append('HISTOGRAM_FALLING')
                score += 0.25

            # EMA crossover bearish
            if current['ema_fast'] < current['ema_slow']:
                confluences.append('EMA_BEARISH')
                score += 0.15

            if len(confluences) >= config.MIN_CONFLUENCES_AGGRESSIVE:
                side = 'SHORT'

        if not side or score < config.MIN_SIGNAL_SCORE_AGGRESSIVE:
            return None

        # Stop-loss et take-profit
        leverage = config.LEVERAGE_AGGRESSIVE
        sl_pct = config.STOP_LOSS_PCT[250]
        tp_pct = sl_pct * config.TAKE_PROFIT_RATIO

        if side == 'LONG':
            stop_loss = close * (1 - sl_pct)
            take_profit = close * (1 + tp_pct)
        else:
            stop_loss = close * (1 + sl_pct)
            take_profit = close * (1 - tp_pct)

        return Signal(
            symbol=symbol,
            side=side,
            score=score,
            strategy=self.name,
            entry_price=close,
            stop_loss=stop_loss,
            take_profit=take_profit,
            confluences=confluences,
            timeframe=config.TIMEFRAME_PRIMARY,
            timestamp=current['timestamp']
        )


class BollingerBounceStrategy:
    """Stratégie 3: Bollinger Bands mean reversion"""

    def __init__(self):
        self.name = "BOLLINGER_BOUNCE"
        self.logger = logging.getLogger(self.name)

    def analyze(self, df: pd.DataFrame, symbol: str) -> Optional[Signal]:
        """
        Mean reversion via Bollinger Bands

        Logique:
        - LONG: Touch lower band + RSI oversold + bullish candle
        - SHORT: Touch upper band + RSI overbought + bearish candle
        """
        if len(df) < 30:
            return None

        df = df.copy()

        # Indicateurs
        upper, middle, lower = TechnicalIndicators.bollinger_bands(
            df['close'],
            period=config.BB_PERIOD,
            std=config.BB_STD
        )
        df['bb_upper'] = upper
        df['bb_middle'] = middle
        df['bb_lower'] = lower
        df['rsi'] = TechnicalIndicators.rsi(df['close'], period=config.RSI_PERIOD)

        current = df.iloc[-1]
        close = current['close']
        low = current['low']
        high = current['high']

        # Distance aux bandes (en %)
        distance_to_lower = (close - current['bb_lower']) / close
        distance_to_upper = (current['bb_upper'] - close) / close

        confluences = []
        score = 0.0
        side = None

        # ========== LONG SETUP ==========
        # Touch ou pénétration de la bande basse
        if distance_to_lower < 0.003:  # < 0.3%
            confluences.append('BB_LOWER_TOUCH')
            score += 0.40

            # RSI oversold
            if current['rsi'] < config.RSI_OVERSOLD:
                confluences.append('RSI_OVERSOLD')
                score += 0.30

            # Bullish candle (close > open)
            if close > current['open']:
                confluences.append('BULLISH_CANDLE')
                score += 0.20

            # Wick en bas (rejection)
            body = abs(close - current['open'])
            lower_wick = current['open'] - low if close > current['open'] else close - low
            if lower_wick > body * 1.5:
                confluences.append('LOWER_WICK_REJECTION')
                score += 0.10

            if len(confluences) >= 2:
                side = 'LONG'

        # ========== SHORT SETUP ==========
        # Touch ou pénétration de la bande haute
        elif distance_to_upper < 0.003:  # < 0.3%
            confluences.append('BB_UPPER_TOUCH')
            score += 0.40

            # RSI overbought
            if current['rsi'] > config.RSI_OVERBOUGHT:
                confluences.append('RSI_OVERBOUGHT')
                score += 0.30

            # Bearish candle
            if close < current['open']:
                confluences.append('BEARISH_CANDLE')
                score += 0.20

            # Wick en haut (rejection)
            body = abs(close - current['open'])
            upper_wick = high - current['open'] if close < current['open'] else high - close
            if upper_wick > body * 1.5:
                confluences.append('UPPER_WICK_REJECTION')
                score += 0.10

            if len(confluences) >= 2:
                side = 'SHORT'

        if not side or score < config.MIN_SIGNAL_SCORE_AGGRESSIVE:
            return None

        # Stop-loss et take-profit
        leverage = config.LEVERAGE_AGGRESSIVE
        sl_pct = config.STOP_LOSS_PCT[250]
        tp_pct = sl_pct * config.TAKE_PROFIT_RATIO

        if side == 'LONG':
            stop_loss = close * (1 - sl_pct)
            take_profit = close * (1 + tp_pct)
        else:
            stop_loss = close * (1 + sl_pct)
            take_profit = close * (1 - tp_pct)

        return Signal(
            symbol=symbol,
            side=side,
            score=score,
            strategy=self.name,
            entry_price=close,
            stop_loss=stop_loss,
            take_profit=take_profit,
            confluences=confluences,
            timeframe=config.TIMEFRAME_PRIMARY,
            timestamp=current['timestamp']
        )


class ShortTermSignalGenerator:
    """Générateur principal - teste toutes les stratégies"""

    def __init__(self):
        self.logger = logging.getLogger(self.__class__.__name__)

        # Instancier toutes les stratégies
        self.strategies = [
            ScalpingStrategy(),
            MACDMomentumStrategy(),
            BollingerBounceStrategy()
        ]

        self.logger.info(f"✅ {len(self.strategies)} stratégies chargées")

    def generate_signals(self, df: pd.DataFrame, symbol: str) -> List[Signal]:
        """
        Génère des signaux avec toutes les stratégies

        Returns:
            Liste de signaux (un par stratégie qui trouve un setup)
        """
        signals = []

        for strategy in self.strategies:
            try:
                signal = strategy.analyze(df, symbol)
                if signal:
                    signals.append(signal)
                    self.logger.debug(
                        f"✅ {strategy.name}: {signal.side} {symbol} "
                        f"score={signal.score:.2f} confluences={len(signal.confluences)}"
                    )
            except Exception as e:
                self.logger.error(f"❌ Erreur {strategy.name}: {e}")

        return signals

    def get_best_signal(self, df: pd.DataFrame, symbol: str) -> Optional[Signal]:
        """Retourne le meilleur signal (score le plus élevé)"""
        signals = self.generate_signals(df, symbol)

        if not signals:
            return None

        # Trier par score décroissant
        signals.sort(key=lambda x: x.score, reverse=True)

        return signals[0]


if __name__ == '__main__':
    # Test basique
    logging.basicConfig(level=logging.DEBUG)

    # Créer données de test
    dates = pd.date_range('2025-01-01', periods=100, freq='1min')
    df = pd.DataFrame({
        'timestamp': dates,
        'open': np.random.randn(100).cumsum() + 100,
        'high': np.random.randn(100).cumsum() + 101,
        'low': np.random.randn(100).cumsum() + 99,
        'close': np.random.randn(100).cumsum() + 100,
        'volume': np.random.randint(1000, 10000, 100)
    })

    generator = ShortTermSignalGenerator()
    signals = generator.generate_signals(df, 'BTCUSDT')

    print(f"\n📊 Signaux générés: {len(signals)}")
    for sig in signals:
        print(f"\n{sig.strategy}:")
        print(f"  Side: {sig.side}")
        print(f"  Score: {sig.score:.2%}")
        print(f"  Entry: ${sig.entry_price:.2f}")
        print(f"  SL: ${sig.stop_loss:.2f}")
        print(f"  TP: ${sig.take_profit:.2f}")
        print(f"  Confluences: {', '.join(sig.confluences)}")
