"""永期监控:假平仓防护与 TP/SL 分类."""
import unittest
from unittest.mock import MagicMock, patch

from lib.hedge_plan.hedge_plan_monitor_lib import (
    _classify_po_flat_reason,
    _tick_po,
    _within_open_grace,
)


class TestPoMonitorSafety(unittest.TestCase):
    def test_classify_sl_before_tp(self):
        self.assertEqual(
            _classify_po_flat_reason(
                direction="long", entry=1800, mark=1690, tp=1900, sl=1700
            ),
            "perp_sl",
        )
        self.assertEqual(
            _classify_po_flat_reason(
                direction="long", entry=1800, mark=1910, tp=1900, sl=1700
            ),
            "perp_tp",
        )

    def test_classify_ambiguous_prefers_sl(self):
        # mark 在中间:偏 SL,避免误判 TP 跳过强平期权
        self.assertEqual(
            _classify_po_flat_reason(
                direction="long", entry=1800, mark=1800, tp=1900, sl=1700
            ),
            "perp_sl",
        )

    def test_classify_unknown_without_mark(self):
        self.assertEqual(
            _classify_po_flat_reason(
                direction="long", entry=1800, mark=None, tp=1900, sl=1700
            ),
            "perp_flat_unknown",
        )

    def test_tick_po_skips_when_live_none(self):
        cfg = {"get_live_position_contracts": lambda *_a, **_k: None}
        plan = {"id": 1, "direction": "long", "entry_mark": 1800, "tp": 1900, "sl": 1700}
        legs = [{"leg_role": "perp", "status": "open", "symbol": "ETH/USDT:USDT", "size": 1}]
        self.assertIsNone(_tick_po(cfg, MagicMock(), plan, legs))

    def test_tick_po_skips_when_live_positive(self):
        cfg = {"get_live_position_contracts": lambda *_a, **_k: 2.0}
        plan = {"id": 1, "direction": "long", "entry_mark": 1800, "tp": 1900, "sl": 1700}
        legs = [{"leg_role": "perp", "status": "open", "symbol": "ETH/USDT:USDT", "size": 1}]
        self.assertIsNone(_tick_po(cfg, MagicMock(), plan, legs))

    def test_within_grace_true_without_opened_at(self):
        self.assertTrue(_within_open_grace({}))

    def test_pending_sl_sticky(self):
        """perp_sl_pending_opt 粘滞,不因 mark 反弹改判 TP."""
        cfg = {
            "get_live_position_contracts": lambda *_a, **_k: 0.0,
            "get_contract_size": lambda *_a, **_k: 0.01,
            "exchange": None,
        }
        plan = {
            "id": 9,
            "direction": "long",
            "entry_mark": 1800,
            "tp": 1900,
            "sl": 1700,
            "premium_total": 5,
            "opened_at": "2000-01-01 00:00:00",
            "close_reason": "perp_sl_pending_opt",
        }
        legs = [
            {"id": 1, "leg_role": "perp", "status": "open", "symbol": "ETH/USDT:USDT", "size": 1, "avg_open": 1800},
            {
                "id": 2,
                "leg_role": "option_hedge",
                "status": "open",
                "inst_id": "ETH-USD-260731-1700-P",
                "size": 1,
                "avg_open": 10,
            },
        ]
        with patch("lib.hedge_plan.hedge_plan_monitor_lib._sell_option") as sell:
            sell.return_value = {"ok": False, "msg": "thin book", "fully_closed": False}
            with patch("lib.hedge_plan.hedge_plan_monitor_lib.notify_hedge"):
                with patch("lib.hedge_plan.hedge_plan_monitor_lib.update_plan") as up:
                    conn = MagicMock()
                    out = _tick_po(cfg, conn, plan, legs)
                    self.assertTrue(out and out.get("retry"))
                    up.assert_called()
                    self.assertEqual(up.call_args.kwargs.get("close_reason"), "perp_sl_pending_opt")
                    self.assertNotEqual(up.call_args.kwargs.get("status"), "closed")


if __name__ == "__main__":
    unittest.main()

