"""Live pre-screening for ORB paper trading using Alpaca data.

Adapts the backtester's screening logic (libs/intraday/screener.py) for live use.
Key difference: we cannot use today's daily bar (open/high/low) before 9:30 ET,
so pre_screen uses only lookback enrichment features (ATR14, avg_dollar_vol, prev_close).
"""
from __future__ import annotations

import yaml

from libs.intraday.domain import ORBStrategyParams

# ── Universe YAML paths (mirrors screener.py _UNIVERSE_YAML_MAP) ──────────────

_UNIVERSE_YAML_MAP = {
    "broad": "configs/symbols_broad_snapshot_3408.yaml",
    "midlarge": "configs/symbols_midlarge_snapshot_exact.yaml",
    "largecap": "configs/symbols.yaml",
    "midcap": "configs/symbols_midcap.yaml",
}


def load_universe(source: str, symbols_file: str | None = None) -> list[str]:
    """Load ticker list from YAML universe.

    Supports: 'broad', 'midlarge', 'largecap', 'midcap', or 'yaml' (requires symbols_file).
    Returns sorted, deduplicated list of uppercase ticker symbols.
    """
    if source in _UNIVERSE_YAML_MAP:
        return _load_yaml_symbols(_UNIVERSE_YAML_MAP[source])
    if source == "yaml":
        if not symbols_file:
            raise ValueError("source='yaml' requires symbols_file")
        return _load_yaml_symbols(symbols_file)
    # For unsupported dynamic sources (sp500, nasdaq100, screener),
    # fall back to midlarge YAML to avoid Oracle/API dependency.
    return _load_yaml_symbols(_UNIVERSE_YAML_MAP["midlarge"])


def _load_yaml_symbols(path: str) -> list[str]:
    """Load ticker list from a YAML symbols file. Mirrors screener.py:_load_yaml_symbols."""
    with open(path) as f:
        data = yaml.safe_load(f)
    if isinstance(data, list):
        return sorted({str(s).upper() for s in data if s})
    if isinstance(data, dict):
        symbols: list[str] = []
        for key in ("symbols", "existing_only", "screener", "existing-only"):
            if key in data:
                val = data[key]
                if isinstance(val, list):
                    symbols.extend(str(s).upper() for s in val if s)
        if symbols:
            return sorted(set(symbols))
        return sorted({str(k).upper() for k in data.keys() if not k.startswith("_")})
    raise ValueError(f"Unexpected YAML format in {path}")


# ── Data format conversion ────────────────────────────────────────────────────


def bars_to_enrichment_format(
    bars: "dict[str, list]",
) -> dict[str, list[dict]]:
    """Convert AlpacaBroker.get_bars() Bar dataclass list to the dict format
    expected by enrich_daily_bars().

    Input:  {symbol: [Bar(date=str, open=float, ...), ...]}
    Output: {symbol: [{"date": str, "open": float, "high": float, ...}, ...]}
    """
    result: dict[str, list[dict]] = {}
    for sym, bar_list in bars.items():
        result[sym] = [
            {
                "date": b.date,
                "open": b.open,
                "high": b.high,
                "low": b.low,
                "close": b.close,
                "volume": b.volume,
            }
            for b in bar_list
        ]
    return result


def intraday_bars_to_format(
    raw: dict[str, list[dict]],
) -> dict[str, list[dict]]:
    """Ensure intraday bar dicts from AlpacaBroker.get_intraday_bars() are in the
    format expected by compute_orb_candidates().

    The method already returns dicts with 'timestamp', 'open', etc.
    This function is a pass-through that filters out tickers with no bars.
    """
    return {sym: bars for sym, bars in raw.items() if bars}


# ── Live pre-screening ────────────────────────────────────────────────────────


def live_pre_screen(
    enrichment: dict[str, dict[str, dict]],
    date_str: str,
    params: ORBStrategyParams,
) -> list[str]:
    """Pre-screen tickers using only lookback enrichment features (no today's daily bar).

    This is the live replacement for orb_pre_screen_candidates() which requires
    today's daily bar (available only after market open).

    Filters (applied to the most recent enrichment entry before date_str):
    - prev_close >= min_price       (proxy for current price)
    - atr_14 >= min_atr_14
    - atr_14/prev_close in [min_atr_pct, max_atr_pct]  (V23 quality filter)
    - avg_dollar_vol_30d >= min_avg_dollar_volume

    Returns list of qualifying tickers (unsorted).
    """
    candidates: list[str] = []

    for ticker, date_map in enrichment.items():
        if not date_map:
            continue

        # Get the most recent enrichment date at or before date_str
        relevant_dates = sorted(d for d in date_map if d <= date_str)
        if not relevant_dates:
            continue
        feats = date_map[relevant_dates[-1]]

        prev_close = feats.get("prev_close", 0.0) or 0.0
        atr_14 = feats.get("atr_14", 0.0) or 0.0
        avg_dollar_vol = feats.get("avg_dollar_vol_30d", 0.0) or 0.0

        if prev_close < params.min_price:
            continue
        if atr_14 < params.min_atr_14:
            continue
        if prev_close > 0:
            atr_ratio = atr_14 / prev_close
            if params.min_atr_pct is not None and atr_ratio < params.min_atr_pct:
                continue
            if params.max_atr_pct is not None and atr_ratio > params.max_atr_pct:
                continue
        if avg_dollar_vol < params.min_avg_dollar_volume:
            continue

        candidates.append(ticker)

    return candidates


def get_latest_enrichment(
    enrichment: dict[str, dict[str, dict]],
    date_str: str,
    ticker: str,
) -> dict | None:
    """Get the most recent enrichment entry for a ticker before date_str."""
    date_map = enrichment.get(ticker, {})
    relevant = sorted(d for d in date_map if d < date_str)
    if not relevant:
        return None
    return date_map[relevant[-1]]
