"""Bollinger Bands mean reversion strategy.

Buys when price touches lower band in uptrend, sells when price
touches upper band in downtrend. Works well in ranging markets.
"""

from decimal import Decimal
from typing import Optional, Dict, Any, List
import pandas as pd
from src.strategies.base import BaseStrategy, StrategySignal, SignalType
from src.data.indicators import get_indicators
from src.core.logger import get_logger

logger = get_logger(__name__)


class BollingerMeanReversionStrategy(BaseStrategy):
    """Bollinger Bands mean reversion strategy.
    
    This strategy trades mean reversion using Bollinger Bands:
    - Buy when price touches lower band in uptrend
    - Sell when price touches upper band in downtrend
    - Uses trend filter to avoid counter-trend trades
    """
    
    def __init__(self, name: str, parameters: Optional[Dict[str, Any]] = None, timeframes: Optional[list] = None):
        """Initialize Bollinger Bands mean reversion strategy.
        
        Parameters:
            period: Moving average period for Bollinger Bands (default 20)
            std_dev: Standard deviation multiplier (default 2.0)
            trend_filter: Enable trend filter (default True)
            trend_ma_period: Moving average period for trend detection (default 50)
            entry_threshold: How close price must be to band (0.0-1.0, default 0.95)
            exit_threshold: Exit when price reaches middle band (default 0.5)
        """
        super().__init__(name, parameters, timeframes)
        
        self.period = self.parameters.get('period', 20)
        self.std_dev = self.parameters.get('std_dev', 2.0)
        self.trend_filter = self.parameters.get('trend_filter', True)
        self.trend_ma_period = self.parameters.get('trend_ma_period', 50)
        self.entry_threshold = self.parameters.get('entry_threshold', 0.95)
        self.exit_threshold = self.parameters.get('exit_threshold', 0.5)
        
        self.indicators = get_indicators()
        self._price_history: List[float] = []
        self._in_position = False
        self._entry_price: Optional[Decimal] = None
    
    def _get_trend_direction(self, prices: pd.Series) -> Optional[str]:
        """Determine trend direction.
        
        Args:
            prices: Price series
            
        Returns:
            'up', 'down', or None
        """
        if len(prices) < self.trend_ma_period:
            return None
        
        sma = self.indicators.sma(prices, self.trend_ma_period)
        current_price = prices.iloc[-1]
        sma_value = sma.iloc[-1]
        
        # Use percentage difference to determine trend
        price_diff = (current_price - sma_value) / sma_value if sma_value > 0 else 0.0
        
        if price_diff > 0.01:  # 1% above SMA = uptrend
            return 'up'
        elif price_diff < -0.01:  # 1% below SMA = downtrend
            return 'down'
        
        return None
    
    def _check_band_touch(self, price: float, upper: float, middle: float, lower: float) -> Optional[str]:
        """Check if price is touching a band.
        
        Args:
            price: Current price
            upper: Upper Bollinger Band
            middle: Middle Bollinger Band
            lower: Lower Bollinger Band
            
        Returns:
            'upper' if touching upper band, 'lower' if touching lower band, None otherwise
        """
        band_width = upper - lower
        if band_width == 0:
            return None
        
        # Calculate position within bands (0 = lower, 1 = upper)
        position = (price - lower) / band_width
        
        # Check if touching lower band
        if position <= (1.0 - self.entry_threshold):
            return 'lower'
        
        # Check if touching upper band
        if position >= self.entry_threshold:
            return 'upper'
        
        return None
    
    async def on_tick(self, symbol: str, price: Decimal, timeframe: str, data: Dict[str, Any]) -> Optional[StrategySignal]:
        """Generate signal based on Bollinger Bands mean reversion."""
        # Add price to history
        self._price_history.append(float(price))
        
        # Need enough data for Bollinger Bands and trend detection
        max_period = max(self.period, self.trend_ma_period)
        
        if len(self._price_history) < max_period + 1:
            return None
        
        prices = pd.Series(self._price_history[-max_period-1:])
        current_price = float(price)
        
        # Calculate Bollinger Bands
        bb_data = self.indicators.bollinger_bands(prices, period=self.period, std_dev=self.std_dev)
        
        if len(bb_data['upper']) == 0 or pd.isna(bb_data['upper'].iloc[-1]):
            return None
        
        upper = bb_data['upper'].iloc[-1]
        middle = bb_data['middle'].iloc[-1]
        lower = bb_data['lower'].iloc[-1]
        
        # Determine trend direction if using trend filter
        trend = None
        if self.trend_filter:
            trend = self._get_trend_direction(prices)
        
        # Check for entry signals
        if not self._in_position:
            band_touch = self._check_band_touch(current_price, upper, middle, lower)
            
            if band_touch == 'lower':
                # Price touching lower band - potential buy signal
                if not self.trend_filter or trend == 'up':
                    # Only buy in uptrend (mean reversion back up)
                    self._in_position = True
                    self._entry_price = price
                    
                    # Calculate signal strength based on how far below lower band
                    distance_from_lower = (lower - current_price) / lower if lower > 0 else 0.0
                    strength = min(1.0, distance_from_lower * 10)  # Scale for strength
                    
                    return StrategySignal(
                        signal_type=SignalType.BUY,
                        symbol=symbol,
                        strength=max(0.5, strength),
                        price=price,
                        metadata={
                            'band_touch': 'lower',
                            'upper': float(upper),
                            'middle': float(middle),
                            'lower': float(lower),
                            'price_position': (current_price - lower) / (upper - lower) if (upper - lower) > 0 else 0.5,
                            'trend': trend,
                            'strategy': 'bollinger_mean_reversion',
                            'type': 'entry'
                        }
                    )
            
            elif band_touch == 'upper':
                # Price touching upper band - potential sell signal (for short)
                if not self.trend_filter or trend == 'down':
                    # Only sell in downtrend (mean reversion back down)
                    # Note: This is a SELL signal (could be used for shorting or exiting long positions)
                    # For mean reversion, we typically only go long, so this might be an exit signal
                    # For simplicity, we'll make it a SELL signal
                    pass  # Could implement short entry here if desired
        
        else:
            # In position - check for exit
            entry_price = self._entry_price or price
            entry_float = float(entry_price)
            
            # Exit when price reaches middle band (mean reversion complete)
            band_width = upper - lower
            position_in_bands = (current_price - lower) / band_width if band_width > 0 else 0.5
            
            # Exit conditions
            should_exit = False
            exit_reason = None
            
            if position_in_bands >= self.exit_threshold:
                # Price has moved back toward middle - take profit
                should_exit = True
                exit_reason = 'target_reached'
            elif band_touch := self._check_band_touch(current_price, upper, middle, lower):
                # Price touched opposite band - stop loss
                if band_touch == 'upper' and entry_float < middle:
                    should_exit = True
                    exit_reason = 'stop_loss'
            
            if should_exit:
                self._in_position = False
                profit_pct = (current_price - entry_float) / entry_float if entry_float > 0 else 0.0
                
                return StrategySignal(
                    signal_type=SignalType.SELL,
                    symbol=symbol,
                    strength=1.0,
                    price=price,
                    metadata={
                        'entry_price': float(entry_price),
                        'exit_price': current_price,
                        'profit_pct': profit_pct * 100,
                        'exit_reason': exit_reason,
                        'strategy': 'bollinger_mean_reversion',
                        'type': 'exit'
                    }
                )
        
        return None
    
    def on_signal(self, signal: StrategySignal) -> Optional[StrategySignal]:
        """Process signal."""
        if signal.signal_type == SignalType.SELL:
            self._in_position = False
            self._entry_price = None
        
        return signal if self.should_execute(signal) else None

