"""Local DBN loader — reads a Databento .dbn OHLCV-1d (parent symbology: all outright expiries) and
builds the CONTINUOUS front-month series, faithfully replicating the validated surfer pipeline
(signal_sweep.load_panel + build_returns):

  - keep only outrights matching `^ROOT[FGHJKMNQUVXZ]\\d{1,2}$` (drop calendar spreads);
  - each day the front = the highest-VOLUME outright (its instrument_id, open, close);
  - the continuous log-return is the front's day-over-day return ONLY when the instrument_id is unchanged
    (a roll books 0, never a cross-contract price gap) — equivalent to ratio back-adjustment.
"""
from __future__ import annotations

import datetime as dt
import re

import numpy as np

from fxhnt.domain.models import AssetClass, Market, PriceSeries

_MONTHS = "FGHJKMNQUVXZ"


def continuous_front_adjust(inst: np.ndarray, close: np.ndarray, high: np.ndarray,
                            low: np.ndarray) -> tuple[np.ndarray, np.ndarray, np.ndarray]:
    """Ratio back-adjust a front-month series: roll days (instrument_id change) book a 0 return so
    there is no cross-contract gap; high/low are scaled by the same per-day factor as close, keeping the
    intraday range proportional on the adjusted scale. cumprod of ratios (not exp(cumsum(log))) keeps
    roll days bit-for-bit flat. Returns (syn_close, syn_high, syn_low)."""
    same_contract = np.zeros(len(close), bool)
    same_contract[1:] = inst[1:] == inst[:-1]
    ratio = np.ones_like(close)
    ratio[1:] = np.where(same_contract[1:], close[1:] / close[:-1], 1.0)
    synthetic = 100.0 * np.cumprod(ratio)
    safe_high = np.where(np.isfinite(high), high, close)
    safe_low = np.where(np.isfinite(low), low, close)
    syn_high = np.divide(safe_high, close, out=np.ones_like(synthetic), where=close > 0) * synthetic
    syn_low = np.divide(safe_low, close, out=np.ones_like(synthetic), where=close > 0) * synthetic
    return synthetic, syn_high, syn_low


def read_front_arrays(path: str, symbol: str) -> tuple[np.ndarray, np.ndarray, np.ndarray, np.ndarray, np.ndarray]:
    """Read a Databento OHLCV-1d .dbn and return the raw continuous-front arrays
    (days, instrument_id, close, high, low) — outrights only, front = highest-volume per day."""
    import databento as db

    df = db.DBNStore.from_file(path).to_df().reset_index()
    if df.empty or "close" not in df.columns:
        raise ValueError(f"{path}: no OHLCV close")
    df = df[df["close"] > 0]
    pattern = re.compile("^" + re.escape(symbol) + "[" + _MONTHS + r"]\d{1,2}$")
    df = df[df["symbol"].astype(str).str.match(pattern)]
    if df.empty:
        raise ValueError(f"{path}: no outright contracts match {symbol}")
    df = df.copy()
    df["day"] = df["ts_event"].astype("int64") // (86_400 * 10**9)
    front = df.loc[df.groupby("day")["volume"].idxmax(),
                   ["day", "instrument_id", "close", "high", "low"]].sort_values("day")
    return (front["day"].to_numpy(np.int64), front["instrument_id"].to_numpy(np.int64),
            front["close"].to_numpy(np.float64), front["high"].to_numpy(np.float64),
            front["low"].to_numpy(np.float64))


def _front_to_series(symbol: str, asset_class: AssetClass, days: np.ndarray, inst: np.ndarray,
                     close: np.ndarray, high: np.ndarray, low: np.ndarray) -> PriceSeries:
    """Build the continuous front-month PriceSeries from raw front arrays."""
    synthetic, syn_high, syn_low = continuous_front_adjust(inst, close, high, low)
    dates = tuple(dt.datetime.fromtimestamp(int(d) * 86_400, dt.timezone.utc).strftime("%Y-%m-%d")
                  for d in days)
    return PriceSeries(market=Market(symbol=symbol, asset_class=asset_class), dates=dates,
                       close=synthetic, high=syn_high, low=syn_low)


class LocalDbnLoader:
    name = "dbn_local"

    def load(self, path: str, symbol: str, asset_class: AssetClass = AssetClass.FUTURE) -> PriceSeries:
        return _front_to_series(symbol, asset_class, *read_front_arrays(path, symbol))
