"""Shared helpers for stock-only live monitoring vs IBKR positions."""

from __future__ import annotations

from typing import Any

from RenTech.strategy_stack.combine_best_ideas_stack import FUND_WEIGHT_TABLE_STOCK_VOL_EDGE_MA_SLOPE

STOCK_ONLY_WEIGHTS: dict[str, float] = dict(FUND_WEIGHT_TABLE_STOCK_VOL_EDGE_MA_SLOPE)

# Live / Today Trades book we actually ran with the maintainer ($50k × 1.5):
# core ETF sleeves + CM dips. Excludes Johansen, MA slope top-N/intraday, Ride Rockets.
TODAY_TRADES_CORE_SLEEVES: frozenset[str] = frozenset(
    {
        "tactical_aw",
        "equity_dip",
        "vol_edge",
        "tsmom",
        "ma_slope_inverse",
        "qs_actionable_etf",
    }
)


def today_trades_core_weights() -> dict[str, float]:
    """
    Core live-book weights at the same absolute levels as the full stock-only
    table after renormalization (so VOO/SH/SVIX notionals match chat tickets).
    Do not renormalize again after this subset.
    """
    full = {k: float(v) for k, v in STOCK_ONLY_WEIGHTS.items() if float(v) > 0}
    wsum = sum(full.values())
    renorm = {k: v / wsum for k, v in full.items()} if wsum > 0 else full
    return {
        k: renorm[k]
        for k in TODAY_TRADES_CORE_SLEEVES
        if k in renorm and renorm[k] > 0
    }


STOCK_SLEEVE_LABELS: dict[str, str] = {
    "tactical_aw": "Tactical All Weather",
    "equity_dip": "Equity Dip (CM SP100)",
    "vol_edge": "Volatility Edge ETN",
    "qs_actionable_etf": "QS Actionable-4 (SPY)",
    "ma_slope_topn": "MA Slope Top-10",
    "johansen_etf": "Johansen ETF Triplets",
    "tsmom": "TSMOM / Managed Futures",
    "ma_slope_inverse": "MA Slope Inverse (SH)",
    "ride_rockets": "Ride Rockets 50/50",
    "ma_slope_intraday": "MA Slope Intraday",
}

# Plain-English blurbs for the live Today Trades core book.
STOCK_SLEEVE_BLURBS: dict[str, str] = {
    "tactical_aw": (
        "Macro ETF rotation (VOO/TLT/IEF/GLD/DBC). Each sleeve only stays on if price "
        "is above its SMA and has positive momentum; otherwise that sleeve sits in cash."
    ),
    "equity_dip": (
        "CrackingMarkets-style SP100 dips: after a ≥3% down day with SMA200 up and high ATR%, "
        "place a DAY limit at close − 0.9×ATR. Exit on profit (+0.5×ATR), close above prior high, "
        "or after >10 trading days. No hard stop. Live sizing ≈ sleeve ÷ 10 names."
    ),
    "vol_edge": (
        "Volatility risk-premium sleeve in short-vol ETNs (usually SVIX). Sized from VIX vs "
        "realized vol / term structure; can flip toward long-vol (VIXY) when the edge flips."
    ),
    "tsmom": (
        "Time-series momentum / managed futures across SPY, EFA, EEM, TLT, IEF, GLD, DBC, UUP. "
        "Blends 3/6/12-month trends, vol-scales each leg, and can go long or short. Low equity beta."
    ),
    "ma_slope_inverse": (
        "Crash hedge: buy SH (inverse S&P) when SPY looks bearish on dual momentum / SMA200 "
        "regime rules. Flat (cash) when the regime is clear."
    ),
    "qs_actionable_etf": (
        "Four QuantifiedStrategies-style SPY calendar/overnight rules (equal-weight when active): "
        "Turnaround Tuesday, overnight after 3 down days, first day of the month, overnight at a "
        "10-day low. When on, it adds long VOO (our SPY proxy); when off, cash. PnL is folded into "
        "VOO/Tactical marks — it has no separate ticker."
    ),
}


def today_trades_strategy_guide(budgets: dict[str, float] | None = None) -> list[dict[str, Any]]:
    """Ordered sleeve definitions for the Today Trades UI."""
    budgets = budgets or {}
    order = (
        "tactical_aw",
        "equity_dip",
        "vol_edge",
        "tsmom",
        "ma_slope_inverse",
        "qs_actionable_etf",
    )
    out: list[dict[str, Any]] = []
    for key in order:
        if key not in TODAY_TRADES_CORE_SLEEVES:
            continue
        row: dict[str, Any] = {
            "sleeve": key,
            "label": STOCK_SLEEVE_LABELS.get(key, key),
            "blurb": STOCK_SLEEVE_BLURBS.get(key, ""),
        }
        if key in budgets:
            row["budget_usd"] = round(float(budgets[key]), 2)
        w = today_trades_core_weights().get(key)
        if w is not None:
            row["weight"] = round(float(w), 4)
        out.append(row)
    return out

MACRO_TICKERS = ("SPY", "TLT", "IEF", "GLD", "DBC")
TSMOM_TICKERS = ("SPY", "EFA", "EEM", "TLT", "IEF", "GLD", "DBC", "UUP")
VOL_EDGE_TICKERS = ("SVIX", "VIXY", "SVXY")
MA_INVERSE_TICKERS = ("SH",)

# ETF / sleeve symbols that are NOT CrackingMarkets dip names (for open-count).
CORE_BOOK_ETFS: frozenset[str] = frozenset(
    {
        *MACRO_TICKERS,
        *TSMOM_TICKERS,
        *VOL_EDGE_TICKERS,
        *MA_INVERSE_TICKERS,
        "VOO",  # live SPY proxy
        "IVV",
        "QQQ",
        "IWM",
        "DIA",
    }
)

DEFAULT_DIP_MAX_HOLDINGS = 10


def dip_open_symbols_from_ib_mv(ib_mv: dict[str, float]) -> set[str]:
    """Long stock names that look like dip holdings (exclude core ETFs)."""
    out: set[str] = set()
    for sym, mv in ib_mv.items():
        s = str(sym).upper()
        if s in CORE_BOOK_ETFS:
            continue
        if float(mv) > 50.0:
            out.add(s)
    return out


def dip_per_name_budget_usd(
    fund_nav: float,
    fund_weight: float,
    fund_scale: float,
    *,
    max_holdings: int = DEFAULT_DIP_MAX_HOLDINGS,
) -> float:
    sleeve = sleeve_budget_usd(fund_nav, fund_weight, fund_scale)
    return sleeve / max(int(max_holdings), 1)

JOHANSEN_TRIPLETS: list[tuple[tuple[str, str, str], str]] = [
    (("GDXJ", "IAU", "SIL"), "precious_metals_junior"),
    (("GLD", "UNG", "USO"), "commodity_gold_gas_oil"),
    (("XLB", "XLI", "XLP"), "sector_cyclical_defensive"),
    (("COP", "USO", "XOP"), "energy_complex"),
    (("DBC", "PDBC", "USO"), "commodity_broad"),
    (("EWA", "EWC", "IGE"), "chan_classic"),
]


def sleeve_budget_usd(fund_nav: float, fund_weight: float, fund_scale: float) -> float:
    return float(fund_nav) * float(fund_weight) * float(fund_scale)


def compare_sleeve_targets(
    *,
    book: str,
    targets: dict[str, float],
    ib_mv: dict[str, float],
    fund_nav: float,
    fund_weight: float,
    fund_scale: float = 1.5,
    meta: dict[str, Any] | None = None,
    allow_short: bool = False,
) -> list[dict[str, Any]]:
    """
    Compare model targets to IB stock market values.

    *targets* are fractions of sleeve capital (negative = short when allow_short).
    """
    meta = meta or {}
    actions: list[dict[str, Any]] = []
    sleeve_nav = sleeve_budget_usd(fund_nav, fund_weight, fund_scale)
    universe = set(targets.keys())
    held = {s: mv for s, mv in ib_mv.items() if abs(mv) > 50}

    for sym, w in sorted(targets.items(), key=lambda x: -abs(x[1])):
        if abs(w) < 1e-6:
            continue
        target_usd = sleeve_nav * abs(w)
        have = held.get(sym, 0.0)
        if w > 0:
            if have < target_usd * 0.5:
                act = "ADD"
                pri = 20
            else:
                act = "HOLD"
                pri = 80
            summary = f"{'Increase' if act == 'ADD' else 'Hold'} long {sym} toward {w:.1%} of sleeve"
        elif allow_short:
            want_mv = -target_usd
            if have > want_mv * 0.5:
                act = "ADD"
                pri = 25
                summary = f"Increase short {sym} toward {abs(w):.1%} of sleeve"
            else:
                act = "HOLD"
                pri = 80
                summary = f"Hold short {sym} (~{abs(w):.1%} of sleeve)"
        else:
            continue
        actions.append(
            {
                "priority": pri,
                "action": act,
                "book": book,
                "symbol": sym,
                "summary": summary,
                "detail": {
                    "target_weight": w,
                    "target_notional_usd": round(target_usd if w > 0 else -target_usd, 2),
                    "ib_market_value_usd": round(have, 2),
                    "sleeve_budget_usd": round(sleeve_nav, 2),
                    **meta,
                },
            }
        )

    book_etfs = set(MACRO_TICKERS) | set(TSMOM_TICKERS) | set(VOL_EDGE_TICKERS) | set(MA_INVERSE_TICKERS)
    for sym, mv in held.items():
        if sym in universe:
            continue
        if sym in book_etfs or book in ("ma_slope_topn", "johansen_etf", "equity_dip"):
            actions.append(
                {
                    "priority": 10,
                    "action": "REMOVE",
                    "book": book,
                    "symbol": sym,
                    "summary": f"Flat {sym} — not in current {book} model",
                    "detail": {"ib_market_value_usd": round(mv, 2), **meta},
                }
            )
    return actions
