from __future__ import annotations

from dataclasses import dataclass
from decimal import Decimal, ROUND_DOWN

from src.execution.order_intent import OrderIntent

CORE = {"BTC", "ETH", "SOL", "LINK"}
RESEARCH_ALT = {"WLD", "SUI", "ENA", "BCH"}
ALLOWED = CORE | RESEARCH_ALT


@dataclass(frozen=True)
class MarketContext:
    coin: str
    symbol: str
    current_price: Decimal
    recent_high: Decimal
    sma_fast: Decimal
    sma_slow: Decimal
    volume_24h: Decimal
    breadth_positive_candidates: int
    relative_strength_rank: int
    baseline_price: Decimal
    wallet_equity_usdc: Decimal
    coin_leakage_blocked: bool = False
    risk_multiplier: Decimal = Decimal("1")
    data_quality_allowed: bool = True
    data_quality_reasons: tuple[str, ...] = ()
    strict_anti_chase: bool = False
    retest_low: Decimal | None = None
    atr_pct: Decimal = Decimal("1.20")
    move_15m_pct: Decimal = Decimal("0")
    move_1h_pct: Decimal = Decimal("0")
    pullback_from_high_pct: Decimal = Decimal("0")
    spread_pct: Decimal = Decimal("0.03")
    expected_slippage_pct: Decimal = Decimal("0.02")
    funding_rate_hourly_pct: Decimal = Decimal("0")
    vwap_distance_pct: Decimal = Decimal("0")
    upper_wick_pct: Decimal = Decimal("0")
    candle_body_pct: Decimal = Decimal("0")
    open_interest_change_pct: Decimal = Decimal("0")
    premium_pct: Decimal = Decimal("0")


def _size_for_notional(notional: Decimal, price: Decimal) -> Decimal:
    return (notional / price).quantize(Decimal("0.00000001"), rounding=ROUND_DOWN)


def strict_anti_chase_blockers(context: MarketContext) -> list[str]:
    """Conservative v76 strict entry timing guards.

    The original v76 strict Paper edge was inflated by immediate expected-move
    exits. Under the real lifecycle, late entries produce too many time exits
    and stop losses. These guards keep the strategy from buying local tops and
    require a basic retest/reclaim shape before opening a lifecycle position.
    """
    if not context.strict_anti_chase:
        return []
    blockers: list[str] = []
    if context.atr_pct <= 0:
        blockers.append("anti_chase_missing_atr")
    if context.spread_pct > Decimal("0.08"):
        blockers.append("anti_chase_spread_too_wide")
    if context.funding_rate_hourly_pct > Decimal("0.015"):
        blockers.append("anti_chase_crowded_positive_funding")
    if context.open_interest_change_pct > Decimal("12") and context.funding_rate_hourly_pct > Decimal("0.005"):
        blockers.append("anti_chase_oi_funding_crowded")
    if context.premium_pct > Decimal("0.10"):
        blockers.append("anti_chase_premium_too_high")

    if context.vwap_distance_pct > Decimal("1.20"):
        blockers.append("anti_chase_vwap_distance_too_high")
    if context.atr_pct > Decimal("0") and context.move_15m_pct > context.atr_pct * Decimal("2.50"):
        blockers.append("anti_chase_atr_pump")
    if context.upper_wick_pct > max(Decimal("0.60"), context.candle_body_pct * Decimal("1.50")):
        blockers.append("anti_chase_upper_wick_rejection")
    if context.move_1h_pct > context.atr_pct * Decimal("3.50") and context.upper_wick_pct > Decimal("0.35"):
        blockers.append("anti_chase_exhaustion_candle")

    overextended = context.move_15m_pct > context.atr_pct * Decimal("1.50") or context.move_1h_pct > context.atr_pct * Decimal("2.75")
    no_retest = context.pullback_from_high_pct < max(Decimal("0.35"), context.atr_pct * Decimal("0.30"))
    if overextended and no_retest:
        blockers.append("anti_chase_overextended_no_retest")

    retest_low = context.retest_low if context.retest_low is not None else context.sma_slow
    retest_held = context.current_price >= context.sma_fast >= context.sma_slow and context.current_price > retest_low
    not_chasing_high = context.current_price <= context.recent_high * Decimal("1.015")
    if context.current_price > context.recent_high and not no_retest:
        retest_held = context.sma_fast >= context.sma_slow and not_chasing_high
    if not retest_held:
        blockers.append("anti_chase_no_retest_reclaim")
    if not not_chasing_high and no_retest:
        blockers.append("anti_chase_buying_above_local_high")
    return blockers


def build_v76_order_intent(context: MarketContext, *, client_order_id: str) -> OrderIntent | None:
    coin = context.coin.upper()
    if coin not in ALLOWED:
        return None
    if context.coin_leakage_blocked:
        return None
    if not context.data_quality_allowed:
        return None
    if context.volume_24h < Decimal("12000000"):
        return None
    if context.breadth_positive_candidates < 3 or context.relative_strength_rank > 4:
        return None
    if strict_anti_chase_blockers(context):
        return None

    breakout = context.current_price > context.recent_high and context.sma_fast >= context.sma_slow
    survival_reclaim = context.current_price < context.baseline_price and context.current_price >= context.sma_fast
    if breakout:
        reason = "confirmed_squeeze_breakout"
        risk_pct = Decimal("0.25")
        stop_distance_pct = Decimal("1.25")
    elif survival_reclaim:
        reason = "survival_rebound_sma_reclaim"
        risk_pct = Decimal("0.125")
        stop_distance_pct = Decimal("1.0")
    else:
        return None

    risk_usd = context.wallet_equity_usdc * risk_pct / Decimal("100") * context.risk_multiplier
    notional = risk_usd / (stop_distance_pct / Decimal("100")) if risk_usd > 0 else Decimal("0")
    max_notional = context.wallet_equity_usdc * Decimal("20") / Decimal("100")
    notional = min(notional, max_notional)
    if notional < Decimal("10"):
        return None
    size = _size_for_notional(notional, context.current_price)
    stop_loss = context.current_price * (Decimal("1") - stop_distance_pct / Decimal("100"))
    return OrderIntent(
        strategy_id="candidate_v76_hl_confirmed_squeeze_hybrid",
        symbol=context.symbol,
        coin=coin,
        side="buy",
        reduce_only=False,
        order_type="market",
        tif="Ioc",
        size=size,
        price=None,
        trigger_price=None,
        stop_loss=stop_loss.quantize(Decimal("0.00000001"), rounding=ROUND_DOWN),
        take_profit=None,
        client_order_id=client_order_id,
        reason=reason,
        risk_usd=risk_usd,
        estimated_notional_usd=notional,
    )
