"""Microprice port — TDD against the Rust ml-features `microprice.rs` test oracle. Exact DBN fixed-point
inputs (i64, 1e-9) so the float arithmetic is identical to the Rust."""
from __future__ import annotations

from fxhnt.domain.features import (
    MicropriceFeatures,
    multilevel_microprice,
    stoikov_microprice,
)
from fxhnt.domain.marketdata import BidAskLevel, Mbp10Snapshot

_BID = 100_000_000_000  # 100.00 at DBN 1e-9 scaling
_ASK = 100_040_000_000  # 100.04


def _snap(*levels: BidAskLevel) -> Mbp10Snapshot:
    return Mbp10Snapshot(symbol="ES.FUT", ts=0, levels=tuple(levels))


def _l1(bid_sz: int, ask_sz: int, bid_px: int = _BID, ask_px: int = _ASK) -> BidAskLevel:
    return BidAskLevel(bid_px=bid_px, bid_sz=bid_sz, ask_px=ask_px, ask_sz=ask_sz)


def test_balanced_book_equals_mid() -> None:
    snap = _snap(_l1(100, 100))
    assert abs(stoikov_microprice(snap) - snap.mid_price()) < 1e-9   # == 100.02


def test_heavy_ask_tilts_toward_bid() -> None:
    snap = _snap(_l1(10, 1000))
    mp, mid = stoikov_microprice(snap), snap.mid_price()
    assert mp < mid                                                  # heavy ask -> below mid
    assert abs(mp - 100.0) < abs(mp - mid)                           # closer to the bid
    assert abs(mp - 100.000_396_039_6) < 1e-6                        # exact: (100·1000+100.04·10)/1010


def test_heavy_bid_tilts_toward_ask() -> None:
    snap = _snap(_l1(1000, 10))
    assert stoikov_microprice(snap) > snap.mid_price()


def test_zero_depth_falls_back_to_mid() -> None:
    snap = _snap(_l1(0, 0))
    assert stoikov_microprice(snap) == snap.mid_price()


def test_empty_book_is_zero() -> None:
    assert stoikov_microprice(_snap()) == 0.0


def test_multilevel_k1_equals_stoikov() -> None:
    snap = _snap(_l1(100, 200))
    assert abs(multilevel_microprice(snap, 1) - stoikov_microprice(snap)) < 1e-9


def test_residual_sign_is_signum_never_zero() -> None:
    feats = MicropriceFeatures().update(_snap(_l1(1000, 10)))   # heavy bid -> residual > 0
    assert feats["microprice_residual"] > 0 and feats["microprice_residual_sign"] == 1.0
    feats = MicropriceFeatures().update(_snap(_l1(10, 1000)))   # heavy ask -> residual < 0
    assert feats["microprice_residual"] < 0 and feats["microprice_residual_sign"] == -1.0


def test_change_and_acceleration_streaming() -> None:
    f = MicropriceFeatures()
    r0 = f.update(_snap(_l1(100, 100), BidAskLevel(bid_px=_BID, bid_sz=100, ask_px=_ASK, ask_sz=100)))
    assert r0["microprice_change"] == 0.0 and r0["microprice_acceleration"] == 0.0   # cold start

    # a linear ramp of the balanced mid -> first difference constant, second difference ≈ 0
    f = MicropriceFeatures()
    s = [f.update(_snap(_l1(100, 100, bid_px=_BID + k * 10_000_000, ask_px=_ASK + k * 10_000_000)))
         for k in range(3)]
    assert abs(s[2]["microprice_change"] - s[1]["microprice_change"]) < 1e-6   # constant Δ
    assert abs(s[2]["microprice_acceleration"]) < 1e-6                          # Δ² ≈ 0
