"""Adapter dataframe/payload -> PriceSeries transforms fill real high/low (pure helpers, no network)."""
from __future__ import annotations

import numpy as np
import pandas as pd

from fxhnt.domain.models import AssetClass, Market

_M = Market(symbol="ES", asset_class=AssetClass.FUTURE)


def test_databento_df_to_series_fills_high_low_and_guards_nan() -> None:
    from fxhnt.adapters.data.databento import _df_to_series
    idx = pd.to_datetime(["2020-01-01", "2020-01-02", "2020-01-03"])
    df = pd.DataFrame({"close": [100.0, 101.0, 102.0],
                       "high": [101.0, np.nan, 103.0],     # NaN must fall back to close
                       "low": [99.0, 100.0, 101.0]}, index=idx)
    ps = _df_to_series(_M, df)
    assert list(ps.dates) == ["2020-01-01", "2020-01-02", "2020-01-03"]
    assert list(ps.high) == [101.0, 101.0, 103.0]          # NaN -> close (101.0)
    assert list(ps.low) == [99.0, 100.0, 101.0]


def test_databento_df_to_series_falls_back_to_close_when_high_low_absent() -> None:
    from fxhnt.adapters.data.databento import _df_to_series
    idx = pd.to_datetime(["2020-01-01", "2020-01-02"])
    df = pd.DataFrame({"close": [100.0, 101.0]}, index=idx)   # no high/low columns
    ps = _df_to_series(_M, df)
    assert list(ps.high) == [100.0, 101.0]                    # high defaults to close
    assert list(ps.low) == [100.0, 101.0]                     # low defaults to close


def test_yahoo_build_series_scales_high_low_by_adjustment_ratio() -> None:
    from fxhnt.adapters.data.yahoo import _build_series
    # day 0: adjusted (ratio 0.5 -> a 2:1 split adjustment); day 1: ratio 1.0
    ts = [1577836800, 1577923200]              # 2020-01-01, 2020-01-02 (UTC)
    adjclose = [50.0, 101.0]
    raw_close = [100.0, 101.0]                 # ratio = adj/raw = 0.5, then 1.0
    raw_high = [110.0, 102.0]
    raw_low = [90.0, 100.0]
    ps = _build_series(_M, ts, adjclose, raw_close, raw_high, raw_low, today="2099-01-01",
                       start=None, end=None)
    assert list(ps.close) == [50.0, 101.0]
    assert list(ps.high) == [55.0, 102.0]      # 110*0.5, 102*1.0
    assert list(ps.low) == [45.0, 100.0]       # 90*0.5, 100*1.0


def test_yahoo_build_series_defaults_missing_high_low_to_close() -> None:
    from fxhnt.adapters.data.yahoo import _build_series
    ts = [1577836800, 1577923200]
    ps = _build_series(_M, ts, adjclose=[100.0, 101.0], raw_close=[100.0, 101.0],
                       raw_high=[None, 102.0], raw_low=[99.0, None], today="2099-01-01",
                       start=None, end=None)
    assert list(ps.high) == [100.0, 102.0]     # None high -> that bar's close
    assert list(ps.low) == [99.0, 101.0]       # None low -> that bar's close


def test_dbn_local_front_to_series_preserves_range_across_roll() -> None:
    from fxhnt.adapters.data.dbn_local import _front_to_series
    days = np.array([0, 1, 2], dtype=np.int64)
    inst = np.array([10, 10, 20], dtype=np.int64)      # roll between day 1 and day 2 (new contract)
    close = np.array([100.0, 102.0, 50.0])             # day 2 is a different contract price level
    high = np.array([101.0, 103.0, 51.0])
    low = np.array([99.0, 101.0, 49.0])
    ps = _front_to_series("ES", AssetClass.FUTURE, days, inst, close, high, low)
    # synthetic ratio-adjusted: roll day books 0 return (no cross-contract gap)
    assert ps.close[2] == ps.close[1]                  # roll -> flat synthetic level
    # intraday range is preserved proportionally on the adjusted scale (high/close on day 2 == raw 51/50)
    assert ps.high[2] / ps.close[2] == 51.0 / 50.0
    assert ps.low[2] / ps.close[2] == 49.0 / 50.0
    assert ps.high[0] >= ps.close[0] >= ps.low[0]
