"""半自动出场与参数。"""

from __future__ import annotations

from app.strategy.selection import list_otm_strikes, pick_otm_strike
from app.strategy.semi_auto import (
    REASON_PERP_NET,
    REASON_POINTS,
    check_semi_exits,
    effective_min_leverage,
)


def test_semi_points_long_needs_net_positive() -> None:
    # 目标 = 行权价 1800 + 50 = 1850；到点但净利≤0 → 不平
    d = check_semi_exits(
        net_pnl=-1.0,
        strike=1800,
        index_px=1850,
        view_side="long",
        option_move_points=50,
        perp_exit_unit=5,
        risk_k=1,
    )
    assert d.should_close is False
    assert "净利≤0" in d.detail

    d2 = check_semi_exits(
        net_pnl=1.0,
        strike=1800,
        index_px=1850,
        view_side="long",
        option_move_points=50,
        perp_exit_unit=5,
        risk_k=1,
    )
    assert d2.should_close is True
    assert d2.reason == REASON_POINTS
    assert d2.target_index == 1850.0


def test_semi_points_uses_strike_not_spot() -> None:
    # 现价 1915、K1930、+50 → 目标 1980；现价未到则不平（净利勿触达锁定）
    d = check_semi_exits(
        net_pnl=1.0,
        strike=1930,
        index_px=1915,
        view_side="long",
        option_move_points=50,
        perp_exit_unit=5,
        risk_k=1,
    )
    assert d.should_close is False
    assert d.target_index == 1980.0

    d2 = check_semi_exits(
        net_pnl=1.0,
        strike=1930,
        index_px=1980,
        view_side="long",
        option_move_points=50,
        perp_exit_unit=5,
        risk_k=1,
    )
    assert d2.should_close is True
    assert d2.reason == REASON_POINTS


def test_semi_points_short() -> None:
    # Put：目标 = K − 50
    d = check_semi_exits(
        net_pnl=2.0,
        strike=1800,
        index_px=1750,
        view_side="short",
        option_move_points=50,
        perp_exit_unit=5,
        risk_k=1,
    )
    assert d.should_close is True
    assert d.reason == REASON_POINTS
    assert d.target_index == 1750.0


def test_semi_net_exit_with_k() -> None:
    # 未到点，但净利 ≥ 5×2=10（永续锁定）
    d = check_semi_exits(
        net_pnl=10.0,
        strike=1800,
        index_px=1810,
        view_side="long",
        option_move_points=50,
        perp_exit_unit=5,
        risk_k=2,
    )
    assert d.should_close is True
    assert d.reason == REASON_PERP_NET
    assert d.net_target == 10.0


def test_semi_not_yet() -> None:
    d = check_semi_exits(
        net_pnl=3.0,
        strike=1800,
        index_px=1820,
        view_side="long",
        option_move_points=50,
        perp_exit_unit=5,
        risk_k=1,
    )
    assert d.should_close is False


def test_otm_leverage_floor() -> None:
    assert effective_min_leverage("otm", 100) == 180
    assert effective_min_leverage("otm", 200) == 200
    assert effective_min_leverage("itm", 100) == 100


def test_pick_otm_within_offset() -> None:
    strikes = [1800.0, 1825.0, 1850.0, 1875.0]
    # Call 虚值：标的 1830 → 1850（20点）在 25 内；1875 超
    k = pick_otm_strike(strikes, 1830, option_side="call", max_offset=25)
    assert k == 1850.0
    assert (
        pick_otm_strike(strikes, 1830, option_side="call", max_offset=15) is None
    )
    # Put 虚值
    k2 = pick_otm_strike(strikes, 1830, option_side="put", max_offset=30)
    assert k2 == 1825.0


def test_list_otm_strikes_near_to_far() -> None:
    # 现价 1917 → Call 虚值 1920/1930/1940（偏离≤25），近→远
    strikes = [1910.0, 1920.0, 1930.0, 1940.0, 1950.0]
    ks = list_otm_strikes(strikes, 1917.0, option_side="call", max_offset=25)
    assert ks == [1920.0, 1930.0, 1940.0]
