"""REBUILD STORICO — fonte di verita' = DERIBIT MAINNET (il venue dove ESEGUIAMO).

Decisione (analisi 2026-06-19, vedi docs/diary/2026-06-19-deribit-history.md):
la contaminazione di data/raw nasce dal downloader che pesca la fase "Deribit" da
Cerbero col token TESTNET (feed farlocco) + la fase storica da Binance/USDT (wedge
USDT ~10bps, fino a 3% sotto depeg). Il cross-check multi-venue ha mostrato che il
venue PIU' vicino al consenso e' Deribit mainnet (0-1 bps), che e' anche dove si
esegue. Quindi: ricostruire lo storico da Deribit MAINNET (ccxt pubblico, NO token).

ARCHITETTURA (la "via migliore"):
  - SORGENTE: ccxt.deribit pubblico = api.deribit.com MAINNET (reale, tokenless;
    il token serve solo per trading/account, NON per gli OHLCV).
  - STRUMENTO per il PREZZO:
      BTC/ETH -> INVERSE perp (BTC/USD:BTC, ETH/USD:ETH): storia lunga (2018-08/2019-03)
                 e ~3 bps dal lineare USDC che eseguiamo (misurato) -> proxy fedele.
      alt     -> LINEARE USDC (X/USDC:USDC): unica opzione su Deribit, dal 2022.
  - BASE UNICA + RESAMPLE: si scarica UN solo timeframe base (default 5m) e si
    derivano tutti gli altri per aggregazione -> coerenza interna GARANTITA
    (15m == agg dei 5m per costruzione) e si valida UNA serie, non 20.
  - COPERTURA ONESTA a due livelli: major 2018/2019->oggi; alt SOLO dal 2022
    (prima non erano tradabili su Deribit -> backtestarli su Binance pre-2022 =
    validare un edge su un mercato che non potevamo eseguire).

NB: le daily NATIVE Deribit chiudono alle 08:00 UTC (settlement); qui il 1d e' DERIVATO
dal base per resample -> confini 00:00 UTC standard e coerenti con gli altri TF.

    uv run python scripts/analysis/rebuild_history.py --smoke           # prova su slice, NON tocca data/raw
    uv run python scripts/analysis/rebuild_history.py --smoke --asset ETH
    uv run python scripts/analysis/rebuild_history.py --asset BTC ETH    # FULL rebuild (scrive data/raw, con backup)
    uv run python scripts/analysis/rebuild_history.py                    # FULL tutti gli asset
"""
from __future__ import annotations
import sys
import time
import shutil
from pathlib import Path
PROJECT_ROOT = Path(__file__).resolve().parents[2]
sys.path.insert(0, str(PROJECT_ROOT))
import numpy as np
import pandas as pd
import ccxt

from src.data.downloader import _parquet_path, DATA_DIR

BACKUP = PROJECT_ROOT / "data" / "_feed_backup"

# asset -> (simbolo ccxt Deribit, start storico reale sul venue)
DERIBIT_INSTR = {
    "BTC":  ("BTC/USD:BTC",   "2018-08-14"),   # inverse, storia lunga ~ lineare entro 3 bps
    "ETH":  ("ETH/USD:ETH",   "2019-03-14"),
    "SOL":  ("SOL/USDC:USDC", "2022-03-15"),   # alt: solo lineare USDC, solo dal 2022
    "ADA":  ("ADA/USDC:USDC", "2022-03-21"),
    "XRP":  ("XRP/USDC:USDC", "2022-03-16"),
    "LTC":  ("LTC/USDC:USDC", "2022-04-28"),
    "DOGE": ("DOGE/USDC:USDC","2022-04-28"),
    "BNB":  ("BNB/USDC:USDC", "2024-10-01"),   # ATTENZIONE: storia cortissima (~1.7y)
}

BASE_TF = "5m"                                  # base unica (5m: supporta tutti i TF standard)
TARGET_TFS = ["5m", "15m", "1h"]                # = set del download_all; 4h/1d il runner li resampla dal 1h
SCHEMA = ["timestamp", "open", "high", "low", "close", "volume"]
COINBASE = ccxt.coinbase({"enableRateLimit": True})


def _deribit():
    return ccxt.deribit({"enableRateLimit": True})


def fetch_base(symbol: str, tf: str, start: str, end: str | None = None) -> pd.DataFrame:
    """OHLCV Deribit mainnet paginato in AVANTI (since esplicito che avanza — evita
    l'ancoraggio-a-now di ccxt deribit con since storico). Ritorna schema canonico."""
    ex = _deribit()
    tf_ms = ex.parse_timeframe(tf) * 1000
    start_ms = int(pd.Timestamp(start, tz="UTC").timestamp() * 1000)
    end_ms = int(pd.Timestamp(end, tz="UTC").timestamp() * 1000) if end else int(time.time() * 1000)
    rows: dict[int, list] = {}
    since, guard = start_ms, 0
    while since <= end_ms and guard < 5000:
        guard += 1
        for attempt in range(3):
            try:
                r = ex.fetch_ohlcv(symbol, tf, since=since, limit=1000)
                break
            except Exception as e:
                if attempt == 2:
                    print(f"      ! {type(e).__name__}: {str(e)[:60]}")
                    r = []
                time.sleep(2 ** attempt)
        r = [x for x in r if int(x[0]) >= since]
        if not r:
            break
        for x in r:
            t = int(x[0])
            if start_ms <= t <= end_ms:
                rows[t] = [t, float(x[1]), float(x[2]), float(x[3]), float(x[4]), float(x[5] or 0)]
        nxt = int(r[-1][0]) + tf_ms
        if nxt <= since:
            break
        since = nxt
    if not rows:
        return pd.DataFrame(columns=SCHEMA)
    df = pd.DataFrame(rows.values(), columns=SCHEMA)
    return df.drop_duplicates("timestamp").sort_values("timestamp").reset_index(drop=True)


def resample(base: pd.DataFrame, tf: str) -> pd.DataFrame:
    """Aggrega il base al timeframe tf (confini 00:00 UTC). Coerente per costruzione."""
    if tf == BASE_TF:
        return base.copy()
    g = base.copy()
    g.index = pd.to_datetime(g["timestamp"], unit="ms", utc=True)
    rule = {"5m": "5min", "15m": "15min", "30m": "30min", "1h": "1h", "2h": "2h",
            "3h": "3h", "4h": "4h", "6h": "6h", "12h": "12h", "1d": "1D"}[tf]
    out = g.resample(rule, label="left", closed="left").agg(
        {"open": "first", "high": "max", "low": "min", "close": "last", "volume": "sum"})
    out = out.dropna(subset=["open"])
    # ms epoch resolution-independent (pandas 2.x indicizza datetime64[ms]: view/asi8
    # cambiano risoluzione e darebbero ts corrotti -> uso una differenza di Timedelta)
    epoch = pd.Timestamp("1970-01-01", tz="UTC")
    ts_ms = ((out.index - epoch) // pd.Timedelta(milliseconds=1)).astype("int64")
    out.insert(0, "timestamp", ts_ms)
    return out.reset_index(drop=True)[SCHEMA]


def cross_audit(df1h: pd.DataFrame, asset: str, start: str, end: str) -> str:
    """Certifica il 1h ricostruito contro Coinbase USD (venue indipendente, NON USDT)."""
    sym = {"BTC": "BTC/USD", "ETH": "ETH/USD", "SOL": "SOL/USD", "ADA": "ADA/USD",
           "XRP": "XRP/USD", "LTC": "LTC/USD", "DOGE": "DOGE/USD", "BNB": None}.get(asset)
    if sym is None:
        return "no-coinbase-ref"
    try:
        if not COINBASE.markets:
            COINBASE.load_markets()
        if sym not in COINBASE.markets:
            return f"coinbase: simbolo {sym} assente"
        s_ms = int(pd.Timestamp(start, tz="UTC").timestamp() * 1000)
        e_ms = int(pd.Timestamp(end, tz="UTC").timestamp() * 1000)
        out, since = {}, s_ms
        while since <= e_ms:
            r = COINBASE.fetch_ohlcv(sym, "1h", since=since, limit=300)
            r = [x for x in r if int(x[0]) >= since]
            if not r:
                break
            for x in r:
                if s_ms <= int(x[0]) <= e_ms and x[4]:
                    out[int(x[0])] = float(x[4])
            nxt = int(r[-1][0]) + 3600_000
            if nxt <= since:
                break
            since = nxt
        ref = pd.Series(out)
    except Exception as e:
        return f"coinbase-err {type(e).__name__}"
    a = df1h.set_index("timestamp")["close"]
    m = pd.concat([a.rename("a"), ref.rename("b")], axis=1, join="inner")
    if len(m) == 0:
        return "0 overlap"
    d = (m["a"] - m["b"]).abs() / m["b"] * 1e4
    return f"vs Coinbase USD: barre={len(m)} med {d.median():.1f} bps p95 {d.quantile(.95):.1f} max {d.max():.1f}"


def build(asset: str, write: bool, smoke: bool) -> None:
    symbol, full_start = DERIBIT_INSTR[asset]
    if smoke:
        start, end = "2026-04-15", "2026-05-27"
    else:
        start, end = full_start, None
    print(f"\n  [{asset}] {symbol}  base {BASE_TF}  da {start}{' -> '+end if end else ' -> oggi'}")
    base = fetch_base(symbol, BASE_TF, start, end)
    if base.empty:
        print("      VUOTO — skip"); return
    f0 = pd.to_datetime(base['timestamp'].iloc[0], unit='ms', utc=True).date()
    f1 = pd.to_datetime(base['timestamp'].iloc[-1], unit='ms', utc=True).date()
    print(f"      base: {len(base)} barre {BASE_TF}  [{f0} -> {f1}]")
    built = {tf: resample(base, tf) for tf in TARGET_TFS}
    for tf, d in built.items():
        print(f"      {tf:<4} -> {len(d):>7} barre")
    # certifica il 1h
    aud_start = "2026-04-15" if smoke else "2026-03-01"
    aud_end = "2026-05-27" if smoke else f1.isoformat()
    print(f"      AUDIT 1h {cross_audit(built['1h'], asset, aud_start, aud_end)}")
    if not write:
        print("      (smoke: NON scrivo data/raw)")
        return
    BACKUP.mkdir(parents=True, exist_ok=True)
    for tf, d in built.items():
        path = _parquet_path(asset, tf)
        if path.exists():
            shutil.copy2(path, BACKUP / f"{asset.lower()}_{tf}.parquet.prebuild.bak")
        tmp = path.with_suffix(".parquet.tmp")
        d.to_parquet(tmp, index=False)
        tmp.replace(path)
        print(f"      scritto {path.name}")


def main():
    argv = sys.argv[1:]
    smoke = "--smoke" in argv
    assets = []
    if "--asset" in argv:
        i = argv.index("--asset")
        assets = [a.upper() for a in argv[i + 1:] if not a.startswith("--")]
    assets = assets or list(DERIBIT_INSTR)
    print("=" * 78)
    print(f"  REBUILD STORICO da DERIBIT MAINNET — {'SMOKE (no write)' if smoke else 'FULL (scrive data/raw, backup)'}")
    print(f"  asset: {assets}  base={BASE_TF}  target={TARGET_TFS}")
    print("=" * 78)
    for a in assets:
        build(a, write=not smoke, smoke=smoke)


if __name__ == "__main__":
    main()
