"""期权定价单测."""
from lib.options.options_pricing_lib import (
    calc_order_size,
    premium_per_sheet,
    sheets_from_eth_amount,
    total_premium,
)
from lib.exchange.okx_options_lib import format_option_px, inst_family_from_inst_id, round_option_px


def test_inst_family_from_inst_id():
    assert inst_family_from_inst_id("ETH-USD_UM-260707-1790-C") == "ETH-USD_UM"
    assert inst_family_from_inst_id("BTC-USD-260925-60000-C") == "BTC-USD"


def test_round_option_px():
    assert round_option_px(14.9184, "0.2", "sell") == 14.8
    assert round_option_px(14.81, "0.2", "buy") == 15.0
    assert format_option_px(14.8, "0.2") == "14.8"
    # BTC 期权 tickSz=5: 整数末尾 0 必须保留 (1370 不能显成 137)
    assert format_option_px(1370, "5") == "1370"
    assert format_option_px(1160, 5) == "1160"
    assert format_option_px(1000, "5") == "1000"
    # 无 tick 时不得透出浮点毛刺
    assert format_option_px(482.4881990066513, None) == "482.4882"


def test_premium_per_sheet():
    assert abs(premium_per_sheet(15.6, 0.01) - 0.156) < 1e-9


def test_total_premium_half_eth():
    assert abs(total_premium(15.6, 0.5) - 7.8) < 1e-9


def test_sheets_from_eth():
    assert sheets_from_eth_amount(0.5, 0.01) == 50


def test_calc_order_size_budget():
    r = calc_order_size(
        quote_per_unit=15.6,
        ct_mult=0.01,
        min_sz=1,
        budget_usdc=10,
        budget_buffer=0.95,
        budget_cap=10,
    )
    assert r["ok"] is True
    assert r["sheets"] >= 1
    assert r["total_premium"] <= 10


def test_calc_order_size_sheets():
    r = calc_order_size(
        quote_per_unit=15.6,
        ct_mult=0.01,
        min_sz=1,
        sheets=3,
        budget_cap=10,
    )
    assert r["ok"] is True
    assert r["sheets"] == 3
    assert abs(r["total_premium"] - 0.468) < 1e-9


def test_option_moneyness():
    from lib.options.options_pricing_lib import option_moneyness, option_moneyness_label

    assert option_moneyness(opt_type="C", strike=1700, index_px=1800) == "itm"
    assert option_moneyness(opt_type="C", strike=1900, index_px=1800) == "otm"
    assert option_moneyness_label("itm") == "实值"
    assert option_moneyness_label("otm") == "虚值"


def test_equivalent_contract_leverage():
    from lib.options.options_pricing_lib import equivalent_contract_leverage

    # index 1768, 0.2 ETH, premium 2.44 -> ~144.9x
    lev = equivalent_contract_leverage(index_px=1768, eth_amount=0.2, total_premium=2.44)
    assert lev == 144.9


def test_straddle_pricing():
    from lib.options.options_pricing_lib import (
        format_straddle_band,
        straddle_ask_per_unit,
        straddle_breakeven_band,
        straddle_premium_total,
    )

    assert straddle_ask_per_unit(0.148, 16.2) == 16.348
    assert straddle_premium_total(0.148, 16.2, 1.0) == 16.35
    lo, hi = straddle_breakeven_band(1800, 16.348)
    assert lo == 1783.65
    assert hi == 1816.35
    assert format_straddle_band(1800, 16.348) == "1784 ~ 1816"
    assert straddle_ask_per_unit(0.148, None) is None


def test_estimate_expiry_value_and_profit_at_index():
    from lib.options.options_pricing_lib import (
        estimate_expiry_profit_at_index,
        estimate_expiry_value_at_index,
    )

    value = estimate_expiry_value_at_index(
        opt_type="C", strike=1800, target_idx=2000, eth_amount=1.0
    )
    assert value == 200.0

    profit = estimate_expiry_profit_at_index(
        opt_type="C",
        strike=1800,
        target_idx=2000,
        entry_px=0.148,
        eth_amount=1.0,
        total_premium=14.8,
    )
    assert profit == 185.2

    # Call 1780, ask 12.2, 0.01 ETH, target 1793 -> value 0.13, profit 0.01
    v = estimate_expiry_value_at_index(
        opt_type="C", strike=1780, target_idx=1793, eth_amount=0.01
    )
    assert v == 0.13
    p = estimate_expiry_profit_at_index(
        opt_type="C",
        strike=1780,
        target_idx=1793,
        entry_px=12.2,
        eth_amount=0.01,
        total_premium=0.122,
    )
    assert p == 0.01
    # OTM call loses premium
    p2 = estimate_expiry_profit_at_index(
        opt_type="C",
        strike=1780,
        target_idx=1770,
        entry_px=12.2,
        eth_amount=0.01,
        total_premium=0.122,
    )
    assert p2 == -0.12


def test_resolve_chain_quote_otm_no_quote():
    from lib.exchange.okx_options_lib import _resolve_chain_quote

    q = _resolve_chain_quote(
        ticker={},
        meta={"tickSz": "0.2"},
        opt_type="C",
        strike=1800,
        index_px=1776,
    )
    assert q["ask"] is None
    assert q["bid"] is None
    assert q["ask_estimated"] is False


def test_resolve_chain_quote_estimated_ask():
    from lib.exchange.okx_options_lib import _resolve_chain_quote

    q = _resolve_chain_quote(
        ticker={"bidPx": "0.2", "bidSz": "3500"},
        meta={"tickSz": "0.2"},
        opt_type="C",
        strike=1650,
        index_px=1776,
    )
    assert q["ask_estimated"] is True
    assert q["ask"] is not None
    assert q["ask"] >= 120


def test_format_quote_liquidity():
    from lib.options.options_pricing_lib import format_quote_liquidity

    assert format_quote_liquidity(17.2, 150) == "17.2/150"
    assert format_quote_liquidity(817.6, 11) == "817.6/11"
    assert format_quote_liquidity(15.6, None) == "15.6"
    assert format_quote_liquidity(None, 10) is None


def test_estimate_close_by_bids_full_depth():
    from lib.options.options_pricing_lib import estimate_close_by_bids

    # 多档估算需显式 max_levels;默认只估买一
    out = estimate_close_by_bids(
        [{"px": 12.3, "sz": 2}, {"px": 12.1, "sz": 3}],
        4,
        ct_mult=0.01,
        premium_paid=0.4,
        max_levels=5,
    )
    assert out["covered_sheets"] == 4
    assert out["uncovered_sheets"] == 0
    assert out["total_received"] == 0.488
    assert out["avg_px"] == 12.2
    assert out["estimated_pnl"] == 0.088
    assert out["estimated_pnl_ratio_pct"] == 22.0
    assert [x["sheets"] for x in out["levels"]] == [2, 2]

    bid1 = estimate_close_by_bids(
        [{"px": 12.3, "sz": 2}, {"px": 12.1, "sz": 3}],
        4,
        ct_mult=0.01,
        premium_paid=0.4,
    )
    assert bid1["covered_sheets"] == 2
    assert bid1["uncovered_sheets"] == 2
    assert [x["sheets"] for x in bid1["levels"]] == [2]


def test_estimate_close_by_bids_partial_depth():
    from lib.options.options_pricing_lib import estimate_close_by_bids

    out = estimate_close_by_bids([{"px": 10, "sz": 1}], 3, ct_mult=0.01, premium_paid=0.6)
    assert out["covered_sheets"] == 1
    assert out["uncovered_sheets"] == 2
    assert out["total_received"] == 0.1
    # 净盈亏 = 回收 − 全部权利金(不按覆盖比例摊薄)
    assert out["estimated_pnl"] == -0.5
    assert out["estimated_pnl_ratio_pct"] == round(-0.5 / 0.6 * 100, 2)


def test_estimate_close_by_bids_empty():
    from lib.options.options_pricing_lib import estimate_close_by_bids

    out = estimate_close_by_bids([], 2)
    assert out["covered_sheets"] == 0
    assert out["uncovered_sheets"] == 2
    assert out["avg_px"] is None


def test_stub_bid_blocks_auto_close_estimate():
    from lib.options.options_pricing_lib import estimate_close_by_bids, is_stub_bid_px

    stub, reason = is_stub_bid_px(0.2, mark_px=42.0)
    assert stub is True
    assert "残档" in reason or "无效" in reason or "远低于" in reason

    out = estimate_close_by_bids(
        [{"px": 0.2, "sz": 3500}],
        66,
        ct_mult=0.01,
        premium_paid=9.37,
        mark_px=42.0,
    )
    assert out["auto_close_blocked"] is True
    assert out["bid_invalid"] is True
    assert out["estimated_pnl"] is None
    assert out["levels"] == []

    ok, _ = is_stub_bid_px(30.0, mark_px=42.0)
    assert ok is False
    good = estimate_close_by_bids(
        [{"px": 30.0, "sz": 100}],
        10,
        ct_mult=0.01,
        premium_paid=1.0,
        mark_px=42.0,
    )
    assert good["auto_close_blocked"] is False
    assert good["covered_sheets"] == 10


def test_expiry_breakeven_from_ask():
    from lib.options.options_pricing_lib import expiry_breakeven_from_ask

    assert expiry_breakeven_from_ask(opt_type="C", strike=1760, ask_px=15.6) == 1775.6
    assert expiry_breakeven_from_ask(opt_type="P", strike=1760, ask_px=15.6) == 1744.4
    assert expiry_breakeven_from_ask(opt_type="C", strike=1760, ask_px=None, mark_px=14.2) == 1774.2


def test_calc_order_size_too_small():
    r = calc_order_size(
        quote_per_unit=2000.0,
        ct_mult=0.01,
        min_sz=1,
        budget_usdc=10,
        budget_buffer=0.95,
        budget_cap=10,
    )
    assert r["ok"] is False


def test_expiry_breakeven_from_api():
    from lib.options.options_pricing_lib import expiry_breakeven_px

    assert expiry_breakeven_px(
        opt_type="C", strike=3500, avg_px=15.6, be_px_api=3516.2
    ) == 3516.2


def test_expiry_breakeven_call_put():
    from lib.options.options_pricing_lib import expiry_breakeven_px

    assert expiry_breakeven_px(opt_type="C", strike=3500, avg_px=15.6) == 3515.6
    assert expiry_breakeven_px(opt_type="P", strike=3500, avg_px=15.6) == 3484.4


def test_close_breakeven_at_mark_equals_avg():
    from lib.options.options_pricing_lib import close_breakeven_idx

    assert close_breakeven_idx(
        opt_type="C", idx_px=3480, mark_px=15.6, avg_px=15.6
    ) == 3480.0
    assert close_breakeven_idx(
        opt_type="P", idx_px=3480, mark_px=15.6, avg_px=15.6
    ) == 3480.0


def test_close_breakeven_with_delta():
    from lib.options.options_pricing_lib import close_breakeven_idx

    # mark below avg, delta 0.5 ETH on 0.5 ETH position -> slope 1
    be = close_breakeven_idx(
        opt_type="C",
        idx_px=3480,
        mark_px=14.6,
        avg_px=15.6,
        delta_pa=0.5,
        pos=50,
        ct_mult=0.01,
    )
    assert be == 3481.0


def test_format_options_breakeven_line():
    from lib.options.options_pricing_lib import format_options_breakeven_line

    s = format_options_breakeven_line(
        expiry_be_px=3515.6, close_be_px=3498.0, idx_px=3480.0
    )
    assert "到期平衡3516" in s
    assert "平掉回本3498" in s
    assert "指数3480" in s


def test_format_position_row_premium_and_inst_parse():
    from lib.exchange.okx_options_lib import format_position_row

    row = format_position_row(
        {
            "instId": "ETH-USD_UM-260709-1700-P",
            "pos": "20",
            "avgPx": "6.2",
            "markPx": "6.3241",
            "idxPx": "1746",
            "upl": "0.0248",
            "uplRatio": "0.02",
        }
    )
    assert row["opt_type"] == "P"
    assert row["strike"] == 1700.0
    assert row["premium_paid"] == 1.24
    assert row["exp_time_ms"] is not None
    assert row["exp_time_ms"] > 0


def test_expiry_ms_from_inst_id():
    from lib.exchange.okx_options_lib import expiry_ms_from_inst_id, normalize_option_exp_ms

    ms = expiry_ms_from_inst_id("ETH-USD_UM-260709-1700-P")
    assert ms is not None
    from datetime import datetime, timezone

    dt = datetime.fromtimestamp(ms / 1000, tz=timezone.utc)
    assert dt.year == 2026 and dt.month == 7 and dt.day == 9 and dt.hour == 8
    assert normalize_option_exp_ms(None, "ETH-USD_UM-260709-1700-P") == ms


def test_format_position_row_breakeven():
    from lib.exchange.okx_options_lib import format_position_row

    row = format_position_row(
        {
            "instId": "ETH-USD_UM-260703-1800-C",
            "pos": "50",
            "avgPx": "15.6",
            "markPx": "16.2",
            "idxPx": "3480",
            "bePx": "3515.6",
            "optType": "C",
            "stk": "3500",
            "deltaPA": "0.45",
            "upl": "0.3",
            "uplRatio": "0.02",
        }
    )
    assert row["expiry_be_px"] == 3515.6
    assert row["idx_px"] == 3480.0
    assert row["close_be_px"] is not None
    assert row["dist_expiry_be"] == 35.6
