"""Netting delle chiusure market (v1.1.25): close_amount tenta il reduce-only e
riesegue il RESIDUO in market puro quando il netting di conto lo cappa/respinge
(audit 2026-06-11: 3 gambe pairs orfane + 1 close cappato). Il chiamante riceve
UN Fill combinato. Nessuna rete: _submit monkeypatchato."""
from src.live.execution import ExecutionClient, Fill


def _fill(inst, side, amount, filled, px, verified=True, state="filled", fee=0.05):
    return Fill(inst, side, 0.0, amount, px if filled else None, 0.0, fee,
                f"oid-{side}-{filled}", state, verified, filled_amount=filled)


def _client_with(submits, allowance=float("inf")):
    """ExecutionClient con _submit finto e guard del netting stubbato (il guard
    reale legge conto+libri via HTTP/filesystem; qui si inietta il gap)."""
    ec = ExecutionClient()
    calls = []

    def fake_submit(instrument, side, amount, notional, reduce_only, label=None,
                    order_type="market", price=None):
        calls.append(dict(amount=amount, reduce_only=reduce_only, label=label))
        return submits.pop(0)(instrument, side, amount)

    ec._submit = fake_submit
    ec._net_close_allowance = lambda *a, **k: allowance
    return ec, calls


INST = "ETH_USDC-PERPETUAL"


def test_full_reduce_only_no_fallback():
    # caso normale: il reduce-only filla tutto -> NESSUN secondo ordine
    ec, calls = _client_with([
        lambda i, s, a: _fill(i, s, a, filled=a, px=1700.0),
    ])
    f = ec.close_amount(INST, "sell", 0.103, label="w1")
    assert len(calls) == 1 and calls[0]["reduce_only"] is True
    assert f.verified and abs(f.filled_amount - 0.103) < 1e-9
    assert "netting" not in (f.notes or "")


def test_capped_reduce_only_nets_residual():
    # reduce-only CAPPATO (filla 0.078 su 0.105) -> residuo 0.027 in market puro
    ec, calls = _client_with([
        lambda i, s, a: _fill(i, s, a, filled=0.078, px=1700.0),
        lambda i, s, a: _fill(i, s, a, filled=a, px=1702.0),
    ])
    f = ec.close_amount(INST, "sell", 0.105, label="w1")
    assert len(calls) == 2
    assert calls[0]["reduce_only"] is True and calls[1]["reduce_only"] is False
    assert abs(calls[1]["amount"] - 0.027) < 1e-9
    assert calls[1]["label"] == "w1|net"
    assert f.verified and abs(f.filled_amount - 0.105) < 1e-9
    # prezzo medio pesato: (0.078*1700 + 0.027*1702) / 0.105
    exp_px = (0.078 * 1700.0 + 0.027 * 1702.0) / 0.105
    assert abs(f.fill_price - exp_px) < 1e-6
    assert abs(f.fee_usd - 0.10) < 1e-9          # fee sommate
    assert "netting" in f.notes


def test_rejected_reduce_only_nets_full_amount():
    # reduce-only RESPINTO (error, 0 fill) -> tutto l'amount in market puro
    ec, calls = _client_with([
        lambda i, s, a: _fill(i, s, a, filled=0.0, px=None, verified=False, state="error", fee=0.0),
        lambda i, s, a: _fill(i, s, a, filled=a, px=1701.0),
    ])
    f = ec.close_amount(INST, "buy", 0.027, label="pair")
    assert len(calls) == 2 and abs(calls[1]["amount"] - 0.027) < 1e-9
    assert f.verified and abs(f.filled_amount - 0.027) < 1e-9
    assert f.fill_price == 1701.0
    assert "netting" in f.notes


def test_net_leg_also_fails_reports_partial():
    # anche il market puro fallisce -> Fill combinato NON verified, filled = solo r-o
    ec, calls = _client_with([
        lambda i, s, a: _fill(i, s, a, filled=0.05, px=1700.0),
        lambda i, s, a: _fill(i, s, a, filled=0.0, px=None, verified=False, state="error", fee=0.0),
    ])
    f = ec.close_amount(INST, "sell", 0.105, label="w1")
    assert len(calls) == 2
    assert not f.verified
    assert abs(f.filled_amount - 0.05) < 1e-9    # solo il fillato vero nel ledger


def test_stale_state_guard_blocks_naked_order():
    # quota gia' chiusa (DSL in outage / flatten manuale): reduce-only filla 0 e
    # il gap conto-vs-libri e' 0 -> NESSUN ordine market nudo (code-review 2026-06-11)
    ec, calls = _client_with([
        lambda i, s, a: _fill(i, s, a, filled=0.0, px=None, verified=False, state="error", fee=0.0),
    ], allowance=0.0)
    f = ec.close_amount(INST, "buy", 0.105, label="w1")
    assert len(calls) == 1                       # solo il reduce-only, niente fallback
    assert not f.verified and f.filled_amount == 0.0
    assert "netting NEGATO" in f.notes


def test_guard_unknown_fails_safe():
    # guard non calcolabile (rete): FAIL-SAFE, niente ordine nudo
    ec, calls = _client_with([
        lambda i, s, a: _fill(i, s, a, filled=0.0, px=None, verified=False, state="error", fee=0.0),
    ], allowance=None)
    f = ec.close_amount(INST, "buy", 0.105, label="w1")
    assert len(calls) == 1
    assert "non calcolabile" in f.notes


def test_guard_caps_residual_to_allowance():
    # gap parziale: il residuo nudo e' cappato a quanto i libri giustificano
    ec, calls = _client_with([
        lambda i, s, a: _fill(i, s, a, filled=0.0, px=None, verified=False, state="error", fee=0.0),
        lambda i, s, a: _fill(i, s, a, filled=a, px=1700.0),
    ], allowance=0.027)
    f = ec.close_amount(INST, "sell", 0.105, label="w1")
    assert len(calls) == 2
    assert abs(calls[1]["amount"] - 0.027) < 1e-9
    assert not f.verified                        # 0.027 < 0.105: chiusura parziale onesta
    assert abs(f.filled_amount - 0.027) < 1e-9


def test_dust_residual_no_fallback():
    # residuo da artefatto float (1e-17): NON deve diventare un lotto minimo nudo
    ec, calls = _client_with([
        lambda i, s, a: _fill(i, s, a, filled=a - 1e-17, px=1700.0),
    ])
    f = ec.close_amount(INST, "sell", 0.105, label="w1")
    assert len(calls) == 1
    assert f.verified
