# app/src/trading/market_analysis/htf.py

from __future__ import annotations

from math import isfinite

from src.core.numbers import safe_float
from src.core.types import JsonDict
from src.integrations.exchange.service import ExchangeService
from src.trading.market_analysis.indicators import atr, ema
from src.trading.market_analysis.indicators.trend import (
    classify_trend,
    classify_trend_quality,
    classify_trend_strength,
    ema_distance_atr_ratio as calculate_ema_distance_atr_ratio,
    ema_slope_percent,
    trend_consistency,
    trend_efficiency,
    trend_gap_percent_value,
)
from src.trading.market_analysis.indicators.volatility import (
    adaptive_threshold,
    atr_percent_baseline,
    classify_volatility,
)
from src.trading.market_analysis.models import (
    MarketPhase,
    MarketState,
    TrendDirection,
    TrendQuality,
    TrendStrength,
    VolatilityState,
)
from src.trading.market_analysis.quality import (
    candle_noise_score as calculate_candle_noise_score,
    price_position_score as calculate_price_position_score,
)


def htf_volatility_context(
    service,
    *,
    symbol: str,
    base_interval: str,
) -> JsonDict:
    if base_interval == service._htf_interval:
        return {
            "htf_interval": service._htf_interval,
            "htf_atr_percent": None,
            "htf_atr_percent_baseline": None,
            "htf_volatility_ratio": None,
            "htf_volatility": None,
            "htf_reason": "HTF_SKIPPED_SAME_INTERVAL",
        }

    try:
        candles = ExchangeService().get_candles(
            symbol=symbol,
            interval=service._htf_interval,
            limit=service._htf_limit,
        )
    except Exception as exc:
        return {
            "htf_interval": service._htf_interval,
            "htf_atr_percent": None,
            "htf_atr_percent_baseline": None,
            "htf_volatility_ratio": None,
            "htf_volatility": None,
            "htf_reason": f"HTF_KLINES_ERROR: {exc}",
        }

    if len(candles) < service._min_candles:
        return {
            "htf_interval": service._htf_interval,
            "htf_atr_percent": None,
            "htf_atr_percent_baseline": None,
            "htf_volatility_ratio": None,
            "htf_volatility": None,
            "htf_reason": "HTF_NOT_ENOUGH_CANDLES",
        }

    close_price = safe_float(candles[-1].close_price)
    atr_value = atr(candles, service._atr_period)

    if (
        close_price is None
        or not isfinite(close_price)
        or close_price <= 0
        or atr_value is None
    ):
        return {
            "htf_interval": service._htf_interval,
            "htf_atr_percent": None,
            "htf_atr_percent_baseline": None,
            "htf_volatility_ratio": None,
            "htf_volatility": None,
            "htf_reason": "HTF_ATR_UNAVAILABLE",
        }

    htf_atr_percent = (atr_value / close_price) * 100
    htf_baseline = atr_percent_baseline(
        candles=candles,
        close_price=close_price,
        atr_period=service._atr_period,
        atr_baseline_window=service._atr_baseline_window,
    )

    htf_ratio = (
        htf_atr_percent / htf_baseline
        if htf_baseline is not None and htf_baseline > 0
        else None
    )

    htf_volatility = classify_volatility(
        atr_percent=htf_atr_percent,
        volatility_ratio=htf_ratio,
        htf_volatility_ratio=None,
        low_volatility_atr_percent=service._low_volatility_atr_percent,
        high_volatility_atr_percent=service._high_volatility_atr_percent,
    )

    return {
        "htf_interval": service._htf_interval,
        "htf_atr_percent": round(htf_atr_percent, 4),
        "htf_atr_percent_baseline": round(htf_baseline, 4)
        if htf_baseline is not None
        else None,
        "htf_volatility_ratio": round(htf_ratio, 4)
        if htf_ratio is not None
        else None,
        "htf_volatility": htf_volatility.value,
        "htf_reason": "HTF_OK",
    }


def htf_trend_context(
    service,
    *,
    symbol: str,
    base_interval: str,
    local_state: MarketState,
    local_trend: TrendDirection,
) -> JsonDict:
    if base_interval == service._htf_interval:
        return {
            "htf_market_state": local_state.value,
            "htf_trend": local_trend.value,
            "htf_trend_strength": TrendStrength.UNKNOWN.value,
            "htf_trend_quality": TrendQuality.UNKNOWN.value,
            "htf_market_phase": MarketPhase.UNKNOWN.value,
            "htf_alignment": "SAME_INTERVAL",
            "htf_confirmation_score": 1.0,
            "htf_reason": "HTF_SKIPPED_SAME_INTERVAL",
        }

    try:
        candles = ExchangeService().get_candles(
            symbol=symbol,
            interval=service._htf_interval,
            limit=service._htf_limit,
        )
    except Exception as exc:
        return _htf_unknown_context(f"HTF_KLINES_ERROR: {exc}")

    if len(candles) < service._min_candles:
        return _htf_unknown_context("HTF_NOT_ENOUGH_CANDLES")

    closes: list[float] = []

    for candle in candles:
        close_value = safe_float(candle.close_price)

        if close_value is None or not isfinite(close_value):
            return _htf_unknown_context("HTF_INDICATORS_UNAVAILABLE")

        closes.append(close_value)

    close_price = closes[-1] if closes else None
    ema_fast = ema(closes, service._fast_ema_period)
    ema_slow = ema(closes, service._slow_ema_period)
    atr_value = atr(candles, service._atr_period)

    if (
        close_price is None
        or close_price <= 0
        or ema_fast is None
        or ema_slow is None
        or atr_value is None
    ):
        return _htf_unknown_context("HTF_INDICATORS_UNAVAILABLE")

    atr_percent = (atr_value / close_price) * 100

    fast_slope_threshold_percent = adaptive_threshold(
        atr_percent=atr_percent,
        multiplier=0.08,
        minimum=0.01,
    )

    slow_slope_threshold_percent = adaptive_threshold(
        atr_percent=atr_percent,
        multiplier=0.03,
        minimum=0.005,
    )

    trend_direction_gap_threshold_percent = adaptive_threshold(
        atr_percent=atr_percent,
        multiplier=0.12,
        minimum=0.025,
    )

    weak_trend_gap_threshold_percent = adaptive_threshold(
        atr_percent=atr_percent,
        multiplier=0.18,
        minimum=0.05,
    )

    strong_trend_gap_threshold_percent = adaptive_threshold(
        atr_percent=atr_percent,
        multiplier=0.55,
        minimum=0.18,
    )

    ema_fast_slope_percent = ema_slope_percent(
        closes=closes,
        period=service._fast_ema_period,
        window=service._ema_fast_slope_window,
    )

    ema_slow_slope_percent = ema_slope_percent(
        closes=closes,
        period=service._slow_ema_period,
        window=service._ema_slow_slope_window,
    )

    trend = classify_trend(
        ema_fast=ema_fast,
        ema_slow=ema_slow,
        ema_fast_slope_percent=ema_fast_slope_percent,
        ema_slow_slope_percent=ema_slow_slope_percent,
        fast_slope_threshold_percent=fast_slope_threshold_percent,
        slow_slope_threshold_percent=slow_slope_threshold_percent,
        trend_direction_gap_threshold_percent=trend_direction_gap_threshold_percent,
    )

    trend_gap_percent = trend_gap_percent_value(
        ema_fast=ema_fast,
        ema_slow=ema_slow,
    )

    trend_strength = classify_trend_strength(
        trend_gap_percent=trend_gap_percent,
        weak_threshold_percent=weak_trend_gap_threshold_percent,
        strong_threshold_percent=strong_trend_gap_threshold_percent,
    )

    trend_consistency_value = trend_consistency(
        closes=closes,
        trend=trend,
        trend_consistency_window=service._trend_consistency_window,
    )

    trend_efficiency_value = trend_efficiency(
        closes=closes,
        trend_consistency_window=service._trend_consistency_window,
    )

    ema_distance_atr_ratio_value = calculate_ema_distance_atr_ratio(
        ema_fast=ema_fast,
        ema_slow=ema_slow,
        atr_value=atr_value,
    )

    candle_noise_score = calculate_candle_noise_score(
        candles,
        candle_noise_window=service._candle_noise_window,
        min_clean_body_ratio=service._min_clean_body_ratio,
    )

    price_position_score = calculate_price_position_score(
        closes=closes,
        ema_fast=ema_fast,
        trend=trend,
        price_position_window=service._price_position_window,
    )

    trend_quality = classify_trend_quality(
        trend_consistency=trend_consistency_value,
        trend_efficiency=trend_efficiency_value,
        ema_distance_atr_ratio=ema_distance_atr_ratio_value,
        candle_noise_score=candle_noise_score,
        price_position_score=price_position_score,
        trend_strength=trend_strength,
        min_clean_candle_score=service._min_clean_candle_score,
        min_price_position_score=service._min_price_position_score,
    )

    market_phase = _htf_market_phase(
        trend=trend,
        trend_strength=trend_strength,
        trend_quality=trend_quality,
    )

    market_state = _htf_market_state(
        trend=trend,
        trend_strength=trend_strength,
        trend_quality=trend_quality,
        market_phase=market_phase,
    )

    alignment = _htf_alignment(
        local_state=local_state,
        htf_state=market_state,
        local_trend=local_trend,
        htf_trend=trend,
    )

    confirmation_score = _htf_confirmation_score(
        alignment=alignment,
        trend_strength=trend_strength,
        trend_quality=trend_quality,
        trend_consistency=trend_consistency_value,
        trend_efficiency=trend_efficiency_value,
    )

    return {
        "htf_market_state": market_state.value,
        "htf_trend": trend.value,
        "htf_trend_strength": trend_strength.value,
        "htf_trend_quality": trend_quality.value,
        "htf_market_phase": market_phase.value,
        "htf_alignment": alignment,
        "htf_confirmation_score": round(confirmation_score, 3),
        "htf_reason": (
            f"HTF_{service._htf_interval}:"
            f"{market_state.value}:"
            f"{trend.value}:"
            f"{alignment}"
        ),
    }


def safe_market_state(value: object) -> MarketState | None:
    try:
        return MarketState(str(value))
    except Exception:
        return None


def safe_trend_direction(value: object) -> TrendDirection | None:
    try:
        return TrendDirection(str(value))
    except Exception:
        return None


def safe_trend_strength(value: object) -> TrendStrength | None:
    try:
        return TrendStrength(str(value))
    except Exception:
        return None


def safe_trend_quality(value: object) -> TrendQuality | None:
    try:
        return TrendQuality(str(value))
    except Exception:
        return None


def safe_market_phase(value: object) -> MarketPhase | None:
    try:
        return MarketPhase(str(value))
    except Exception:
        return None


def safe_volatility_state(value: object) -> VolatilityState | None:
    try:
        return VolatilityState(str(value))
    except Exception:
        return None


def _htf_unknown_context(reason: str) -> JsonDict:
    return {
        "htf_market_state": MarketState.UNKNOWN.value,
        "htf_trend": TrendDirection.UNKNOWN.value,
        "htf_trend_strength": TrendStrength.UNKNOWN.value,
        "htf_trend_quality": TrendQuality.UNKNOWN.value,
        "htf_market_phase": MarketPhase.UNKNOWN.value,
        "htf_alignment": "UNKNOWN",
        "htf_confirmation_score": None,
        "htf_reason": reason,
    }


def _htf_market_phase(
    *,
    trend: TrendDirection,
    trend_strength: TrendStrength,
    trend_quality: TrendQuality,
) -> MarketPhase:
    if trend in {TrendDirection.UNKNOWN, TrendDirection.FLAT}:
        return MarketPhase.RANGE

    if trend_strength == TrendStrength.WEAK:
        return MarketPhase.RANGE

    if trend_quality == TrendQuality.NOISY:
        return MarketPhase.RANGE

    return MarketPhase.IMPULSE


def _htf_market_state(
    *,
    trend: TrendDirection,
    trend_strength: TrendStrength,
    trend_quality: TrendQuality,
    market_phase: MarketPhase,
) -> MarketState:
    if trend == TrendDirection.UP:
        return MarketState.TREND_UP

    if trend == TrendDirection.DOWN:
        return MarketState.TREND_DOWN

    if trend == TrendDirection.FLAT:
        return MarketState.RANGE

    return MarketState.UNKNOWN


def _htf_alignment(
    *,
    local_state: MarketState,
    htf_state: MarketState,
    local_trend: TrendDirection,
    htf_trend: TrendDirection,
) -> str:
    if htf_trend == TrendDirection.UNKNOWN:
        return "UNKNOWN"

    if htf_trend == TrendDirection.FLAT:
        return "NEUTRAL"

    if local_trend == TrendDirection.UP:
        return "ALIGNED" if htf_trend == TrendDirection.UP else "AGAINST"

    if local_trend == TrendDirection.DOWN:
        return "ALIGNED" if htf_trend == TrendDirection.DOWN else "AGAINST"

    if local_state == MarketState.TREND_UP:
        return "ALIGNED" if htf_trend == TrendDirection.UP else "AGAINST"

    if local_state == MarketState.TREND_DOWN:
        return "ALIGNED" if htf_trend == TrendDirection.DOWN else "AGAINST"

    return "NEUTRAL"


def _htf_confirmation_score(
    *,
    alignment: str,
    trend_strength: TrendStrength,
    trend_quality: TrendQuality,
    trend_consistency: float | None,
    trend_efficiency: float | None,
) -> float:
    if alignment == "AGAINST":
        return 0.0

    if alignment == "UNKNOWN":
        return 0.5

    if alignment == "NEUTRAL":
        return 0.55

    score = 0.65

    if trend_strength == TrendStrength.STRONG:
        score += 0.15
    elif trend_strength == TrendStrength.WEAK:
        score -= 0.2

    if trend_quality == TrendQuality.CLEAN:
        score += 0.1
    elif trend_quality == TrendQuality.NOISY:
        score -= 0.2

    if trend_consistency is not None:
        score += (trend_consistency - 0.5) * 0.2

    if trend_efficiency is not None:
        score += (trend_efficiency - 0.3) * 0.15

    return max(0.0, min(1.0, score))
