from __future__ import annotations

from dataclasses import replace
from decimal import Decimal, ROUND_DOWN
from typing import Any

from src.execution.order_intent import OrderIntent
from src.market.trend_retest_features import TrendRetestFeatures
from src.research.tradingview_context import TradingViewContext, tradingview_blockers


LONG_STRATEGY_ID = "candidate_v77_trend_retest_anti_chase_long"
SHORT_RESEARCH_ID = "research_v77_bear_trend_retest_short"
STRATEGY_VERSION = "v77.1.0"
PRIMARY_COINS = {"BTC", "ETH", "SOL", "LINK"}


def with_cross_section(
    feature: TrendRetestFeatures,
    *,
    strength_rank: int,
    weakness_rank: int,
    breadth_positive_pct: Decimal,
) -> TrendRetestFeatures:
    return replace(
        feature,
        strength_rank=strength_rank,
        weakness_rank=weakness_rank,
        breadth_positive_pct=breadth_positive_pct,
    )


def _common_blockers(feature: TrendRetestFeatures, *, side: str, tv: TradingViewContext | None) -> list[str]:
    blockers: list[str] = []
    if feature.coin not in PRIMARY_COINS:
        blockers.append("coin_not_primary")
    if not feature.data_quality_allowed:
        blockers.extend(feature.data_quality_reasons or ("data_quality",))
    if feature.volume_24h < Decimal("25000000"):
        blockers.append("weak_liquidity")
    if feature.spread_pct > Decimal("0.08"):
        blockers.append("spread_too_wide")
    impact = feature.buy_impact_1k_pct if side == "long" else feature.sell_impact_1k_pct
    if impact > Decimal("0.08"):
        blockers.append("impact_slippage_too_high")
    if feature.volume_ratio < Decimal("1.20"):
        blockers.append("volume_expansion_missing")
    blockers.extend(tradingview_blockers(tv, side=side, max_age_seconds=3600))
    return blockers


def entry_blockers(feature: TrendRetestFeatures, *, side: str, tv: TradingViewContext | None = None) -> list[str]:
    side = side.lower()
    if side not in {"long", "short"}:
        raise ValueError("side must be long or short")
    blockers = _common_blockers(feature, side=side, tv=tv)
    tolerance = Decimal("0.003")
    if side == "long":
        if feature.sma_1h_fast <= feature.sma_1h_slow or feature.close_15m <= feature.sma_1h_fast:
            blockers.append("trend_1h_not_bullish")
        if feature.sma_4h_fast <= feature.sma_4h_slow:
            blockers.append("trend_4h_not_bullish")
        if feature.breadth_positive_pct < Decimal("50"):
            blockers.append("market_breadth_not_supportive")
        if feature.strength_rank > 3:
            blockers.append("weak_relative_strength")
        overextended = feature.move_15m_pct > Decimal("1.20") or feature.move_1h_pct > Decimal("3.00")
        if overextended and feature.pullback_from_high_pct < Decimal("0.45"):
            blockers.append("anti_chase_overextended_no_retest")
        touched_retest = feature.low_15m <= feature.sma_1h_fast * (Decimal("1") + tolerance)
        reclaimed = feature.close_15m >= feature.sma_1h_fast and feature.close_15m > feature.open_15m
        if not touched_retest or not reclaimed or feature.pullback_from_high_pct < Decimal("0.45"):
            blockers.append("no_confirmed_close_reclaim")
        if feature.rsi_15m > Decimal("70") and feature.volume_ratio < Decimal("1.60"):
            blockers.append("overbought_without_strong_volume_retest")
    else:
        if feature.sma_1h_fast >= feature.sma_1h_slow or feature.close_15m >= feature.sma_1h_fast:
            blockers.append("trend_1h_not_bearish")
        if feature.sma_4h_fast >= feature.sma_4h_slow:
            blockers.append("trend_4h_not_bearish")
        if feature.breadth_positive_pct > Decimal("50"):
            blockers.append("market_breadth_not_bearish")
        if feature.weakness_rank > 3:
            blockers.append("weak_relative_weakness")
        overextended = feature.move_15m_pct < Decimal("-1.20") or feature.move_1h_pct < Decimal("-3.00")
        if overextended and feature.rebound_from_low_pct < Decimal("0.45"):
            blockers.append("anti_chase_short_overextended_no_retest")
        touched_retest = feature.high_15m >= feature.sma_1h_fast * (Decimal("1") - tolerance)
        rejected = feature.close_15m <= feature.sma_1h_fast and feature.close_15m < feature.open_15m
        if not touched_retest or not rejected or feature.rebound_from_low_pct < Decimal("0.45"):
            blockers.append("no_confirmed_close_rejection")
        if feature.rsi_15m < Decimal("30") and feature.volume_ratio < Decimal("1.60"):
            blockers.append("oversold_without_strong_volume_retest")
    return list(dict.fromkeys(blockers))


def build_intent(feature: TrendRetestFeatures, *, side: str, client_order_id: str) -> OrderIntent | None:
    side = side.lower()
    if side not in {"long", "short"}:
        raise ValueError("side must be long or short")
    price = feature.current_price
    stop_distance_pct = max(Decimal("0.80"), feature.atr_pct * Decimal("0.85"))
    risk_usd = Decimal("75") * Decimal("0.0015")
    notional = min(Decimal("15"), risk_usd / (stop_distance_pct / Decimal("100")))
    if notional < Decimal("10") or price <= 0:
        return None
    size = (notional / price).quantize(Decimal("0.00000001"), rounding=ROUND_DOWN)
    if side == "long":
        stop = min(feature.recent_low * Decimal("0.9975"), price * (Decimal("1") - stop_distance_pct / Decimal("100")))
        tp1 = price + (price - stop) * Decimal("1.20")
        order_side = "buy"
        strategy_id = LONG_STRATEGY_ID
        reason = "confirmed_trend_retest_anti_chase_long"
    else:
        stop = max(feature.recent_high * Decimal("1.0025"), price * (Decimal("1") + stop_distance_pct / Decimal("100")))
        tp1 = price - (stop - price) * Decimal("1.20")
        order_side = "sell"
        strategy_id = SHORT_RESEARCH_ID
        reason = "confirmed_bear_trend_retest_short_research"
    return OrderIntent(
        strategy_id=strategy_id,
        symbol=f"{feature.coin}/USDC:USDC",
        coin=feature.coin,
        side=order_side,
        reduce_only=False,
        order_type="market",
        tif="Ioc",
        size=size,
        price=None,
        trigger_price=None,
        stop_loss=stop.quantize(Decimal("0.00000001"), rounding=ROUND_DOWN),
        take_profit=tp1.quantize(Decimal("0.00000001"), rounding=ROUND_DOWN),
        client_order_id=client_order_id,
        reason=reason,
        risk_usd=risk_usd,
        estimated_notional_usd=notional,
    )


def feature_journal(feature: TrendRetestFeatures) -> dict[str, Any]:
    return {
        "data_source": "hyperliquid_live_readonly_ohlcv_l2",
        "proxy_inputs_used": False,
        "data_window_id": feature.data_window_id,
        "candle_ts": feature.candle_ts,
        "move_15m_pct": str(feature.move_15m_pct),
        "move_1h_pct": str(feature.move_1h_pct),
        "sma_1h_fast": str(feature.sma_1h_fast),
        "sma_1h_slow": str(feature.sma_1h_slow),
        "sma_4h_fast": str(feature.sma_4h_fast),
        "sma_4h_slow": str(feature.sma_4h_slow),
        "atr_pct": str(feature.atr_pct),
        "rsi_15m": str(feature.rsi_15m),
        "volume_24h": str(feature.volume_24h),
        "volume_ratio": str(feature.volume_ratio),
        "pullback_from_high_pct": str(feature.pullback_from_high_pct),
        "rebound_from_low_pct": str(feature.rebound_from_low_pct),
        "spread_pct": str(feature.spread_pct),
        "buy_impact_1k_pct": str(feature.buy_impact_1k_pct),
        "sell_impact_1k_pct": str(feature.sell_impact_1k_pct),
        "strength_rank": feature.strength_rank,
        "weakness_rank": feature.weakness_rank,
        "breadth_positive_pct": str(feature.breadth_positive_pct),
        "continuation_closes_confirmed": feature.continuation_closes_confirmed,
        "data_quality_allowed": feature.data_quality_allowed,
        "data_quality_reasons": list(feature.data_quality_reasons),
    }
