# app/src/trading/market_analysis/indicators/trend.py
 
from __future__ import annotations

from src.trading.market_analysis.models import (
    TrendDirection,
    TrendQuality,
    TrendStrength,
)


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

    multiplier = 2 / (period + 1)
    current = sum(values[:period]) / period

    for value in values[period:]:
        current = (value - current) * multiplier + current

    return current


def trend_gap_percent_value(
    *,
    ema_fast: float,
    ema_slow: float,
) -> float | None:
    if ema_slow <= 0:
        return None

    return ((ema_fast - ema_slow) / ema_slow) * 100


def ema_slope_percent(
    *,
    closes: list[float],
    period: int,
    window: int,
) -> float | None:
    required = period + window + 5

    if len(closes) < required:
        return None

    current_ema = ema(closes, period)
    previous_ema = ema(closes[:-window], period)

    if (
        current_ema is None
        or previous_ema is None
        or previous_ema <= 0
    ):
        return None

    return ((current_ema - previous_ema) / previous_ema) * 100


def classify_trend(
    *,
    ema_fast: float,
    ema_slow: float,
    ema_fast_slope_percent: float | None = None,
    ema_slow_slope_percent: float | None = None,
    fast_slope_threshold_percent: float,
    slow_slope_threshold_percent: float,
    trend_direction_gap_threshold_percent: float,
) -> TrendDirection:
    gap_percent = trend_gap_percent_value(
        ema_fast=ema_fast,
        ema_slow=ema_slow,
    )

    if gap_percent is None:
        return TrendDirection.UNKNOWN

    fast_slope = ema_fast_slope_percent or 0.0
    slow_slope = ema_slow_slope_percent or 0.0

    fast_up = fast_slope >= fast_slope_threshold_percent
    fast_down = fast_slope <= -fast_slope_threshold_percent

    slow_up = slow_slope >= slow_slope_threshold_percent
    slow_down = slow_slope <= -slow_slope_threshold_percent

    if gap_percent >= trend_direction_gap_threshold_percent:
        if fast_down and slow_down:
            return TrendDirection.FLAT
        return TrendDirection.UP

    if gap_percent <= -trend_direction_gap_threshold_percent:
        if fast_up and slow_up:
            return TrendDirection.FLAT
        return TrendDirection.DOWN

    if fast_up and slow_up:
        return TrendDirection.UP

    if fast_down and slow_down:
        return TrendDirection.DOWN

    return TrendDirection.FLAT


def classify_trend_strength(
    *,
    trend_gap_percent: float | None,
    weak_threshold_percent: float,
    strong_threshold_percent: float,
) -> TrendStrength:
    if trend_gap_percent is None:
        return TrendStrength.UNKNOWN

    gap = abs(trend_gap_percent)

    if gap < weak_threshold_percent:
        return TrendStrength.WEAK

    if gap < strong_threshold_percent:
        return TrendStrength.NORMAL

    return TrendStrength.STRONG


def trend_consistency(
    *,
    closes: list[float],
    trend: TrendDirection,
    trend_consistency_window: int,
) -> float | None:
    if len(closes) < 2:
        return None

    window = closes[-trend_consistency_window:]

    if len(window) < 2:
        return None

    up_moves = 0
    down_moves = 0

    for previous_price, current_price in zip(window, window[1:]):
        if current_price > previous_price:
            up_moves += 1
        elif current_price < previous_price:
            down_moves += 1

    total_moves = max(1, len(window) - 1)

    if trend == TrendDirection.UP:
        return up_moves / total_moves

    if trend == TrendDirection.DOWN:
        return down_moves / total_moves

    return None


def trend_efficiency(
    *,
    closes: list[float],
    trend_consistency_window: int,
) -> float | None:
    window = closes[-trend_consistency_window:]

    if len(window) < 2:
        return None

    net_move = abs(window[-1] - window[0])
    total_move = 0.0

    for previous_price, current_price in zip(window, window[1:]):
        total_move += abs(current_price - previous_price)

    if total_move <= 0:
        return None

    return net_move / total_move


def ema_distance_atr_ratio(
    *,
    ema_fast: float,
    ema_slow: float,
    atr_value: float,
) -> float | None:
    if atr_value <= 0:
        return None

    return abs(ema_fast - ema_slow) / atr_value


def classify_trend_quality(
    *,
    trend_consistency: float | None,
    trend_efficiency: float | None,
    ema_distance_atr_ratio: float | None,
    candle_noise_score: float | None,
    price_position_score: float | None,
    trend_strength: TrendStrength,
    min_clean_candle_score: float,
    min_price_position_score: float,
) -> TrendQuality:
    if trend_consistency is None:
        return TrendQuality.UNKNOWN

    if trend_strength == TrendStrength.WEAK:
        return TrendQuality.NOISY

    if (
        candle_noise_score is not None
        and candle_noise_score < min_clean_candle_score
    ):
        return TrendQuality.NOISY

    if (
        price_position_score is not None
        and price_position_score < min_price_position_score
    ):
        return TrendQuality.NOISY

    if trend_efficiency is not None and trend_efficiency < 0.28:
        return TrendQuality.NOISY

    # Сжатые EMA сами по себе не означают шум.
    # После флэта хороший вход часто начинается именно из сжатия.
    # Поэтому качество тренда не понижаем только из-за EMA compression.
    if (
        ema_distance_atr_ratio is not None
        and ema_distance_atr_ratio < 0.25
        and trend_efficiency is not None
        and trend_efficiency < 0.25
    ):
        return TrendQuality.NOISY

    if trend_consistency >= 0.68:
        return TrendQuality.CLEAN

    if trend_consistency >= 0.55:
        return TrendQuality.NORMAL

    return TrendQuality.NOISY