"""Historical daily bars from IBKR for live sleeve signal generation."""

from __future__ import annotations

from typing import Any

import pandas as pd

from RenTech.strategy_stack.main import _compute_daily_backtest_features


async def fetch_daily_bars(
    ib: Any,
    symbol: str,
    *,
    duration_str: str = "2 Y",
    exchange: str = "SMART",
    currency: str = "USD",
) -> pd.DataFrame:
    """
    Return panel with ``close``, ``ret``, ``sma_200``, ``aqr_mom`` (same as backtest features).
    """
    from ib_insync import Stock

    contract = Stock(str(symbol).upper(), str(exchange), str(currency))
    qualified = await ib.qualifyContractsAsync(contract)
    if not qualified:
        raise RuntimeError(f"IB could not qualify contract for {symbol!r}")

    bars = await ib.reqHistoricalDataAsync(
        contract,
        endDateTime="",
        durationStr=str(duration_str),
        barSizeSetting="1 day",
        whatToShow="TRADES",
        useRTH=True,
        formatDate=1,
    )
    if len(bars) < 200:
        bars = await ib.reqHistoricalDataAsync(
            contract,
            endDateTime="",
            durationStr="1 Y",
            barSizeSetting="1 day",
            whatToShow="TRADES",
            useRTH=True,
            formatDate=1,
        )
    if not bars:
        raise RuntimeError(f"No historical bars for {symbol!r}")

    df = pd.DataFrame(
        [
            {
                "date": pd.Timestamp(b.date),
                "close": float(b.close),
            }
            for b in bars
        ]
    )
    df = df.set_index("date").sort_index()
    return _compute_daily_backtest_features(df)


async def fetch_daily_panel(
    ib: Any,
    symbols: list[str],
    *,
    duration_str: str = "2 Y",
) -> dict[str, pd.DataFrame]:
    """Fetch each symbol sequentially (IB pacing-friendly)."""
    out: dict[str, pd.DataFrame] = {}
    errors: list[str] = []
    for sym in symbols:
        try:
            out[sym] = await fetch_daily_bars(ib, sym, duration_str=duration_str)
            print(f"  [IB bars] {sym}: {len(out[sym])} sessions", flush=True)
        except Exception as e:
            errors.append(f"{sym}: {e}")
            print(f"  [IB bars] {sym}: FAILED ({e})", flush=True)
    if errors and len(out) < max(1, len(symbols) // 2):
        raise RuntimeError("Too many IB bar failures: " + "; ".join(errors[:5]))
    return out
