"""永期「以期权为主」定仓/方向/目标位/校验."""
import unittest

from lib.hedge_plan.hedge_plan_option_primary_lib import (
    PREMIUM_EXEC_FACTOR,
    build_option_primary_preview,
    estimate_combo_net_pnl,
    floor2,
    opt_type_for_view,
    option_bid_liquidity_ok,
    perp_direction_for_view,
    pick_option_primary_candidate,
    size_from_premium,
    target_hit,
    validate_option_primary_start,
    validate_option_primary_watch,
)
from lib.hedge_plan.hedge_plan_orders_lib import build_po_path_plan, validate_start_body


class TestOptionPrimary(unittest.TestCase):
    def test_direction_mapping(self):
        self.assertEqual(opt_type_for_view("long"), "C")
        self.assertEqual(opt_type_for_view("short"), "P")
        self.assertEqual(perp_direction_for_view("long"), "short")
        self.assertEqual(perp_direction_for_view("short"), "long")

    def test_size_from_premium_095_and_eth_2dp(self):
        # ask=10 → 1 ETH 成本 10U; 预算 100 → usable 95 → eth=9.5 → sheets=950 (ct=0.01)
        sized = size_from_premium(
            premium_budget=100,
            ask=10,
            ct_mult=0.01,
            ratio=2,
            contract_size=0.01,
        )
        self.assertTrue(sized["ok"])
        self.assertAlmostEqual(sized["usable_premium"], 95.0)
        self.assertEqual(sized["eth_qty"], 9.5)
        self.assertEqual(sized["sheets"], 950.0)
        # perp_eth = 9.5/2=4.75; contracts=4.75/0.01=475
        self.assertAlmostEqual(sized["contracts"], 475.0)
        self.assertEqual(PREMIUM_EXEC_FACTOR, 0.95)

    def test_floor2(self):
        self.assertEqual(floor2(1.239), 1.23)
        self.assertEqual(floor2(0.009), 0.0)

    def test_target_hit(self):
        self.assertTrue(target_hit(view_side="long", index_px=1950, strike=1900, points=50))
        self.assertFalse(target_hit(view_side="long", index_px=1949, strike=1900, points=50))
        self.assertTrue(target_hit(view_side="short", index_px=1850, strike=1900, points=50))
        self.assertFalse(target_hit(view_side="short", index_px=1851, strike=1900, points=50))
        self.assertFalse(target_hit(view_side="long", index_px=1900, strike=1900, points=0))

    def test_bid_liquidity(self):
        ok, _ = option_bid_liquidity_ok(1.2, 10, need_sheets=5)
        self.assertTrue(ok)
        ok2, msg = option_bid_liquidity_ok(None, 10, need_sheets=1)
        self.assertFalse(ok2)
        self.assertIn("买一", msg)

    def test_net_pnl_uses_buy_fee_for_sell(self):
        net = estimate_combo_net_pnl(
            view_side="long",
            strike=1900,
            index_px=1950,
            ask_open=20,
            bid=30,
            sheets=2,
            ct_mult=0.01,
            perp_direction="short",
            perp_entry=1900,
            perp_mark=1950,
            contracts=10,
            contract_size=0.01,
            fee=0.001,
        )
        # opt: proceeds=30*2*0.01=0.6; premium=0.4; fees=0.0004+0.0006; opt_net=0.6-0.4-0.001=0.199
        self.assertIn("net", net)
        self.assertEqual(net["fee_rate"], 0.001)

    def test_path_option_primary_no_tpsl_options_first(self):
        path = build_po_path_plan(
            {
                "option_primary": True,
                "direction": "long",
                "opt_inst_id": "ETH-USD-260831-1900-C",
                "sheets": 2,
                "exchange_symbol": "ETH/USDT:USDT",
                "contracts": 1,
            }
        )
        self.assertEqual(path[0]["step"], "options_buy_limit")
        self.assertEqual(path[1]["direction"], "short")
        self.assertFalse(path[1]["attach_tpsl"])

    def test_validate_option_primary_watch_and_start(self):
        watch_body = {
            "option_primary": True,
            "watch_entry": 1,
            "direction": "long",
            "exchange_symbol": "ETH/USDT:USDT",
            "premium_budget": 100,
            "option_target_points": 50,
            "perp_target_points": 30,
            "option_perp_ratio": 4,
            "option_leverage": 200,
            "moneyness": "otm",
        }
        self.assertIsNone(validate_option_primary_watch(watch_body))
        self.assertIsNone(validate_start_body("perp_options", watch_body))

        body = {
            "option_primary": True,
            "watch_entry": 0,
            "direction": "long",
            "contracts": 1,
            "opt_inst_id": "ETH-USD-260831-1900-C",
            "opt_type": "C",
            "sheets": 2,
            "exchange_symbol": "ETH/USDT:USDT",
            "premium_budget": 100,
            "option_target_points": 50,
            "perp_target_points": 30,
            "option_perp_ratio": 2,
            "strike": 1900,
            "index_px": 1905,
            "ask": 10,
            "moneyness": "atm",
            "strike_interval": 15,
            "min_option_hours": 36,
            "hours_to_expiry": 40,
            "option_leverage": 100,
        }
        self.assertIsNone(validate_option_primary_start(body))
        self.assertIsNone(validate_start_body("perp_options", body))
        bad = dict(body, opt_type="P")
        self.assertIsNotNone(validate_start_body("perp_options", bad))

    def test_pick_candidate_respects_leverage(self):
        chain = {
            "index_px": 1900,
            "expiries": [
                {
                    "exp_time": 9_999_999_999_999,
                    "contracts": [
                        {"inst_id": "LOW", "opt_type": "C", "strike": 1920, "ask": 20, "ct_mult": 0.01},
                        {"inst_id": "OK", "opt_type": "C", "strike": 1925, "ask": 8, "ct_mult": 0.01},
                    ],
                }
            ],
        }
        # 1900/20=95 < 200; 1900/8=237.5 ≥ 200
        picked = pick_option_primary_candidate(
            chain,
            direction="long",
            moneyness="otm",
            strike_interval=50,
            min_hours=1,
            min_opt_leverage=200,
        )
        self.assertIsNotNone(picked)
        self.assertEqual(picked["inst_id"], "OK")

    def test_preview_builds_scenarios(self):
        body = {
            "direction": "long",
            "strike": 1900,
            "option_target_points": 50,
            "perp_target_points": 30,
            "ask": 20,
            "sheets": 10,
            "ct_mult": 0.01,
            "contracts": 5,
            "contract_size": 0.01,
            "entry": 1900,
            "index_px": 1900,
            "premium_budget": 100,
        }
        out = build_option_primary_preview(body)
        self.assertTrue(out["option_primary"])
        self.assertEqual(len(out["scenarios"]), 2)

    def test_hours_to_expiry_from_ms(self):
        from lib.hedge_plan.hedge_plan_option_primary_lib import hours_to_expiry_from_ms

        now = 1_700_000_000_000.0
        # +40h in ms
        h = hours_to_expiry_from_ms(now + 40 * 3600 * 1000, now_ms=now)
        self.assertAlmostEqual(h, 40.0, places=3)


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