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

class DivergenceDetector:
    @staticmethod
    def find_peaks(data: List[float], lookback: int = 3) -> List[int]:
        """Find peak indices in a data series"""
        peaks = []
        for i in range(lookback, len(data)-lookback):
            window = data[i-lookback:i+lookback+1]
            if data[i] == max(window):
                peaks.append(i)
        return peaks

    @staticmethod
    def find_troughs(data: List[float], lookback: int = 3) -> List[int]:
        """Find trough indices in a data series"""
        troughs = []
        for i in range(lookback, len(data)-lookback):
            window = data[i-lookback:i+lookback+1]
            if data[i] == min(window):
                troughs.append(i)
        return troughs

    @staticmethod
    def detect_divergence(
        price: List[float],
        indicator: List[float],
        lookback: int = 5,
        min_length: int = 2
    ) -> Optional[str]:
        """
        Detect regular and hidden divergences
        Returns: 'bullish', 'bearish', or None
        """
        if len(price) < lookback * 2 or len(indicator) < lookback * 2:
            return None
        
        # Find peaks and troughs
        price_peaks = DivergenceDetector.find_peaks(price, lookback)
        price_troughs = DivergenceDetector.find_troughs(price, lookback)
        ind_peaks = DivergenceDetector.find_peaks(indicator, lookback)
        ind_troughs = DivergenceDetector.find_troughs(indicator, lookback)
        
        # Check for bearish divergence (price highs vs indicator highs)
        if len(price_peaks) >= min_length and len(ind_peaks) >= min_length:
            # Get last two peaks
            p_peak1, p_peak2 = price_peaks[-2], price_peaks[-1]
            i_peak1, i_peak2 = ind_peaks[-2], ind_peaks[-1]
            
            # Regular bearish: Higher highs in price, lower highs in indicator
            if (price[p_peak2] > price[p_peak1] and 
                indicator[i_peak2] < indicator[i_peak1]):
                return 'bearish'
            
            # Hidden bearish: Lower highs in price, lower highs in indicator
            elif (price[p_peak2] < price[p_peak1] and 
                  indicator[i_peak2] < indicator[i_peak1]):
                return 'bearish'
        
        # Check for bullish divergence (price lows vs indicator lows)
        if len(price_troughs) >= min_length and len(ind_troughs) >= min_length:
            # Get last two troughs
            p_trough1, p_trough2 = price_troughs[-2], price_troughs[-1]
            i_trough1, i_trough2 = ind_troughs[-2], ind_troughs[-1]
            
            # Regular bullish: Lower lows in price, higher lows in indicator
            if (price[p_trough2] < price[p_trough1] and 
                indicator[i_trough2] > indicator[i_trough1]):
                return 'bullish'
            
            # Hidden bullish: Higher lows in price, higher lows in indicator
            elif (price[p_trough2] > price[p_trough1] and 
                  indicator[i_trough2] > indicator[i_trough1]):
                return 'bullish'
        
        return None

    @staticmethod
    def detect_rsi_divergence(df: pd.DataFrame, lookback: int = 5) -> Optional[str]:
        if 'rsi' not in df.columns or 'close' not in df.columns:
            return None
        return DivergenceDetector.detect_divergence(
            price=df['close'].tolist(),
            indicator=df['rsi'].tolist(),
            lookback=lookback
        )

    @staticmethod
    def detect_macd_divergence(df: pd.DataFrame, lookback: int = 5) -> Optional[str]:
        if 'macd' not in df.columns or 'close' not in df.columns:
            return None
        return DivergenceDetector.detect_divergence(
            price=df['close'].tolist(),
            indicator=df['macd'].tolist(),
            lookback=lookback
        )