from __future__ import annotations

from statistics import fmean

from crypto_spot_bot.models import Candle


def add_indicators(candles: list[Candle]) -> list[Candle]:
    closes = [c.close for c in candles]
    highs = [c.high for c in candles]
    lows = [c.low for c in candles]
    volumes = [c.volume for c in candles]
    ema20 = _ema(closes, 20)
    ema50 = _ema(closes, 50)
    ema200 = _ema(closes, 200)
    rsi14 = _rsi(closes, 14)
    atr14 = _atr(highs, lows, closes, 14)
    macd, macd_signal, macd_hist = _macd(closes)
    volume_ma20 = _sma(volumes, 20)
    for index, candle in enumerate(candles):
        candle.ema_20 = ema20[index]
        candle.ema_50 = ema50[index]
        candle.ema_200 = ema200[index]
        candle.rsi_14 = rsi14[index]
        candle.atr_14 = atr14[index]
        candle.macd = macd[index]
        candle.macd_signal = macd_signal[index]
        candle.macd_hist = macd_hist[index]
        candle.volume_ma_20 = volume_ma20[index]
    return candles


def _ema(values: list[float], period: int) -> list[float | None]:
    if not values:
        return []
    result: list[float | None] = [None] * len(values)
    if len(values) < period:
        return result
    seed = fmean(values[:period])
    result[period - 1] = seed
    multiplier = 2 / (period + 1)
    previous = seed
    for index in range(period, len(values)):
        previous = (values[index] - previous) * multiplier + previous
        result[index] = previous
    return result


def _sma(values: list[float], period: int) -> list[float | None]:
    result: list[float | None] = []
    for index in range(len(values)):
        if index + 1 < period:
            result.append(None)
        else:
            result.append(fmean(values[index + 1 - period : index + 1]))
    return result


def _rsi(closes: list[float], period: int) -> list[float | None]:
    result: list[float | None] = [None] * len(closes)
    if len(closes) <= period:
        return result
    gains: list[float] = []
    losses: list[float] = []
    for index in range(1, period + 1):
        delta = closes[index] - closes[index - 1]
        gains.append(max(delta, 0.0))
        losses.append(abs(min(delta, 0.0)))
    avg_gain = fmean(gains)
    avg_loss = fmean(losses)
    result[period] = _rsi_value(avg_gain, avg_loss)
    for index in range(period + 1, len(closes)):
        delta = closes[index] - closes[index - 1]
        gain = max(delta, 0.0)
        loss = abs(min(delta, 0.0))
        avg_gain = ((avg_gain * (period - 1)) + gain) / period
        avg_loss = ((avg_loss * (period - 1)) + loss) / period
        result[index] = _rsi_value(avg_gain, avg_loss)
    return result


def _rsi_value(avg_gain: float, avg_loss: float) -> float:
    if avg_loss == 0:
        return 100.0
    rs = avg_gain / avg_loss
    return 100 - (100 / (1 + rs))


def _atr(highs: list[float], lows: list[float], closes: list[float], period: int) -> list[float | None]:
    result: list[float | None] = [None] * len(closes)
    true_ranges: list[float] = []
    for index in range(len(closes)):
        if index == 0:
            true_ranges.append(highs[index] - lows[index])
        else:
            true_ranges.append(
                max(
                    highs[index] - lows[index],
                    abs(highs[index] - closes[index - 1]),
                    abs(lows[index] - closes[index - 1]),
                )
            )
    if len(true_ranges) < period:
        return result
    atr = fmean(true_ranges[:period])
    result[period - 1] = atr
    for index in range(period, len(true_ranges)):
        atr = ((atr * (period - 1)) + true_ranges[index]) / period
        result[index] = atr
    return result


def _macd(
    closes: list[float],
    fast_period: int = 12,
    slow_period: int = 26,
    signal_period: int = 9,
) -> tuple[list[float | None], list[float | None], list[float | None]]:
    ema_fast = _ema(closes, fast_period)
    ema_slow = _ema(closes, slow_period)
    macd_line: list[float | None] = []
    compact_macd: list[float] = []
    compact_indexes: list[int] = []
    for index, (fast, slow) in enumerate(zip(ema_fast, ema_slow)):
        value = None if fast is None or slow is None else fast - slow
        macd_line.append(value)
        if value is not None:
            compact_macd.append(value)
            compact_indexes.append(index)

    signal_line: list[float | None] = [None] * len(closes)
    hist: list[float | None] = [None] * len(closes)
    compact_signal = _ema(compact_macd, signal_period)
    for compact_index, original_index in enumerate(compact_indexes):
        signal = compact_signal[compact_index]
        macd_value = macd_line[original_index]
        signal_line[original_index] = signal
        if macd_value is not None and signal is not None:
            hist[original_index] = macd_value - signal
    return macd_line, signal_line, hist
