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

from __future__ import annotations

from collections.abc import Sequence
from math import isfinite

from src.core.numbers import safe_float
from src.core.types import NumericLike
from src.market_data.acquisition.models.candle import Candle
from src.trading.market_analysis.models import VolatilityState


def atr(
    candles: Sequence[Candle],
    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:]):
        previous_close = safe_float(previous.close_price)
        current_high = safe_float(current.high_price)
        current_low = safe_float(current.low_price)

        if (
            previous_close is None
            or current_high is None
            or current_low is None
            or not isfinite(previous_close)
            or not isfinite(current_high)
            or not isfinite(current_low)
        ):
            continue

        high_low = current_high - current_low
        high_close = abs(current_high - previous_close)
        low_close = abs(current_low - previous_close)

        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[Candle],
    close_price: float,
    atr_period: int,
    atr_baseline_window: int,
) -> float | None:
    if not isfinite(close_price) or close_price <= 0:
        return None

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

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

        if atr_value is None or not isfinite(atr_value):
            continue

        close = safe_float(part[-1].close_price)

        if (
            close is None
            or not isfinite(close)
            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 multiplier_value is None
        or not isfinite(atr_value)
        or not isfinite(multiplier_value)
        or not isfinite(minimum_value)
        or atr_value <= 0
    ):
        return minimum_value if isfinite(minimum_value) else 0.0

    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 not isfinite(atr_value)
        or atr_value <= 0
    ):
        return VolatilityState.UNKNOWN

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

    if local_ratio is not None and not isfinite(local_ratio):
        local_ratio = None

    if htf_ratio is not None and not isfinite(htf_ratio):
        htf_ratio = None

    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)
        high_value = safe_float(high_volatility_atr_percent)

        if low_value is None or not isfinite(low_value):
            low_value = 0.05

        if high_value is None or not isfinite(high_value):
            high_value = 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