"""Point-in-time crypto fetch for the combined-book momentum sleeve — Binance USDT-perp daily closes,
survivorship-free (live top-N by 24h quote-volume UNION known-dead perps). No API key (public fapi).

Ported from foxhunt scripts/surfer/fetch_crypto_pit.py with two changes for daily forward use:
  * REFRESH (overwrite) instead of skip-existing — the forward tracker needs fresh days every run;
  * a SINGLE klines page (limit=1500) instead of deep endTime pagination — ~1500 daily bars dwarfs the
    30-day momentum lookback, and one request per symbol keeps a daily refresh cheap.
The HTTP GET is injected so tests run offline."""
from __future__ import annotations

import json
import logging
import os
import time
import urllib.request
from collections.abc import Callable
from typing import Any

import numpy as np

_log = logging.getLogger(__name__)
DAY_MS = 86_400_000
# famous delisted/dead Binance USDT perps — klines remain available post-delist (the survivorship signal)
DEAD = ["LUNAUSDT", "ANCUSDT", "SRMUSDT", "HNTUSDT", "MATICUSDT", "FTTUSDT", "RAYUSDT", "WAVESUSDT",
        "BNXUSDT", "SCUSDT", "OCEANUSDT", "AGIXUSDT", "CVCUSDT", "TLMUSDT", "DENTUSDT", "KEYUSDT",
        "CTKUSDT", "TOMOUSDT", "AKROUSDT", "BLZUSDT", "COMBOUSDT", "FTMUSDT", "DGBUSDT", "REEFUSDT",
        "SLPUSDT", "IDEXUSDT", "LINAUSDT", "NEBLUSDT", "RADUSDT", "BTSUSDT", "STMXUSDT", "MDTUSDT",
        "AMBUSDT", "GTCUSDT", "DARUSDT"]


def _default_get(url: str) -> Any:
    try:
        req = urllib.request.Request(url, headers={"User-Agent": "curl/8"})
        return json.load(urllib.request.urlopen(req, timeout=30))
    except Exception as e:               # noqa: BLE001 — network best-effort; caller logs/skips
        _log.warning("crypto GET failed (%s): %s", url, e)
        return None


def _universe(n: int, get: Callable[[str], Any]) -> list[str]:
    info = get("https://fapi.binance.com/fapi/v1/exchangeInfo")
    perps = {s["symbol"] for s in info["symbols"]
             if s.get("contractType") == "PERPETUAL" and s.get("quoteAsset") == "USDT"
             and s.get("status") == "TRADING"}
    tick = get("https://fapi.binance.com/fapi/v1/ticker/24hr")
    vol = {t["symbol"]: float(t["quoteVolume"]) for t in tick if t["symbol"] in perps}
    return sorted(vol, key=lambda s: -vol[s])[:n]


def _klines(sym: str, get: Callable[[str], Any]) -> tuple[np.ndarray, np.ndarray, np.ndarray]:
    k = get(f"https://fapi.binance.com/fapi/v1/klines?symbol={sym}&interval=1d&limit=1500")
    if not k:
        return np.array([]), np.array([]), np.array([])
    d = {int(r[0]): (float(r[4]), float(r[7])) for r in k}   # close, quote-volume
    days = np.array(sorted(d))
    cl = np.array([d[t][0] for t in days])
    qv = np.array([d[t][1] for t in days])
    return days // DAY_MS, cl, qv


def _funding_daily(sym: str, get: Callable[[str], Any]) -> dict[int, float]:
    f = get(f"https://fapi.binance.com/fapi/v1/fundingRate?symbol={sym}&limit=1000")
    daily: dict[int, float] = {}
    for r in f or []:
        d_key = int(r["fundingTime"]) // DAY_MS
        daily[d_key] = daily.get(d_key, 0.0) + float(r["fundingRate"])
    return daily


def fetch_crypto_pit(data_dir: str, *, top_n: int = 120, get: Callable[[str], Any] = _default_get,
                     sleep: Callable[[float], None] = time.sleep, dead: list[str] | None = None) -> tuple[int, int]:
    """Fetch the survivorship-free crypto panel into <data_dir>/crypto_pit/<SYM>.npz (keys: day, close, qvol,
    funding). REFRESH semantics (overwrites). Returns (symbols_written, symbols_attempted)."""
    out_dir = os.path.join(data_dir, "crypto_pit")
    os.makedirs(out_dir, exist_ok=True)
    syms = sorted(set(_universe(top_n, get)) | set(DEAD if dead is None else dead))
    ok = 0
    for sym in syms:
        kd, cl, qv = _klines(sym, get)
        if len(kd) < 200:
            continue
        fund = _funding_daily(sym, get)
        funding = np.array([fund.get(int(d), 0.0) for d in kd])
        np.savez(os.path.join(out_dir, f"{sym}.npz"), day=kd, close=cl, qvol=qv, funding=funding)
        ok += 1
        sleep(0.1)
    _log.info("crypto fetch: %d/%d symbols -> %s", ok, len(syms), out_dir)
    return ok, len(syms)
