"""Entry point detection algorithms for trading strategy."""

from __future__ import annotations

from dataclasses import dataclass
from enum import Enum
from typing import List, Optional, Tuple

from .candle_patterns import PatternSignal, PatternType, detect_all_patterns
from .trend_analysis import Candle, TrendDirection, detect_trend


class SignalType(Enum):
    """Types of entry signals."""
    BUY = "buy"
    SELL = "sell"


@dataclass
class EntrySignal:
    """A detected entry point signal."""
    signal_type: SignalType
    price: float  # Suggested entry price
    confidence: float  # 0.0 to 1.0
    reason: str  # Description of why this signal was generated
    stop_loss: Optional[float] = None
    take_profit: Optional[float] = None
    pattern: Optional[str] = None  # Pattern name if pattern-based


@dataclass
class FibonacciLevels:
    """Fibonacci retracement levels."""
    level_236: float  # 23.6% retracement
    level_382: float  # 38.2% retracement
    level_500: float  # 50.0% retracement
    level_618: float  # 61.8% retracement
    level_786: float  # 78.6% retracement


def compute_fibonacci_levels(high: float, low: float) -> FibonacciLevels:
    """
    Compute Fibonacci retracement levels between high and low.

    Args:
        high: Swing high price
        low: Swing low price

    Returns:
        FibonacciLevels with standard retracement levels
    """
    diff = high - low
    return FibonacciLevels(
        level_236=low + diff * 0.236,
        level_382=low + diff * 0.382,
        level_500=low + diff * 0.500,
        level_618=low + diff * 0.618,
        level_786=low + diff * 0.786,
    )


def _find_swing_points(candles: List[Candle],
                        window: int = 5) -> Tuple[List[float], List[float]]:
    """
    Find local swing highs and swing lows in candle data.

    Args:
        candles: List of candles (oldest to newest)
        window: Number of candles on each side to consider

    Returns:
        Tuple of (swing_highs, swing_lows) as price lists
    """
    swing_highs = []
    swing_lows = []

    for i in range(window, len(candles) - window):
        # Check if local maximum
        is_high = all(candles[i].high >= candles[j].high
                      for j in range(i - window, i + window + 1) if j != i)
        if is_high:
            swing_highs.append(candles[i].high)

        # Check if local minimum
        is_low = all(candles[i].low <= candles[j].low
                     for j in range(i - window, i + window + 1) if j != i)
        if is_low:
            swing_lows.append(candles[i].low)

    return swing_highs, swing_lows


def _is_price_near_level(price: float, level: float,
                         tolerance_pct: float = 0.01) -> bool:
    """Check if price is within tolerance percentage of a level."""
    if level == 0:
        return False
    return abs(price - level) / level <= tolerance_pct


def detect_entry_buy(candles: List[Candle],
                      min_candles: int = 10) -> Optional[EntrySignal]:
    """
    Detect a buy entry point based on trend, patterns, and price action.

    Strategy:
    1. Confirm uptrend or bullish reversal
    2. Look for bullish candle patterns (engulfing, morning star, hammer)
    3. Check for pullback to support (Fibonacci, moving averages)

    Args:
        candles: List of candles (oldest to newest)
        min_candles: Minimum candles required for analysis

    Returns:
        EntrySignal if buy conditions met, None otherwise
    """
    if len(candles) < min_candles:
        return None

    last_candle = candles[-1]
    current_price = last_candle.close

    # 1. Trend analysis
    trend = detect_trend(candles)

    # Bullish signal: uptrend or bullish reversal in neutral trend
    if trend.direction == TrendDirection.DOWNTREND:
        return None  # Don't buy in strong downtrend

    # 2. Pattern detection
    patterns = detect_all_patterns(candles)
    bullish_patterns = [p for p in patterns
                        if p.pattern in (PatternType.BULLISH_ENGULFING,
                                         PatternType.MORNING_STAR,
                                         PatternType.HAMMER,
                                         PatternType.THREE_WHITE_SOLDIERS)]

    if not bullish_patterns and trend.direction != TrendDirection.UPTREND:
        return None  # Need either bullish pattern or strong uptrend

    # 3. Support level check (price near support = good entry)
    supports, _ = _find_swing_points(candles)

    entry_price = current_price
    confidence = trend.confidence
    reasons = []

    # Base confidence from trend
    if trend.direction == TrendDirection.UPTREND:
        confidence = max(confidence, 0.6)
        reasons.append(f"Uptrend detected (strength: {trend.strength:.3f})")
    elif bullish_patterns:
        confidence = max(confidence, 0.5)
        reasons.append(f"Bullish reversal pattern: {bullish_patterns[0].pattern.value}")

    # Pattern bonus
    for pattern in bullish_patterns:
        confidence = min(confidence + pattern.confidence * 0.2, 0.95)
        reasons.append(f"Pattern: {pattern.pattern.value} (confidence: {pattern.confidence})")

    # Fibonacci support check
    if len(candles) >= 20:
        recent_high = max(c.high for c in candles[-20:])
        recent_low = min(c.low for c in candles[-20:])
        fib = compute_fibonacci_levels(recent_high, recent_low)

        if _is_price_near_level(current_price, fib.level_382, 0.02):
            confidence = min(confidence + 0.1, 0.95)
            reasons.append(f"Price near 38.2% Fibonacci support ({fib.level_382:.4f})")
        elif _is_price_near_level(current_price, fib.level_618, 0.02):
            confidence = min(confidence + 0.15, 0.95)
            reasons.append(f"Price near 61.8% Fibonacci support ({fib.level_618:.4f})")

    if not reasons:
        return None

    # Suggest stop loss below recent swing low
    swing_lows = _find_swing_points(candles)[1]
    stop_loss = None
    if swing_lows:
        stop_loss = min(swing_lows[-3:]) if len(swing_lows) >= 3 else min(swing_lows)
        stop_loss -= stop_loss * 0.005  # 0.5% buffer below swing low

    return EntrySignal(
        signal_type=SignalType.BUY,
        price=round(entry_price, 8),
        confidence=round(confidence, 4),
        reason="; ".join(reasons),
        stop_loss=round(stop_loss, 8) if stop_loss else None,
        pattern=bullish_patterns[0].pattern.value if bullish_patterns else None,
    )


def detect_entry_sell(candles: List[Candle],
                       min_candles: int = 10) -> Optional[EntrySignal]:
    """
    Detect a sell entry point based on trend, patterns, and price action.

    Strategy:
    1. Confirm downtrend or bearish reversal
    2. Look for bearish candle patterns (engulfing, evening star, shooting star)
    3. Check for rejection at resistance

    Args:
        candles: List of candles (oldest to newest)
        min_candles: Minimum candles required for analysis

    Returns:
        EntrySignal if sell conditions met, None otherwise
    """
    if len(candles) < min_candles:
        return None

    last_candle = candles[-1]
    current_price = last_candle.close

    # 1. Trend analysis
    trend = detect_trend(candles)

    if trend.direction == TrendDirection.UPTREND:
        return None  # Don't sell in strong uptrend

    # 2. Pattern detection
    patterns = detect_all_patterns(candles)
    bearish_patterns = [p for p in patterns
                        if p.pattern in (PatternType.BEARISH_ENGULFING,
                                         PatternType.EVENING_STAR,
                                         PatternType.SHOOTING_STAR,
                                         PatternType.THREE_BLACK_CROWS)]

    if not bearish_patterns and trend.direction != TrendDirection.DOWNTREND:
        return None

    entry_price = current_price
    confidence = trend.confidence
    reasons = []

    if trend.direction == TrendDirection.DOWNTREND:
        confidence = max(confidence, 0.6)
        reasons.append(f"Downtrend detected (strength: {trend.strength:.3f})")
    elif bearish_patterns:
        confidence = max(confidence, 0.5)
        reasons.append(f"Bearish reversal pattern: {bearish_patterns[0].pattern.value}")

    for pattern in bearish_patterns:
        confidence = min(confidence + pattern.confidence * 0.2, 0.95)
        reasons.append(f"Pattern: {pattern.pattern.value} (confidence: {pattern.confidence})")

    # Resistance check
    if len(candles) >= 20:
        recent_high = max(c.high for c in candles[-20:])
        recent_low = min(c.low for c in candles[-20:])
        fib = compute_fibonacci_levels(recent_high, recent_low)

        if _is_price_near_level(current_price, fib.level_618, 0.02):
            confidence = min(confidence + 0.1, 0.95)
            reasons.append(f"Price near 61.8% Fibonacci resistance ({fib.level_618:.4f})")
        elif _is_price_near_level(current_price, fib.level_382, 0.02):
            confidence = min(confidence + 0.1, 0.95)
            reasons.append(f"Price near 38.2% Fibonacci resistance ({fib.level_382:.4f})")

    if not reasons:
        return None

    # Suggest stop loss above recent swing high
    swing_highs = _find_swing_points(candles)[0]
    stop_loss = None
    if swing_highs:
        stop_loss = max(swing_highs[-3:]) if len(swing_highs) >= 3 else max(swing_highs)
        stop_loss += stop_loss * 0.005  # 0.5% buffer above swing high

    return EntrySignal(
        signal_type=SignalType.SELL,
        price=round(entry_price, 8),
        confidence=round(confidence, 4),
        reason="; ".join(reasons),
        stop_loss=round(stop_loss, 8) if stop_loss else None,
        pattern=bearish_patterns[0].pattern.value if bearish_patterns else None,
    )


def detect_entry(candles: List[Candle],
                  min_candles: int = 10) -> Optional[EntrySignal]:
    """
    Detect any entry point (buy or sell), preferring whichever has higher confidence.

    Args:
        candles: List of candles (oldest to newest)
        min_candles: Minimum candles required for analysis

    Returns:
        Best EntrySignal or None if no signal detected
    """
    buy_signal = detect_entry_buy(candles, min_candles)
    sell_signal = detect_entry_sell(candles, min_candles)

    if buy_signal and sell_signal:
        return buy_signal if buy_signal.confidence >= sell_signal.confidence else sell_signal
    return buy_signal or sell_signal