"""半自动：人工定方向/目标并授权 → 机器盯开盯平 → 平完停等人工。"""

from __future__ import annotations

import json
from dataclasses import dataclass
from typing import Any

from ..config import get_settings
from ..sim.ledger import Ledger

PHASE_WAIT_HUMAN = "wait_human"
REASON_POINTS = "semi_target_points"
REASON_PERP_NET = "semi_perp_exit"
TRADE_LOCK_KEY = "semi_trade_lock"
OTM_LEV_FLOOR = 180.0
MONEYNESS_CHOICES = ("itm", "atm", "otm")


def is_semi_auto(ledger: Ledger | None = None) -> bool:
    led = ledger or Ledger()
    s = get_settings()
    return bool(
        led.get_setting_bool("semi_auto_enabled", s.semi_auto_enabled)
    )


def is_armed(ledger: Ledger | None = None) -> bool:
    led = ledger or Ledger()
    return bool(led.get_setting_bool("semi_armed", False))


def set_armed(db: Any, armed: bool) -> None:
    db.set_setting("semi_armed", "true" if armed else "false")


def clear_armed_if_flat(db: Any, *, has_open_position: bool) -> None:
    """进程重启等：无仓时清授权，避免陈旧 armed 自动开仓。"""
    if not has_open_position:
        set_armed(db, False)


def read_trade_lock(ledger: Ledger | None = None) -> dict[str, Any] | None:
    led = ledger or Ledger()
    raw = led.get_setting_str(TRADE_LOCK_KEY, "") or ""
    raw = str(raw).strip()
    if not raw:
        return None
    try:
        data = json.loads(raw)
    except Exception:
        return None
    if not isinstance(data, dict) or not data.get("group_id"):
        return None
    return data


def lock_trade_params(
    db: Any,
    *,
    group_id: str,
    view_side: str,
    option_move_points: float,
    perp_exit_unit: float,
    moneyness: str = "otm",
    otm_max_offset: float = 25.0,
    perp_unit: float = 1.0,
    option_unit: float = 4.0,
) -> None:
    mny = str(moneyness or "otm").strip().lower()
    if mny not in MONEYNESS_CHOICES:
        mny = "otm"
    payload = {
        "group_id": str(group_id),
        "view_side": "short" if view_side == "short" else "long",
        "option_move_points": float(option_move_points),
        "perp_exit_unit": float(perp_exit_unit),
        "moneyness": mny,
        "otm_max_offset": float(otm_max_offset),
        "perp_unit": float(perp_unit),
        "option_unit": float(option_unit),
    }
    db.set_setting(TRADE_LOCK_KEY, json.dumps(payload, ensure_ascii=False))


def clear_trade_lock(db: Any) -> None:
    db.set_setting(TRADE_LOCK_KEY, "")


def _norm_moneyness(raw: str | None, default: str = "otm") -> str:
    m = str(raw or default).strip().lower()
    return m if m in MONEYNESS_CHOICES else default


def effective_min_leverage(moneyness: str, configured: float) -> float:
    """虚值强制不低于 180；实值/平值用配置值。"""
    lev = max(1.0, float(configured))
    if _norm_moneyness(moneyness) == "otm":
        return max(lev, OTM_LEV_FLOOR)
    return lev


def read_semi_params(
    ledger: Ledger | None = None,
    *,
    group_id: str | None = None,
) -> dict[str, Any]:
    led = ledger or Ledger()
    s = get_settings()
    view = str(
        led.get_setting_str("semi_view_side", s.semi_view_side) or s.semi_view_side
    ).strip().lower()
    if view not in ("long", "short"):
        view = "long"
    move = float(
        led.get_setting_float(
            "semi_option_move_points", s.semi_option_move_points
        )
        or s.semi_option_move_points
    )
    exit_unit = float(
        led.get_setting_float("semi_perp_exit_unit", s.semi_perp_exit_unit)
        or s.semi_perp_exit_unit
    )
    moneyness = _norm_moneyness(
        led.get_setting_str("semi_moneyness", s.semi_moneyness),
        s.semi_moneyness,
    )
    otm_off = float(
        led.get_setting_float("semi_otm_max_offset", s.semi_otm_max_offset)
        or s.semi_otm_max_offset
    )
    perp_u = float(
        led.get_setting_float("semi_perp_unit", s.semi_perp_unit) or s.semi_perp_unit
    )
    opt_u = float(
        led.get_setting_float("semi_option_unit", s.semi_option_unit)
        or s.semi_option_unit
    )
    if perp_u <= 0:
        perp_u = float(s.semi_perp_unit)
    if opt_u <= 0:
        opt_u = float(s.semi_option_unit)
    min_lev_cfg = float(
        led.get_setting_float(
            "semi_min_option_leverage", s.semi_min_option_leverage
        )
        or s.semi_min_option_leverage
    )
    # 持仓中优先用开仓时锁定的出场/选约参数
    lock = read_trade_lock(led)
    if lock and group_id and str(lock.get("group_id")) == str(group_id):
        view = str(lock.get("view_side") or view).strip().lower()
        if view not in ("long", "short"):
            view = "long"
        try:
            move = float(lock.get("option_move_points", move))
            exit_unit = float(lock.get("perp_exit_unit", exit_unit))
            otm_off = float(lock.get("otm_max_offset", otm_off))
            perp_u = float(lock.get("perp_unit", perp_u))
            opt_u = float(lock.get("option_unit", opt_u))
        except (TypeError, ValueError):
            pass
        moneyness = _norm_moneyness(str(lock.get("moneyness") or moneyness), moneyness)
    return {
        "enabled": is_semi_auto(led),
        "armed": is_armed(led),
        "view_side": view,
        # 看法多 → Call+永续空；看法空 → Put+永续多
        "option_side": "call" if view == "long" else "put",
        "perp_side": "short" if view == "long" else "long",
        "option_move_points": move,
        "perp_exit_unit": exit_unit,
        "moneyness": moneyness,
        "otm_max_offset": max(0.0, otm_off),
        "perp_unit": perp_u,
        "option_unit": opt_u,
        "min_option_hours": float(
            led.get_setting_float(
                "semi_min_option_hours", s.semi_min_option_hours
            )
            or s.semi_min_option_hours
        ),
        "min_option_leverage": effective_min_leverage(moneyness, min_lev_cfg),
        "min_option_leverage_cfg": min_lev_cfg,
        "trade_locked": bool(
            lock and group_id and str(lock.get("group_id")) == str(group_id)
        ),
    }


@dataclass(slots=True)
class SemiExitDecision:
    should_close: bool
    reason: str = ""
    detail: str = ""
    target_index: float | None = None
    net_target: float | None = None


def check_semi_exits(
    *,
    net_pnl: float,
    strike: float | None,
    index_px: float | None,
    view_side: str,
    option_move_points: float,
    perp_exit_unit: float,
    risk_k: float = 1.0,
    entry_index: float | None = None,
) -> SemiExitDecision:
    """
    顺方向：指数到达「行权价 ± 波动点」且组合净利>0 → 全平。
      多/Call：目标 = K + N；空/Put：目标 = K − N（N 为设置的波动点，不是现价±N）。
    逆方向兑现（永续锁定净利）：组合净利 ≥ 净利基数×k → 全平。
    """
    view = (view_side or "long").strip().lower()
    if view not in ("long", "short"):
        view = "long"
    move = max(0.0, float(option_move_points))
    k = float(risk_k) if risk_k and risk_k > 0 else 1.0
    net_tgt = max(0.0, float(perp_exit_unit)) * k
    net = float(net_pnl)

    # 逆方向 / 永续净利锁定：达标即可离场（不必等点位）
    if net_tgt > 0 and net + 1e-9 >= net_tgt:
        return SemiExitDecision(
            True,
            REASON_PERP_NET,
            f"半自动·永续净利锁定≥{net_tgt:.2f}U（基数×k）",
            net_target=net_tgt,
        )

    if index_px is None:
        return SemiExitDecision(False, "", "缺指数")
    idx = float(index_px)
    # 锚定行权价；无 strike 时才回退开仓指数（兼容旧仓）
    anchor = None
    if strike is not None and float(strike) > 0:
        anchor = float(strike)
    elif entry_index is not None and float(entry_index) > 0:
        anchor = float(entry_index)
    if anchor is None or idx <= 0 or move <= 0:
        return SemiExitDecision(False, "", "点位无效")

    if view == "long":
        target_idx = anchor + move
        hit = idx + 1e-9 >= target_idx
    else:
        target_idx = anchor - move
        hit = idx - 1e-9 <= target_idx

    if hit and net > 0:
        return SemiExitDecision(
            True,
            REASON_POINTS,
            f"半自动·指数到期权目标{target_idx:.2f}（K{anchor:g}±{move:g}）且净利>0",
            target_index=target_idx,
            net_target=0.0,
        )
    if hit and net <= 0:
        return SemiExitDecision(
            False,
            "",
            f"已到期权目标{target_idx:.2f}但组合净利≤0（{net:.2f}），继续持有",
            target_index=target_idx,
        )
    return SemiExitDecision(
        False,
        "",
        f"未到期权目标（K{anchor:g}→{target_idx:.2f}）",
        target_index=target_idx,
        net_target=net_tgt,
    )
