"""Orderflow trigger-state features for the LLM.

These features turn raw candles/orderflow into explicit playbook booleans.
The LLM should cite one true trigger state before entering.
"""
from __future__ import annotations

from statistics import median
from typing import Any

from .models import Candle, MarketRegime, OrderflowSnapshot
from .policy import TradingPolicy


def build_trigger_features(
    *,
    of: OrderflowSnapshot,
    regime: MarketRegime,
    candles_15m: list[Candle],
    candles_1h: list[Candle],
    ws_summary: dict[str, Any] | None = None,
    policy: TradingPolicy | None = None,
) -> dict[str, Any]:
    """Build levels, trigger_state, and crowding_state for one coin."""
    ws = ws_summary or {}
    policy = policy or TradingPolicy()
    levels = _levels(of, candles_15m, candles_1h)
    trigger_state, trigger_meta = _trigger_state(of, regime, candles_15m, levels, ws, policy)
    crowding_state = _crowding_state(of, candles_15m, ws)
    return {
        "levels": levels,
        "trigger_state": trigger_state,
        "trigger_meta": trigger_meta,
        "crowding_state": crowding_state,
    }


def _levels(of: OrderflowSnapshot, candles_15m: list[Candle], candles_1h: list[Candle]) -> dict[str, float]:
    prev_15m = candles_15m[-2] if len(candles_15m) >= 2 else None
    prev_1h = candles_1h[-2] if len(candles_1h) >= 2 else None
    swings = _recent_swings(candles_15m[:-1] if len(candles_15m) > 1 else candles_15m)
    return {
        "prior_15m_high": _round_px(prev_15m.high if prev_15m else 0.0),
        "prior_15m_low": _round_px(prev_15m.low if prev_15m else 0.0),
        "prior_1h_high": _round_px(prev_1h.high if prev_1h else 0.0),
        "prior_1h_low": _round_px(prev_1h.low if prev_1h else 0.0),
        "last_swing_high": _round_px(swings["high"]),
        "last_swing_low": _round_px(swings["low"]),
        "vwap": _round_px(of.vwap),
        "vpoc": _round_px(of.vpoc),
        "vah": _round_px(of.vah),
        "val": _round_px(of.val),
    }


def _trigger_state(
    of: OrderflowSnapshot,
    regime: MarketRegime,
    candles_15m: list[Candle],
    levels: dict[str, float],
    ws: dict[str, Any],
    policy: TradingPolicy,
) -> tuple[dict[str, bool], dict[str, Any]]:
    last = candles_15m[-1] if candles_15m else None
    prev = candles_15m[-2] if len(candles_15m) >= 2 else None
    if not last:
        return _empty_triggers(), {"reason": "no candles"}

    tolerance = _level_tolerance(of, candles_15m)
    sweep_tolerance = tolerance * policy.trigger_tolerance_factor
    close = last.close
    opened = last.open
    ws_delta = _ws_delta(ws)
    bullish_response = close > opened or ws_delta > policy.trigger_response_delta_threshold
    bearish_response = close < opened or ws_delta < -policy.trigger_response_delta_threshold
    up_context = regime.trend_15m == "up" or regime.trend_1h == "up"
    down_context = regime.trend_15m == "down" or regime.trend_1h == "down"

    prior_low = levels.get("prior_15m_low", 0.0)
    prior_high = levels.get("prior_15m_high", 0.0)
    swing_low = levels.get("last_swing_low", 0.0)
    swing_high = levels.get("last_swing_high", 0.0)
    supports = [levels.get(k, 0.0) for k in ("vwap", "vpoc", "val", "prior_15m_high")]
    resistances = [levels.get(k, 0.0) for k in ("vwap", "vpoc", "vah", "prior_15m_low")]
    support_response = any(_near(last.low, level, tolerance) and close >= level for level in supports if level)
    resistance_response = any(_near(last.high, level, tolerance) and close <= level for level in resistances if level)
    cvd_divergence_2m = str(ws.get("cvd_divergence_2m") or "none")
    cvd_divergence_5m = str(ws.get("cvd_divergence_5m") or "none")
    crowding_label = str(ws.get("crowding_state") or "").lower()

    sweep_reclaim_long = bool(
        prior_low
        and last.low < prior_low - sweep_tolerance
        and close > prior_low
        and bullish_response
    )
    sweep_reclaim_short = bool(
        prior_high
        and last.high > prior_high + sweep_tolerance
        and close < prior_high
        and bearish_response
    )

    pullback_retest_long = bool(
        up_context
        and bullish_response
        and support_response
    )
    pullback_retest_short = bool(
        down_context
        and bearish_response
        and resistance_response
    )

    breakout_retest_long = bool(last.low <= prior_high + tolerance) if prior_high else False
    breakdown_retest_short = bool(last.high >= prior_low - tolerance) if prior_low else False
    fresh_long_initiation = bool(
        ws_delta > policy.fresh_initiation_delta_threshold
        and of.book_imbalance > policy.fresh_initiation_book_threshold
        and of.ask_absorption < policy.absorption_warning_threshold
        and str(ws.get("book_velocity") or "") in {"bullish_liquidity_shift", "stable"}
    )
    fresh_short_initiation = bool(
        ws_delta < -policy.fresh_initiation_delta_threshold
        and of.book_imbalance < -policy.fresh_initiation_book_threshold
        and of.bid_absorption < policy.absorption_warning_threshold
        and str(ws.get("book_velocity") or "") in {"bearish_liquidity_shift", "stable"}
    )

    breakout_acceptance_long = bool(
        prior_high
        and close > prior_high + sweep_tolerance
        and bullish_response
        and (breakout_retest_long or fresh_long_initiation)
    )
    breakdown_acceptance_short = bool(
        prior_low
        and close < prior_low - sweep_tolerance
        and bearish_response
        and (breakdown_retest_short or fresh_short_initiation)
    )

    absorption_reversal_long = bool(
        of.bid_absorption >= policy.reversal_absorption_threshold
        and bullish_response
        and (not swing_low or close > swing_low)
    )
    absorption_reversal_short = bool(
        of.ask_absorption >= policy.reversal_absorption_threshold
        and bearish_response
        and (not swing_high or close < swing_high)
    )
    cvd_divergence_long = bool(
        "bullish" in (cvd_divergence_2m, cvd_divergence_5m)
        and bullish_response
        and support_response
    )
    cvd_divergence_short = bool(
        "bearish" in (cvd_divergence_2m, cvd_divergence_5m)
        and bearish_response
        and resistance_response
    )
    crowding_fade_long = bool(
        crowding_label in {"crowded_shorts", "long_deleveraging"}
        and bullish_response
        and (support_response or of.bid_absorption >= policy.reversal_absorption_threshold)
    )
    crowding_fade_short = bool(
        crowding_label in {"crowded_longs", "short_covering"}
        and bearish_response
        and (resistance_response or of.ask_absorption >= policy.reversal_absorption_threshold)
    )

    triggers = {
        "sweep_reclaim_long": sweep_reclaim_long,
        "sweep_reclaim_short": sweep_reclaim_short,
        "pullback_retest_long": pullback_retest_long,
        "pullback_retest_short": pullback_retest_short,
        "breakout_acceptance_long": breakout_acceptance_long,
        "breakdown_acceptance_short": breakdown_acceptance_short,
        "absorption_reversal_long": absorption_reversal_long,
        "absorption_reversal_short": absorption_reversal_short,
        "cvd_divergence_long": cvd_divergence_long,
        "cvd_divergence_short": cvd_divergence_short,
        "crowding_fade_long": crowding_fade_long,
        "crowding_fade_short": crowding_fade_short,
    }
    meta = {
        "level_tolerance": _round_px(tolerance),
        "sweep_tolerance": _round_px(sweep_tolerance),
        "trigger_tolerance_factor": policy.trigger_tolerance_factor,
        "ws_delta": round(ws_delta, 4),
        "bullish_response": bullish_response,
        "bearish_response": bearish_response,
        "support_response": support_response,
        "resistance_response": resistance_response,
    }
    return triggers, meta


def _crowding_state(of: OrderflowSnapshot, candles_15m: list[Candle], ws: dict[str, Any]) -> dict[str, Any]:
    oi_delta = float(ws.get("oi_delta_60s") or 0.0)
    funding = float(ws.get("funding_ws") or of.funding_rate or 0.0)
    premium = float(ws.get("premium_ws") or 0.0)
    last = candles_15m[-1] if candles_15m else None
    prev = candles_15m[-2] if len(candles_15m) >= 2 else None
    price_change = (last.close - prev.close) / prev.close if last and prev and prev.close else 0.0

    label = str(ws.get("crowding_state") or "neutral")
    if abs(oi_delta) > 0.001:
        if price_change > 0 and oi_delta > 0:
            label = "new_longs"
        elif price_change < 0 and oi_delta > 0:
            label = "new_shorts"
        elif price_change > 0 and oi_delta < 0:
            label = "short_covering"
        elif price_change < 0 and oi_delta < 0:
            label = "long_deleveraging"

    return {
        "oi_60s_delta": round(oi_delta, 4),
        "funding": round(funding, 8),
        "premium": round(premium, 8),
        "price_change_15m": round(price_change, 5),
        "label": label,
    }


def _recent_swings(candles: list[Candle]) -> dict[str, float]:
    recent = candles[-12:] if candles else []
    if not recent:
        return {"high": 0.0, "low": 0.0}
    return {
        "high": max(c.high for c in recent),
        "low": min(c.low for c in recent),
    }


def _level_tolerance(of: OrderflowSnapshot, candles: list[Candle]) -> float:
    recent_ranges = [c.range for c in candles[-12:] if c.range > 0]
    med_range = median(recent_ranges) if recent_ranges else 0.0
    candidates = [x for x in (of.atr_14 * 0.25, med_range * 0.25) if x > 0]
    if candidates:
        return max(candidates)
    return of.mid * 0.001 if of.mid else 0.0


def _empty_triggers() -> dict[str, bool]:
    return {
        "sweep_reclaim_long": False,
        "sweep_reclaim_short": False,
        "pullback_retest_long": False,
        "pullback_retest_short": False,
        "breakout_acceptance_long": False,
        "breakdown_acceptance_short": False,
        "absorption_reversal_long": False,
        "absorption_reversal_short": False,
        "cvd_divergence_long": False,
        "cvd_divergence_short": False,
        "crowding_fade_long": False,
        "crowding_fade_short": False,
    }


def _near(value: float, level: float, tolerance: float) -> bool:
    return abs(value - level) <= tolerance if level and tolerance else False


def _ws_delta(ws: dict[str, Any]) -> float:
    if ws.get("available") is not True:
        return 0.0
    if float(ws.get("tape_stale_s") or 9999.0) > 20.0:
        return 0.0
    return float(ws.get("delta_ratio_30s") or 0.0)


def _round_px(value: float) -> float:
    return float(f"{float(value or 0.0):.8g}")
