# app/src/trading/market_analysis/indicators/momentum.py

from __future__ import annotations

from src.trading.market_analysis.models import (
    MomentumState,
    TrendDirection,
)


def rsi(values: list[float], period: int = 14) -> float | None:
    if period <= 0 or len(values) < period + 1:
        return None

    gains: list[float] = []
    losses: list[float] = []

    recent = values[-(period + 1):]

    for previous, current in zip(recent, recent[1:]):
        change = current - previous

        if change > 0:
            gains.append(change)
            losses.append(0.0)
        else:
            gains.append(0.0)
            losses.append(abs(change))

    average_gain = sum(gains) / period
    average_loss = sum(losses) / period

    if average_loss == 0:
        return 100.0

    rs = average_gain / average_loss
    return 100 - (100 / (1 + rs))


def recent_change_percent(
    *,
    closes: list[float],
    window: int,
) -> float | None:
    if window <= 0 or len(closes) < window + 1:
        return None

    first_price = closes[-(window + 1)]
    last_price = closes[-1]

    if first_price <= 0:
        return None

    return ((last_price - first_price) / first_price) * 100


def momentum_breakout_state(
    *,
    closes: list[float],
    momentum_window: int,
    momentum_decay_window: int,
    breakout_lookback: int,
    momentum_change_threshold_percent: float,
    momentum_decay_threshold_percent: float,
    breakout_distance_threshold_percent: float,
) -> tuple[
    MomentumState,
    TrendDirection,
    float | None,
    float | None,
    float | None,
    float | None,
    str | None,
]:
    if len(closes) < max(momentum_window + 1, breakout_lookback + 1):
        return (
            MomentumState.UNKNOWN,
            TrendDirection.UNKNOWN,
            None,
            None,
            None,
            None,
            "NOT_ENOUGH_DATA",
        )

    first_price = closes[-(momentum_window + 1)]
    last_price = closes[-1]

    if first_price <= 0 or last_price <= 0:
        return (
            MomentumState.UNKNOWN,
            TrendDirection.UNKNOWN,
            None,
            None,
            None,
            None,
            "INVALID_PRICE",
        )

    momentum_change_percent = ((last_price - first_price) / first_price) * 100
    abs_change = abs(momentum_change_percent)

    recent_change_value = recent_change_percent(
        closes=closes,
        window=momentum_decay_window,
    )

    recent_abs_change = (
        abs(recent_change_value)
        if recent_change_value is not None
        else None
    )

    if (
        momentum_change_percent >= momentum_change_threshold_percent
        and recent_change_value is not None
        and recent_change_value > momentum_decay_threshold_percent
    ):
        momentum_direction = TrendDirection.UP

    elif (
        momentum_change_percent <= -momentum_change_threshold_percent
        and recent_change_value is not None
        and recent_change_value < -momentum_decay_threshold_percent
    ):
        momentum_direction = TrendDirection.DOWN

    else:
        momentum_direction = TrendDirection.FLAT

    if momentum_direction == TrendDirection.FLAT:
        if recent_abs_change is not None:
            momentum_strength = min(
                recent_abs_change / momentum_decay_threshold_percent,
                3.0,
            )
        else:
            momentum_strength = 0.0
    else:
        momentum_strength = min(
            abs_change / momentum_change_threshold_percent,
            3.0,
        )

    lookback_window = closes[-(breakout_lookback + 1):-1]
    previous_high = max(lookback_window)
    previous_low = min(lookback_window)

    if previous_high <= 0 or previous_low <= 0:
        return (
            MomentumState.UNKNOWN,
            TrendDirection.UNKNOWN,
            momentum_change_percent,
            momentum_strength,
            None,
            None,
            "INVALID_BREAKOUT_LEVEL",
        )

    if last_price > previous_high:
        breakout_distance_percent = ((last_price - previous_high) / previous_high) * 100

        if breakout_distance_percent >= breakout_distance_threshold_percent:
            return (
                MomentumState.BREAKOUT_UP,
                TrendDirection.UP,
                momentum_change_percent,
                momentum_strength,
                previous_high,
                breakout_distance_percent,
                "PRICE_ABOVE_LOOKBACK_HIGH",
            )

    if last_price < previous_low:
        breakout_distance_percent = ((previous_low - last_price) / previous_low) * 100

        if breakout_distance_percent >= breakout_distance_threshold_percent:
            return (
                MomentumState.BREAKOUT_DOWN,
                TrendDirection.DOWN,
                momentum_change_percent,
                momentum_strength,
                previous_low,
                breakout_distance_percent,
                "PRICE_BELOW_LOOKBACK_LOW",
            )

    if momentum_direction == TrendDirection.UP:
        return (
            MomentumState.MOMENTUM_UP,
            TrendDirection.UP,
            momentum_change_percent,
            momentum_strength,
            None,
            None,
            "FAST_UP_MOVE",
        )

    if momentum_direction == TrendDirection.DOWN:
        return (
            MomentumState.MOMENTUM_DOWN,
            TrendDirection.DOWN,
            momentum_change_percent,
            momentum_strength,
            None,
            None,
            "FAST_DOWN_MOVE",
        )

    return (
        MomentumState.NONE,
        TrendDirection.FLAT,
        momentum_change_percent,
        momentum_strength,
        None,
        None,
        "NO_SIGNIFICANT_MOMENTUM",
    )