"""RSI (Relative Strength Index) signal evaluator.

Computes the standard 14-period RSI using Wilder's smoothing method and
produces overbought (RSI > 70 → BEARISH) or oversold (RSI < 30 → BULLISH)
signals with strength scaled by distance from the threshold.

When RSI is between 30 and 70 (neutral zone), no signal is produced.
"""

from __future__ import annotations

from services.signal_engine.models import OHLCVBar, SignalDirection, SignalResult
from services.signal_engine.signals.base import validate_lookback

# Default RSI period (standard Wilder 14-period)
DEFAULT_RSI_PERIOD: int = 14

# Minimum bars required: period + 1 (for initial price change calculation)
DEFAULT_MIN_BARS: int = DEFAULT_RSI_PERIOD + 1  # 15

# Overbought / oversold thresholds
OVERBOUGHT_THRESHOLD: float = 70.0
OVERSOLD_THRESHOLD: float = 30.0

# Maximum possible distance from threshold (used for strength scaling)
_MAX_DISTANCE_OVERBOUGHT: float = 100.0 - OVERBOUGHT_THRESHOLD  # 30
_MAX_DISTANCE_OVERSOLD: float = OVERSOLD_THRESHOLD - 0.0  # 30

# Confidence multiplier
_CONFIDENCE_MULTIPLIER: float = 0.85


def compute_rsi(bars: list[OHLCVBar], period: int = DEFAULT_RSI_PERIOD) -> float | None:
    """Compute RSI using Wilder's smoothing method.

    Args:
        bars: OHLCV bar series (oldest-first).
        period: RSI period (default 14).

    Returns:
        RSI value in [0, 100], or ``None`` if insufficient data.
    """
    min_bars = period + 1
    if len(bars) < min_bars:
        return None

    closes = [bar.close for bar in bars]

    # Calculate price changes
    changes = [closes[i] - closes[i - 1] for i in range(1, len(closes))]

    # Separate gains and losses for the first `period` changes
    first_gains = [max(0.0, c) for c in changes[:period]]
    first_losses = [max(0.0, -c) for c in changes[:period]]

    avg_gain = sum(first_gains) / period
    avg_loss = sum(first_losses) / period

    # Apply Wilder smoothing for subsequent changes
    for c in changes[period:]:
        gain = max(0.0, c)
        loss = max(0.0, -c)
        avg_gain = (avg_gain * (period - 1) + gain) / period
        avg_loss = (avg_loss * (period - 1) + loss) / period

    # Avoid division by zero: if avg_loss is 0, RSI is 100
    if avg_loss == 0.0:
        return 100.0

    rs = avg_gain / avg_loss
    rsi = 100.0 - (100.0 / (1.0 + rs))
    return rsi


class RSIEvaluator:
    """RSI signal evaluator.

    Satisfies the :class:`~services.signal_engine.signals.base.SignalEvaluator`
    protocol.

    Parameters
    ----------
    period:
        RSI calculation period.  Defaults to ``14``.
    """

    def __init__(self, period: int = DEFAULT_RSI_PERIOD) -> None:
        self.period = period
        self.min_bars = period + 1

    # ------------------------------------------------------------------
    # Public API (SignalEvaluator protocol)
    # ------------------------------------------------------------------

    def evaluate(
        self,
        bars: list[OHLCVBar],
        timeframe: str,
    ) -> SignalResult | None:
        """Evaluate RSI on *bars* for *timeframe*.

        Returns ``None`` when there are fewer than ``period + 1`` bars
        or when RSI is in the neutral zone (30–70).
        """
        if not validate_lookback(bars, self.min_bars):
            return None

        rsi = compute_rsi(bars, self.period)
        if rsi is None:
            return None

        # Overbought: RSI > 70 → BEARISH (potential reversal down)
        if rsi > OVERBOUGHT_THRESHOLD:
            distance = rsi - OVERBOUGHT_THRESHOLD
            strength = min(1.0, max(0.0, distance / _MAX_DISTANCE_OVERBOUGHT))
            confidence = strength * _CONFIDENCE_MULTIPLIER
            return SignalResult(
                signal_type="rsi",
                timeframe=timeframe,
                strength=strength,
                direction=SignalDirection.BEARISH,
                confidence=confidence,
                metadata={
                    "rsi": rsi,
                    "period": self.period,
                    "zone": "overbought",
                },
            )

        # Oversold: RSI < 30 → BULLISH (potential reversal up)
        if rsi < OVERSOLD_THRESHOLD:
            distance = OVERSOLD_THRESHOLD - rsi
            strength = min(1.0, max(0.0, distance / _MAX_DISTANCE_OVERSOLD))
            confidence = strength * _CONFIDENCE_MULTIPLIER
            return SignalResult(
                signal_type="rsi",
                timeframe=timeframe,
                strength=strength,
                direction=SignalDirection.BULLISH,
                confidence=confidence,
                metadata={
                    "rsi": rsi,
                    "period": self.period,
                    "zone": "oversold",
                },
            )

        # Neutral zone (30 ≤ RSI ≤ 70): no signal
        return None
