"""Ingest the Bybit long/short ACCOUNT ratio (retail positioning) into the Bybit-namespaced feature store.

Per symbol: fetch {epoch_day: buy_ratio} (the adapter; period=1d → one value/day), write feature
'long_ratio' at ts = epoch_day*86400 via the store's batched, idempotent upsert. Memory-bounded (results are
written as completed, never collected up front — ~one symbol's payload peak) and DATE-RESUMABLE (each symbol
resumes from its last persisted 'long_ratio' day, so the feature advances every run instead of freezing). ONLY
the long_ratio feature is written — funding/close/spot are the job of the other Bybit ingests; this one never
touches them.

Mirrors `bybit_funding_ingest.ingest_bybit_funding` (concurrent FETCH across a bounded pool + serialized
main-thread WRITE) — same OOM-safe, resume-on-partial contract.
"""
from __future__ import annotations

from collections.abc import Callable
from typing import Any, Protocol

from fxhnt.application.bybit_concurrency import (
    DEFAULT_WORKERS,
    fetch_symbols_concurrently,
    resume_floor_ms,
)


class _BybitAccountRatio(Protocol):
    def long_ratio_history(self, symbol: str, *, start_ms: int | None = None) -> dict[int, float]: ...


def ingest_bybit_long_short(
    store: Any,
    bybit: _BybitAccountRatio,
    symbols: list[str],
    *,
    start_ms: int | None = None,
    resume: bool = True,
    workers: int = DEFAULT_WORKERS,
    progress: Callable[[str], None] | None = None,
) -> dict[str, Any]:
    """Fetch + write the daily long/short account ratio for each symbol into `store`
    (feature='long_ratio', ts=epoch_day*86400).

    Returns {"symbols": N, "day_rows": M, "oldest_day": D|None}: N = symbols that produced >=1 day-row,
    M = total day-rows written, D = the earliest epoch_day across all symbols (None if nothing ingested).

    Concurrency (`workers`, default 12): the per-symbol FETCH (paginating the network-bound Bybit API) runs
    across a bounded thread pool; each symbol's WRITE happens in the main thread as the fetch completes
    (serialized — no store thread-safety needed). Memory-bounded: results are written as completed, never
    collected up front, so peak memory stays ~one symbol.

    Resumable (`resume=True`, default): each symbol resumes FROM ITS LAST PERSISTED 'long_ratio' DAY — the
    per-symbol last day is queried ONCE up front (`store.last_day_by_symbol('long_ratio')`) and used as the
    backward-pagination floor, so a symbol already carrying long_ratio is re-fetched only from its last day
    forward (advancing it; the idempotent upsert overwrites the boundary day in place) and a fresh symbol is
    back-filled from `start_ms`. NOTHING is symbol-skipped, so long_ratio advances every run. Because the
    upsert is idempotent, `resume=False` re-fetches everything from `start_ms` to the identical final state."""
    n_symbols = 0
    day_rows = 0
    oldest_day: int | None = None
    last_day = store.last_day_by_symbol("long_ratio") if resume else {}
    if resume and last_day and progress is not None:
        progress(f"resuming from last persisted date: {len(last_day)} symbols already carry long_ratio")

    def fetch_one(sym: str) -> dict[int, float]:
        return bybit.long_ratio_history(sym, start_ms=resume_floor_ms(last_day, sym, start_ms))

    def on_result(sym: str, daily: dict[int, float]) -> None:
        nonlocal n_symbols, day_rows, oldest_day
        if not daily:
            if progress is not None:
                progress(f"{sym}: 0 day-rows (no account-ratio history in window)")
            return
        rows = [(day * 86_400, {"long_ratio": val}) for day, val in sorted(daily.items())]
        store.write_features(sym, rows)            # batched idempotent upsert; serialized in the main thread
        n_symbols += 1
        day_rows += len(rows)
        sym_oldest = min(daily)
        oldest_day = sym_oldest if oldest_day is None else min(oldest_day, sym_oldest)
        if progress is not None:
            progress(f"{sym}: {len(rows)} day-rows (oldest day {sym_oldest})")

    fetch_symbols_concurrently(symbols, fetch_one, workers=workers, on_result=on_result)
    return {"symbols": n_symbols, "day_rows": day_rows, "oldest_day": oldest_day}
