"""Candle pattern recognition for trading signals."""

from __future__ import annotations

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

from .trend_analysis import Candle


class PatternType(Enum):
    """Recognized candlestick patterns."""
    BULLISH_ENGULFING = "bullish_engulfing"
    BEARISH_ENGULFING = "bearish_engulfing"
    MORNING_STAR = "morning_star"
    EVENING_STAR = "evening_star"
    HAMMER = "hammer"
    INVERTED_HAMMER = "inverted_hammer"
    SHOOTING_STAR = "shooting_star"
    DOJI = "doji"
    THREE_WHITE_SOLDIERS = "three_white_soldiers"
    THREE_BLACK_CROWS = "three_black_crows"


@dataclass
class PatternSignal:
    """A detected candle pattern signal."""
    pattern: PatternType
    index: int  # Index in candle list where pattern ends
    confidence: float  # 0.0 to 1.0
    description: str


def _body_size(candle: Candle) -> float:
    """Absolute body size of a candle."""
    return abs(candle.close - candle.open)


def _upper_shadow(candle: Candle) -> float:
    """Upper shadow (wick) size."""
    return candle.high - max(candle.open, candle.close)


def _lower_shadow(candle: Candle) -> float:
    """Lower shadow (wick) size."""
    return min(candle.open, candle.close) - candle.low


def _is_bullish(candle: Candle) -> bool:
    """Check if candle is bullish (close > open)."""
    return candle.close > candle.open


def _is_bearish(candle: Candle) -> bool:
    """Check if candle is bearish (close < open)."""
    return candle.close < candle.open


def _avg_body(candles: List[Candle], start: int, end: int) -> float:
    """Average body size over a range of candles."""
    if start >= end or end > len(candles):
        return 0.0
    bodies = [_body_size(c) for c in candles[start:end]]
    return sum(bodies) / len(bodies) if bodies else 0.0


def detect_bullish_engulfing(candles: List[Candle],
                              min_body_ratio: float = 0.1) -> Optional[PatternSignal]:
    """
    Detect bullish engulfing pattern.

    Pattern: A small bearish candle followed by a larger bullish candle
    whose body completely engulfs the previous candle's body.

    Args:
        candles: List of candles (oldest to newest)
        min_body_ratio: Minimum ratio of second body to first body

    Returns:
        PatternSignal if detected, None otherwise
    """
    if len(candles) < 2:
        return None

    first = candles[-2]
    second = candles[-1]

    if not _is_bearish(first) or not _is_bullish(second):
        return None

    first_body = _body_size(first)
    second_body = _body_size(second)

    if first_body == 0:
        return None

    # Second body must engulf first body
    if second.open <= first.close and second.close >= first.open:
        # Second body should be meaningfully larger
        if second_body >= first_body * (1 + min_body_ratio):
            confidence = min(second_body / (first_body * 3), 1.0)
            return PatternSignal(
                pattern=PatternType.BULLISH_ENGULFING,
                index=len(candles) - 1,
                confidence=round(confidence, 4),
                description="Bullish engulfing pattern detected"
            )
    return None


def detect_bearish_engulfing(candles: List[Candle],
                              min_body_ratio: float = 0.1) -> Optional[PatternSignal]:
    """
    Detect bearish engulfing pattern.

    Pattern: A small bullish candle followed by a larger bearish candle
    whose body completely engulfs the previous candle's body.
    """
    if len(candles) < 2:
        return None

    first = candles[-2]
    second = candles[-1]

    if not _is_bullish(first) or not _is_bearish(second):
        return None

    first_body = _body_size(first)
    second_body = _body_size(second)

    if first_body == 0:
        return None

    if second.open >= first.close and second.close <= first.open:
        if second_body >= first_body * (1 + min_body_ratio):
            confidence = min(second_body / (first_body * 3), 1.0)
            return PatternSignal(
                pattern=PatternType.BEARISH_ENGULFING,
                index=len(candles) - 1,
                confidence=round(confidence, 4),
                description="Bearish engulfing pattern detected"
            )
    return None


def detect_morning_star(candles: List[Candle]) -> Optional[PatternSignal]:
    """
    Detect morning star pattern (bullish reversal).

    Pattern: Three candles - long bearish, small body (gap down), long bullish (gap up).
    """
    if len(candles) < 3:
        return None

    first = candles[-3]
    second = candles[-2]
    third = candles[-1]

    if not _is_bearish(first) or not _is_bullish(third):
        return None

    first_body = _body_size(first)
    third_body = _body_size(third)
    second_body = _body_size(second)

    avg_body = _avg_body(candles, -3, -1) if len(candles) >= 3 else first_body
    if avg_body == 0:
        return None

    # First candle is long bearish, third is long bullish
    if first_body / avg_body < 1.5 or third_body / avg_body < 1.5:
        return None

    # Second candle should have small body (gap)
    if second_body / avg_body > 0.3:
        return None

    # Third should open above second's close (gap up)
    if third.open <= second.close:
        return None

    confidence = min((third_body / first_body) / 2.0, 1.0)
    return PatternSignal(
        pattern=PatternType.MORNING_STAR,
        index=len(candles) - 1,
        confidence=round(confidence, 4),
        description="Morning star bullish reversal pattern"
    )


def detect_evening_star(candles: List[Candle]) -> Optional[PatternSignal]:
    """
    Detect evening star pattern (bearish reversal).

    Pattern: Three candles - long bullish, small body (gap up), long bearish (gap down).
    """
    if len(candles) < 3:
        return None

    first = candles[-3]
    second = candles[-2]
    third = candles[-1]

    if not _is_bullish(first) or not _is_bearish(third):
        return None

    first_body = _body_size(first)
    third_body = _body_size(third)
    second_body = _body_size(second)

    avg_body = _avg_body(candles, -3, -1) if len(candles) >= 3 else first_body
    if avg_body == 0:
        return None

    if first_body / avg_body < 1.5 or third_body / avg_body < 1.5:
        return None

    if second_body / avg_body > 0.3:
        return None

    # Third should open below second's close (gap down)
    if third.open >= second.close:
        return None

    confidence = min((third_body / first_body) / 2.0, 1.0)
    return PatternSignal(
        pattern=PatternType.EVENING_STAR,
        index=len(candles) - 1,
        confidence=round(confidence, 4),
        description="Evening star bearish reversal pattern"
    )


def detect_hammer(candles: List[Candle]) -> Optional[PatternSignal]:
    """
    Detect hammer pattern (bullish reversal at bottom).

    Pattern: Small body at upper end with long lower shadow (at least 2x body).
    """
    if len(candles) < 1:
        return None

    candle = candles[-1]
    body = _body_size(candle)
    lower = _lower_shadow(candle)
    upper = _upper_shadow(candle)

    if body == 0:
        return None

    # Long lower shadow, small upper shadow, small body
    if lower >= body * 2 and upper <= body * 0.5:
        confidence = min(lower / (body * 4), 1.0)
        return PatternSignal(
            pattern=PatternType.HAMMER,
            index=len(candles) - 1,
            confidence=round(confidence, 4),
            description="Hammer pattern detected (bullish reversal)"
        )
    return None


def detect_shooting_star(candles: List[Candle]) -> Optional[PatternSignal]:
    """
    Detect shooting star pattern (bearish reversal at top).

    Pattern: Small body at lower end with long upper shadow (at least 2x body).
    """
    if len(candles) < 1:
        return None

    candle = candles[-1]
    body = _body_size(candle)
    upper = _upper_shadow(candle)
    lower = _lower_shadow(candle)

    if body == 0:
        return None

    if upper >= body * 2 and lower <= body * 0.5:
        confidence = min(upper / (body * 4), 1.0)
        return PatternSignal(
            pattern=PatternType.SHOOTING_STAR,
            index=len(candles) - 1,
            confidence=round(confidence, 4),
            description="Shooting star pattern detected (bearish reversal)"
        )
    return None


def detect_inverted_hammer(candles: List[Candle]) -> Optional[PatternSignal]:
    """
    Detect inverted hammer pattern (bearish reversal at top).

    Pattern: Small body at lower end with long upper shadow (at least 2x body).
    """
    if len(candles) < 1:
        return None

    candle = candles[-1]
    body = _body_size(candle)
    upper = _upper_shadow(candle)
    lower = _lower_shadow(candle)

    if body == 0:
        return None

    if upper >= body * 2 and lower <= body * 0.5:
        confidence = min(upper / (body * 4), 1.0)
        return PatternSignal(
            pattern=PatternType.INVERTED_HAMMER,
            index=len(candles) - 1,
            confidence=round(confidence, 4),
            description="Inverted hammer pattern detected (bearish reversal)"
        )
    return None


def detect_doji(candles: List[Candle], threshold: float = 0.05) -> Optional[PatternSignal]:
    """
    Detect doji pattern (indecision).

    Pattern: Open and close are very close together (body < threshold of total range).
    """
    if len(candles) < 1:
        return None

    candle = candles[-1]
    body = _body_size(candle)
    total_range = candle.high - candle.low

    if total_range == 0:
        return None

    if body / total_range <= threshold:
        confidence = 1.0 - (body / total_range) / threshold
        return PatternSignal(
            pattern=PatternType.DOJI,
            index=len(candles) - 1,
            confidence=round(confidence, 4),
            description="Doji pattern detected (market indecision)"
        )
    return None


def detect_three_white_soldiers(candles: List[Candle]) -> Optional[PatternSignal]:
    """
    Detect three white soldiers pattern (strong bullish continuation).

    Pattern: Three consecutive long bullish candles with higher closes.
    """
    if len(candles) < 3:
        return None

    c1, c2, c3 = candles[-3], candles[-2], candles[-1]

    if not (_is_bullish(c1) and _is_bullish(c2) and _is_bullish(c3)):
        return None

    bodies = [_body_size(c) for c in [c1, c2, c3]]
    avg_body = sum(bodies) / len(bodies) if bodies else 0.0
    if avg_body == 0:
        return None

    # All bodies should be reasonably large (at least 60% of average)
    if all(b >= avg_body * 0.6 for b in bodies):
        # Each close should be higher than previous close
        if c1.close < c2.close < c3.close:
            confidence = min(sum(bodies) / (avg_body * 4), 1.0)
            return PatternSignal(
                pattern=PatternType.THREE_WHITE_SOLDIERS,
                index=len(candles) - 1,
                confidence=round(confidence, 4),
                description="Three white soldiers (strong bullish)"
            )
    return None


def detect_three_black_crows(candles: List[Candle]) -> Optional[PatternSignal]:
    """
    Detect three black crows pattern (strong bearish continuation).

    Pattern: Three consecutive long bearish candles with lower closes.
    """
    if len(candles) < 3:
        return None

    c1, c2, c3 = candles[-3], candles[-2], candles[-1]

    if not (_is_bearish(c1) and _is_bearish(c2) and _is_bearish(c3)):
        return None

    bodies = [_body_size(c) for c in [c1, c2, c3]]
    avg_body = sum(bodies) / len(bodies) if bodies else 0.0
    if avg_body == 0:
        return None

    if all(b >= avg_body * 0.6 for b in bodies):
        if c1.close > c2.close > c3.close:
            confidence = min(sum(bodies) / (avg_body * 4), 1.0)
            return PatternSignal(
                pattern=PatternType.THREE_BLACK_CROWS,
                index=len(candles) - 1,
                confidence=round(confidence, 4),
                description="Three black crows (strong bearish)"
            )
    return None


def detect_all_patterns(candles: List[Candle]) -> List[PatternSignal]:
    """
    Run all pattern detections on the given candles.

    Args:
        candles: List of candles (oldest to newest)

    Returns:
        List of all detected PatternSignal instances
    """
    signals = []

    # Single-candle patterns
    for detect_fn in [detect_hammer, detect_shooting_star, detect_inverted_hammer, detect_doji]:
        result = detect_fn(candles)
        if result:
            signals.append(result)

    # Multi-candle patterns
    for detect_fn in [detect_bullish_engulfing, detect_bearish_engulfing,
                       detect_morning_star, detect_evening_star,
                       detect_three_white_soldiers, detect_three_black_crows]:
        result = detect_fn(candles)
        if result:
            signals.append(result)

    return signals