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

from __future__ import annotations

from collections.abc import Sequence

from src.core.numbers import safe_float
from src.core.types import NumericLike
from src.integrations.exchange.models import Kline
from src.trading.market_analysis.models import VolatilityState


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

    true_ranges: list[float] = []

    for previous, current in zip(candles, candles[1:]):
        high_low = current.high_price - current.low_price
        high_close = abs(current.high_price - previous.close_price)
        low_close = abs(current.low_price - previous.close_price)

        true_ranges.append(max(high_low, high_close, low_close))

    if len(true_ranges) < period:
        return None

    recent = true_ranges[-period:]
    return sum(recent) / period


def atr_percent_baseline(
    *,
    candles: Sequence[Kline],
    close_price: float,
    atr_period: int,
    atr_baseline_window: int,
) -> float | None:
    if close_price <= 0:
        return None

    values: list[float] = []
    window: list[Kline] = list(candles[-atr_baseline_window:])

    for index in range(atr_period, len(window) + 1):
        part: list[Kline] = window[:index]
        atr_value = atr(list(part), atr_period)

        if atr_value is None:
            continue

        close = getattr(part[-1], "close_price", None)

        if close is None or close <= 0:
            continue

        values.append((atr_value / close) * 100)

    if not values:
        return None

    values.sort()
    middle = len(values) // 2

    if len(values) % 2 == 1:
        return values[middle]

    return (values[middle - 1] + values[middle]) / 2


def adaptive_threshold(
    *,
    atr_percent: NumericLike | None,
    multiplier: NumericLike,
    minimum: NumericLike,
) -> float:
    atr_value = safe_float(atr_percent)
    multiplier_value = safe_float(multiplier)
    minimum_value = safe_float(minimum) or 0.0

    if atr_value is None or atr_value <= 0 or multiplier_value is None:
        return minimum_value

    return max(minimum_value, atr_value * multiplier_value)


def classify_volatility(
    *,
    atr_percent: NumericLike,
    volatility_ratio: NumericLike | None,
    htf_volatility_ratio: NumericLike | None = None,
    low_volatility_atr_percent: NumericLike = 0.05,
    high_volatility_atr_percent: NumericLike = 1.8,
) -> VolatilityState:
    atr_value = safe_float(atr_percent)

    if atr_value is None or atr_value <= 0:
        return VolatilityState.UNKNOWN

    local_ratio = safe_float(volatility_ratio)
    htf_ratio = safe_float(htf_volatility_ratio)

    if htf_ratio is not None:
        if htf_ratio > 1.8 and (local_ratio is None or local_ratio > 1.1):
            return VolatilityState.HIGH

        if htf_ratio < 0.55 and (local_ratio is None or local_ratio < 0.85):
            return VolatilityState.LOW

    if local_ratio is None:
        low_value = safe_float(low_volatility_atr_percent) or 0.05
        high_value = safe_float(high_volatility_atr_percent) or 1.8

        if atr_value < low_value:
            return VolatilityState.LOW

        if atr_value > high_value:
            return VolatilityState.HIGH

        return VolatilityState.NORMAL

    if local_ratio < 0.55:
        return VolatilityState.LOW

    if local_ratio > 1.8:
        return VolatilityState.HIGH

    return VolatilityState.NORMAL