"""Causal Technical Indicators & Market Microstructure Features."""
from __future__ import annotations

import math
from decimal import Decimal
from typing import Sequence

from backend.app.schemas.market import Candle


def calculate_sma(values: Sequence[float], period: int) -> list[float]:
    """Simple Moving Average (strictly causal)."""
    n = len(values)
    sma = [float("nan")] * n
    if n < period:
        return sma

    running_sum = sum(values[:period])
    sma[period - 1] = running_sum / period

    for i in range(period, n):
        running_sum += values[i] - values[i - period]
        sma[i] = running_sum / period
    return sma


def calculate_ema(values: Sequence[float], period: int) -> list[float]:
    """Exponential Moving Average (strictly causal)."""
    n = len(values)
    ema = [float("nan")] * n
    if n < period:
        return ema

    multiplier = 2.0 / (period + 1)
    # Seed with SMA of first `period` points
    seed = sum(values[:period]) / period
    ema[period - 1] = seed

    for i in range(period, n):
        ema[i] = (values[i] - ema[i - 1]) * multiplier + ema[i - 1]
    return ema


def calculate_rsi(prices: Sequence[float], period: int = 14) -> list[float]:
    """Relative Strength Index with Wilder's smoothing."""
    n = len(prices)
    rsi = [float("nan")] * n
    if n <= period:
        return rsi

    gains = [0.0] * n
    losses = [0.0] * n

    for i in range(1, n):
        change = prices[i] - prices[i - 1]
        if change > 0:
            gains[i] = change
        else:
            losses[i] = abs(change)

    avg_gain = sum(gains[1 : period + 1]) / period
    avg_loss = sum(losses[1 : period + 1]) / period

    if avg_loss == 0:
        rsi[period] = 100.0
    else:
        rs = avg_gain / avg_loss
        rsi[period] = 100.0 - (100.0 / (1.0 + rs))

    for i in range(period + 1, n):
        avg_gain = (avg_gain * (period - 1) + gains[i]) / period
        avg_loss = (avg_loss * (period - 1) + losses[i]) / period

        if avg_loss == 0:
            rsi[i] = 100.0
        else:
            rs = avg_gain / avg_loss
            rsi[i] = 100.0 - (100.0 / (1.0 + rs))
    return rsi


def calculate_macd(
    prices: Sequence[float], fast_period: int = 12, slow_period: int = 26, signal_period: int = 9
) -> tuple[list[float], list[float], list[float]]:
    """Moving Average Convergence Divergence (MACD line, Signal line, Histogram)."""
    fast_ema = calculate_ema(prices, fast_period)
    slow_ema = calculate_ema(prices, slow_period)
    n = len(prices)

    macd_line = [float("nan")] * n
    for i in range(n):
        if not math.isnan(fast_ema[i]) and not math.isnan(slow_ema[i]):
            macd_line[i] = fast_ema[i] - slow_ema[i]

    # Signal line is EMA of macd_line
    valid_macd_indices = [i for i, v in enumerate(macd_line) if not math.isnan(v)]
    signal_line = [float("nan")] * n
    hist = [float("nan")] * n

    if len(valid_macd_indices) >= signal_period:
        valid_vals = [macd_line[i] for i in valid_macd_indices]
        valid_signal = calculate_ema(valid_vals, signal_period)
        for idx, orig_i in enumerate(valid_macd_indices):
            signal_line[orig_i] = valid_signal[idx]
            if not math.isnan(valid_signal[idx]):
                hist[orig_i] = macd_line[orig_i] - valid_signal[idx]

    return macd_line, signal_line, hist


def calculate_atr(candles: Sequence[Candle], period: int = 14) -> list[float]:
    """Average True Range (strictly causal)."""
    n = len(candles)
    atr = [float("nan")] * n
    if n <= period:
        return atr

    tr = [0.0] * n
    for i in range(n):
        h = float(candles[i].high)
        l = float(candles[i].low)
        if i == 0:
            tr[i] = h - l
        else:
            prev_c = float(candles[i - 1].close)
            tr[i] = max(h - l, abs(h - prev_c), abs(l - prev_c))

    # Wilder's smoothing
    running_atr = sum(tr[1 : period + 1]) / period
    atr[period] = running_atr

    for i in range(period + 1, n):
        running_atr = (running_atr * (period - 1) + tr[i]) / period
        atr[i] = running_atr

    return atr


def calculate_bollinger_bands(
    prices: Sequence[float], period: int = 20, num_std: float = 2.0
) -> tuple[list[float], list[float], list[float]]:
    """Bollinger Bands: Middle, Upper, Lower."""
    sma = calculate_sma(prices, period)
    n = len(prices)
    upper = [float("nan")] * n
    lower = [float("nan")] * n

    for i in range(period - 1, n):
        window = prices[i - period + 1 : i + 1]
        mean = sma[i]
        variance = sum((x - mean) ** 2 for x in window) / period
        std = math.sqrt(variance)
        upper[i] = mean + num_std * std
        lower[i] = mean - num_std * std

    return sma, upper, lower
