"""LIVE 交易所 SoT：平仓数量与到期路径。"""

from __future__ import annotations

from types import SimpleNamespace

from app.live.reconcile import perp_close_contracts_okx, perp_close_qty_eth_binance


class _FakeOkx:
    def __init__(self, sz) -> None:
        self._sz = sz

    def get_perp_pos_sz(self, _inst, pos_side=None):
        return self._sz


class _FakeBn:
    def __init__(self, sz) -> None:
        self._sz = sz

    def get_perp_pos_sz(self, _inst, position_side=None):
        return self._sz


def test_perp_close_okx_confirmed_flat_never_uses_db() -> None:
    # 交易所已空：即使 allow_db_fallback=True 也返回 0
    assert (
        perp_close_contracts_okx(
            _FakeOkx(0.0),
            perp_inst="ETH-USDT-SWAP",
            perp_side="short",
            perp_qty_eth=8.0,
            ct_val=0.01,
            allow_db_fallback=True,
        )
        == 0
    )


def test_perp_close_okx_unknown_fail_closed_by_default() -> None:
    assert (
        perp_close_contracts_okx(
            _FakeOkx(None),
            perp_inst="ETH-USDT-SWAP",
            perp_side="long",
            perp_qty_eth=8.0,
            ct_val=0.01,
        )
        is None
    )


def test_perp_close_okx_uses_exchange_size() -> None:
    assert (
        perp_close_contracts_okx(
            _FakeOkx(123.0),
            perp_inst="ETH-USDT-SWAP",
            perp_side="long",
            perp_qty_eth=1.0,
            ct_val=0.01,
        )
        == 123
    )


def test_perp_close_bn_confirmed_flat_never_uses_db() -> None:
    assert (
        perp_close_qty_eth_binance(
            _FakeBn(0.0),
            perp_inst="ETHUSDT",
            perp_side="short",
            perp_qty_eth=8.0,
            allow_db_fallback=True,
        )
        == 0.0
    )


def test_matcher_live_residual_no_local_invent(monkeypatch, tmp_path) -> None:
    monkeypatch.setenv("MODE", "LIVE")
    from app.models.db import Database
    from app.sim.matcher import Matcher

    db = Database(tmp_path / "sot.db")
    m = Matcher(db)
    monkeypatch.setattr(m, "_try_exchange_flatten_residual", lambda *a, **k: None)
    row = {
        "group_id": "G1",
        "option_inst_id": "ETH-OPT",
        "option_side": "call",
        "option_qty_eth": 2.0,
        "option_qty_contracts": 200.0,
        "strike": 2000.0,
        "initial_premium": 10.0,
    }
    assert m._settle_one_residual(row, now_ms=1) is None
    db.close()


def test_okx_expiry_skips_option_order(monkeypatch, tmp_path) -> None:
    monkeypatch.setenv("MODE", "LIVE")
    from app.live.executor import OkxLiveExecutor
    from app.models.db import Database

    db = Database(tmp_path / "exp.db")
    ex = OkxLiveExecutor(db)
    monkeypatch.setattr(ex, "_guard_live", lambda: None)
    with db._lock:
        db._conn.execute(
            """UPDATE positions SET
                group_id=?, perp_side=?, perp_qty_eth=?, perp_entry_px=?,
                option_inst_id=?, option_side=?, option_qty_eth=?, option_qty_contracts=?,
                option_entry_px=?, status='open' WHERE id=1""",
            (
                "G-exp",
                "short",
                4.0,
                2000.0,
                "ETH-OPT",
                "call",
                1.0,
                100.0,
                20.0,
            ),
        )
        db._conn.execute(
            """INSERT INTO groups(group_id, status, option_inst_id, perp_inst_id, strike, open_at_ms)
               VALUES (?,?,?,?,?,?)""",
            ("G-exp", "open", "ETH-OPT", "ETH-USDT-SWAP", 1900.0, 1),
        )
        db._conn.commit()

    placed = {"opt": 0, "perp": 0}

    class _C:
        def get_ct_val(self, *_a, **_k):
            return 0.01

        def get_perp_pos_sz(self, *_a, **_k):
            return 400.0

        def place_market(self, *, inst_id, side, sz, **_k):
            if "OPT" in inst_id or "-C" in inst_id or "-P" in inst_id:
                placed["opt"] += 1
            else:
                placed["perp"] += 1
            return SimpleNamespace(avg_px=2010.0, fee=0.1, sz=float(sz))

    monkeypatch.setattr(ex, "_client", lambda: _C())
    monkeypatch.setattr(
        "app.live.executor.exchange_option_abs_size", lambda *_a, **_k: 0.0
    )
    monkeypatch.setattr(ex, "_group_strike", lambda *_a, **_k: 1900.0)
    monkeypatch.setattr(ex, "_close_spot_px", lambda *_a, **_k: 1950.0)
    monkeypatch.setattr(
        "app.live.executor.get_session",
        lambda: SimpleNamespace(snapshot=lambda: {}),
    )
    monkeypatch.setattr(
        "app.live.executor.resolve_perp_inst_id",
        lambda *_a, **_k: "ETH-USDT-SWAP",
    )
    monkeypatch.setattr(
        "app.live.live_pnl.reconcile_closed_group_pnl",
        lambda **_k: 0.0,
    )

    r = ex.close_group(reason="expiry", bypass_liquidity=True)
    assert r.ok, r.detail
    assert placed["opt"] == 0
    assert placed["perp"] == 1
    st = db.fetchone("SELECT status FROM positions WHERE id=1")
    assert str(st["status"]) == "flat"
    db.close()
