from fxhnt.domain.cross_sectional_funding import eligible_asof, funding_score, _epoch_day
from fxhnt.domain.cross_sectional_funding import construction_weights, book_return_xs

SC = {"A": 2.0, "B": 1.0, "C": 0.0, "D": -1.0, "E": -2.0}


def _coin(start, n, qvol, funding, close=1.0):
    return {start + i: (close, qvol, funding) for i in range(n)}


def test_eligible_filters_by_liquidity_and_history_pit():
    panel = {
        "LIQ":   _coin(100, 40, 5e6, 0.001),
        "THIN":  _coin(100, 40, 1e5, 0.001),
        "NEW":   _coin(135, 40, 5e6, 0.001),
    }
    elig = eligible_asof(panel, asof_day=139, min_qvol=1e6, min_history=20, vol_window=30)
    assert elig == ["LIQ"]


def test_funding_score_ranks_high_funding_higher():
    panel = {
        "HI":  _coin(100, 30, 5e6, 0.003),
        "MID": _coin(100, 30, 5e6, 0.001),
        "LO":  _coin(100, 30, 5e6, -0.001),
    }
    sc = funding_score(panel, ["HI", "MID", "LO"], asof_day=129, lookback_days=7)
    assert sc["HI"] > sc["MID"] > sc["LO"]


def test_funding_score_is_pit_no_lookahead():
    panel = {
        "HI":   {100 + i: (1.0, 5e6, 0.002) for i in range(60)},
        "LATE": {100 + i: (1.0, 5e6, (0.0001 if i < 30 else 0.01)) for i in range(60)},
    }
    early = funding_score(panel, ["HI", "LATE"], asof_day=120, lookback_days=7)
    assert early["HI"] > early["LATE"]
    late = funding_score(panel, ["HI", "LATE"], asof_day=159, lookback_days=7)
    assert late["LATE"] > late["HI"]


def test_epoch_day_roundtrip():
    assert _epoch_day("1970-01-02") == 1


def test_long_tilt_longs_top_quantile_sums_to_one():
    w = construction_weights(SC, "long_tilt", quantile=0.4)   # top 40% of 5 = 2 names = A,B
    assert set(k for k, v in w.items() if v != 0) == {"A", "B"}
    assert all(v > 0 for v in w.values() if v != 0)
    assert abs(sum(w.values()) - 1.0) < 1e-9


def test_market_neutral_longs_top_shorts_bottom_sums_to_zero():
    w = construction_weights(SC, "market_neutral", quantile=0.4)
    assert w["A"] > 0 and w["B"] > 0 and w["D"] < 0 and w["E"] < 0
    assert abs(sum(w.values())) < 1e-9
    assert abs(sum(abs(v) for v in w.values()) - 2.0) < 1e-9


def test_construction_no_overlap_at_large_quantile():
    # 5 names, quantile 0.6 -> without clamp k=3 and top/bottom would share the middle name.
    w = construction_weights(SC, "market_neutral", quantile=0.6)
    assert abs(sum(w.values())) < 1e-9            # still dollar-neutral
    assert abs(sum(abs(v) for v in w.values()) - 2.0) < 1e-9   # gross still exactly 2 (no silent cancel)
    longs = [s for s, v in w.items() if v > 0]; shorts = [s for s, v in w.items() if v < 0]
    assert set(longs).isdisjoint(set(shorts))     # no name both long and short


def test_funding_score_ignores_future_data():
    base = {100 + i: (1.0, 5e6, 0.001) for i in range(60)}
    panel_a = {"X": dict(base), "Y": {100 + i: (1.0, 5e6, 0.002) for i in range(60)}}
    panel_b = {"X": dict(base), "Y": {100 + i: (1.0, 5e6, 0.002) for i in range(60)}}
    for d in range(141, 160):                      # corrupt ONLY future days (> asof 140)
        panel_b["X"][d] = (1.0, 5e6, 99.0)
    a = funding_score(panel_a, ["X", "Y"], asof_day=140, lookback_days=7)
    b = funding_score(panel_b, ["X", "Y"], asof_day=140, lookback_days=7)
    assert a == b                                   # future funding must not affect the asof-140 score


def test_executable_shorts_only_borrowable_names():
    w = construction_weights(SC, "executable", quantile=0.4, borrowable={"A", "B", "C"})
    assert w["A"] > 0 and w["B"] > 0
    assert w.get("D", 0.0) == 0.0 and w.get("E", 0.0) == 0.0


def test_book_return_signed_carry_minus_cost():
    prev = {"A": 0.5, "E": -0.5}
    funding_today = {"A": 0.002, "E": -0.002}
    new = {"A": 0.5, "E": -0.5}
    cost = {"A": 0.001, "E": 0.001}
    r = book_return_xs(prev, funding_today, new, cost)
    assert abs(r - 0.002) < 1e-12


def test_book_return_charges_turnover():
    prev = {"A": 1.0}
    new = {"B": 1.0}
    cost = {"A": 0.001, "B": 0.001}
    r = book_return_xs(prev, {"A": 0.0}, new, cost)
    assert abs(r - (-0.001)) < 1e-12


