"""回调/突破触价开仓关键位监控:程序盯价,触达计划入场后市价成交(三所共用逻辑)."""
from __future__ import annotations

from datetime import datetime
from typing import Any, Callable, Optional

from lib.key_monitor.false_breakout_key_monitor_lib import (
    _parse_created_at,
    expires_at_text,
    is_false_breakout_expired,
)
from lib.strategy.strategy_trend_lib import trend_dca_level_reached

# 回调触价(原「触价开仓」)
CALLBACK_TRIGGER_ENTRY_MONITOR_TYPE = "回调触价开仓"
LEGACY_TRIGGER_ENTRY_MONITOR_TYPE = "触价开仓"

# 突破触价:标记价穿越 E 后立即市价开仓
BREAKOUT_TRIGGER_ENTRY_MONITOR_TYPE = "突破触价开仓"

TRIGGER_ENTRY_MONITOR_TYPES = frozenset(
    {
        CALLBACK_TRIGGER_ENTRY_MONITOR_TYPE,
        BREAKOUT_TRIGGER_ENTRY_MONITOR_TYPE,
        LEGACY_TRIGGER_ENTRY_MONITOR_TYPE,
    }
)

TRIGGER_ENTRY_VALIDITY_HOURS = 24
TRIGGER_ENTRY_CLOSE_FILLED = "trigger_entry_filled"
TRIGGER_ENTRY_CLOSE_TP_INVALIDATE = "trigger_tp_invalidate"
TRIGGER_ENTRY_CLOSE_SL_INVALIDATE = "trigger_sl_invalidate"
TRIGGER_ENTRY_CLOSE_EXPIRED = "trigger_entry_expired"
TRIGGER_ENTRY_CLOSE_EXCHANGE_FAILED = "trigger_exchange_failed"

KEY_ENTRY_REASON_CALLBACK = "关键位回调触价开仓"
KEY_ENTRY_REASON_BREAKOUT = "关键位突破触价开仓"
KEY_ENTRY_REASON_TRIGGER_LEGACY = "关键位触价开仓"

TRIGGER_ENTRY_IN_FLIGHT_OID = "__trigger_entry_in_flight__"


def is_trigger_entry_in_flight_row(row: Any) -> bool:
    if row is None:
        return False
    try:
        v = row["fib_limit_order_id"]
    except (KeyError, IndexError, TypeError):
        v = getattr(row, "fib_limit_order_id", None)
    return (v or "").strip() == TRIGGER_ENTRY_IN_FLIGHT_OID


def acquire_trigger_entry_exec_lock(conn: Any, monitor_id: int) -> bool:
    cur = conn.execute(
        "UPDATE key_monitors SET fib_limit_order_id=? WHERE id=? "
        "AND (fib_limit_order_id IS NULL OR fib_limit_order_id='')",
        (TRIGGER_ENTRY_IN_FLIGHT_OID, int(monitor_id)),
    )
    return int(cur.rowcount or 0) == 1


def release_trigger_entry_exec_lock(conn: Any, monitor_id: int) -> None:
    conn.execute(
        "UPDATE key_monitors SET fib_limit_order_id=NULL WHERE id=? AND fib_limit_order_id=?",
        (int(monitor_id), TRIGGER_ENTRY_IN_FLIGHT_OID),
    )


def normalize_trigger_entry_monitor_type(monitor_type: Optional[str]) -> str:
    mt = (monitor_type or "").strip()
    if mt == LEGACY_TRIGGER_ENTRY_MONITOR_TYPE:
        return CALLBACK_TRIGGER_ENTRY_MONITOR_TYPE
    return mt


def is_trigger_entry_key_monitor_type(monitor_type: Optional[str]) -> bool:
    return (monitor_type or "").strip() in TRIGGER_ENTRY_MONITOR_TYPES


def is_callback_trigger_entry_key_monitor_type(monitor_type: Optional[str]) -> bool:
    mt = normalize_trigger_entry_monitor_type(monitor_type)
    return mt == CALLBACK_TRIGGER_ENTRY_MONITOR_TYPE


def is_breakout_trigger_entry_key_monitor_type(monitor_type: Optional[str]) -> bool:
    return (monitor_type or "").strip() == BREAKOUT_TRIGGER_ENTRY_MONITOR_TYPE


def key_entry_reason_for_monitor_type(monitor_type: Optional[str]) -> str:
    if is_breakout_trigger_entry_key_monitor_type(monitor_type):
        return KEY_ENTRY_REASON_BREAKOUT
    if is_trigger_entry_key_monitor_type(monitor_type):
        return KEY_ENTRY_REASON_CALLBACK
    return KEY_ENTRY_REASON_TRIGGER_LEGACY


def trigger_entry_reached(direction: str, mark_price: float, entry: float) -> bool:
    """回调触价:多=价跌至 E;空=价涨至 E."""
    return trend_dca_level_reached(direction, mark_price, entry)


def breakout_trigger_entry_crossed(
    direction: str,
    prev_mark: Optional[float],
    mark: float,
    entry: float,
) -> bool:
    """突破触价:多=向上穿越 E;空=向下穿越 E."""
    try:
        m = float(mark)
        e = float(entry)
        pm = float(prev_mark) if prev_mark is not None else None
    except (TypeError, ValueError):
        return False
    direction = (direction or "long").strip().lower()
    if direction == "long":
        if pm is None:
            return m > e
        return pm <= e and m > e
    if pm is None:
        return m < e
    return pm >= e and m < e


def trigger_should_fire(
    monitor_type: Optional[str],
    direction: str,
    mark: float,
    entry: float,
    prev_mark: Optional[float] = None,
) -> bool:
    if is_breakout_trigger_entry_key_monitor_type(monitor_type):
        return breakout_trigger_entry_crossed(direction, prev_mark, mark, entry)
    return trigger_entry_reached(direction, mark, entry)


def trigger_entry_invalidate_by_tp(direction: str, mark_price: float, take_profit: float) -> bool:
    """未开仓前标记价先触达止盈侧则失效."""
    try:
        m = float(mark_price)
        tp = float(take_profit)
    except (TypeError, ValueError):
        return False
    d = (direction or "long").strip().lower()
    if d == "short":
        return m <= tp
    return m >= tp


def trigger_entry_invalidate_by_sl(direction: str, mark_price: float, stop_loss: float) -> bool:
    """突破触价:未到 E 先触达止损侧则失效."""
    try:
        m = float(mark_price)
        sl = float(stop_loss)
    except (TypeError, ValueError):
        return False
    d = (direction or "long").strip().lower()
    if d == "long":
        return m <= sl
    return m >= sl


def trigger_entry_invalidate(
    monitor_type: Optional[str],
    direction: str,
    mark: float,
    stop_loss: float,
    take_profit: float,
) -> Optional[str]:
    if trigger_entry_invalidate_by_tp(direction, mark, take_profit):
        return "tp"
    if is_breakout_trigger_entry_key_monitor_type(monitor_type):
        if trigger_entry_invalidate_by_sl(direction, mark, stop_loss):
            return "sl"
    return None


def validate_trigger_entry_geometry(
    direction: str,
    entry: float,
    stop_loss: float,
    take_profit: float,
    mark_at_add: Optional[float] = None,
    *,
    monitor_type: Optional[str] = None,
) -> Optional[str]:
    """返回错误文案;合法则 None."""
    try:
        e = float(entry)
        sl = float(stop_loss)
        tp = float(take_profit)
    except (TypeError, ValueError):
        return "入场价,止损,止盈须为有效数字"
    if e <= 0 or sl <= 0 or tp <= 0:
        return "入场价,止损,止盈须大于 0"
    d = (direction or "long").strip().lower()
    mt = normalize_trigger_entry_monitor_type(monitor_type)
    label = "突破触价开仓" if mt == BREAKOUT_TRIGGER_ENTRY_MONITOR_TYPE else "回调触价开仓"
    if d == "long":
        if not (sl < e < tp):
            return "做多:须满足 止损 < 入场价 < 止盈"
        if mark_at_add is not None:
            m = float(mark_at_add)
            if m >= tp:
                return f"做多:当前价已不低于止盈,无法添加{label}"
            if mt == BREAKOUT_TRIGGER_ENTRY_MONITOR_TYPE and m >= e:
                return "做多:当前价须低于入场价(等待向上突破)"
    elif d == "short":
        if not (tp < e < sl):
            return "做空:须满足 止盈 < 入场价 < 止损"
        if mark_at_add is not None:
            m = float(mark_at_add)
            if m <= tp:
                return f"做空:当前价已不高于止盈,无法添加{label}"
            if mt == BREAKOUT_TRIGGER_ENTRY_MONITOR_TYPE and m <= e:
                return "做空:当前价须高于入场价(等待向下跌破)"
    else:
        return "方向须为 long 或 short"
    return None


def validate_trigger_entry_rr(
    direction: str,
    entry: float,
    stop_loss: float,
    take_profit: float,
    min_rr: float,
    calc_rr_ratio: Callable[..., Optional[float]],
) -> Optional[str]:
    rr = calc_rr_ratio(direction, entry, stop_loss, take_profit)
    if rr is None or rr <= float(min_rr):
        fmt = f"{rr:.4f}" if rr is not None else "无法计算"
        return f"计划盈亏比 {fmt}:1 未达要求(>{float(min_rr)}:1)"
    return None


def is_trigger_entry_expired(
    created_at: Any,
    now: datetime,
    *,
    hours: int = TRIGGER_ENTRY_VALIDITY_HOURS,
) -> bool:
    return is_false_breakout_expired(created_at, now, hours=hours)


def trigger_entry_expires_at_text(
    created_at: Any,
    *,
    hours: int = TRIGGER_ENTRY_VALIDITY_HOURS,
) -> str:
    return expires_at_text(created_at, hours=hours)


def count_pending_trigger_entries(conn: Any, trading_day: str) -> int:
    td = (trading_day or "").strip()
    if not td:
        return 0
    placeholders = ",".join("?" * len(TRIGGER_ENTRY_MONITOR_TYPES))
    row = conn.execute(
        f"SELECT COUNT(*) FROM key_monitors WHERE monitor_type IN ({placeholders}) AND session_date=?",
        (*TRIGGER_ENTRY_MONITOR_TYPES, td),
    ).fetchone()
    return int(row[0] if row else 0)


def check_trigger_entry_intent_limit(
    conn: Any,
    trading_day: str,
    opens_today: int,
    hard_limit: int,
) -> tuple[bool, str]:
    """当日开仓意图:已成交次数 + 待触发触价条数."""
    if int(hard_limit) <= 0:
        return True, ""
    pending = count_pending_trigger_entries(conn, trading_day)
    total = int(opens_today) + pending
    if total >= int(hard_limit):
        return (
            False,
            f"本交易日开仓意图已达上限(已开 {int(opens_today)} + 待触发 {pending} / 硬上限 {int(hard_limit)})",
        )
    return True, ""


def trigger_entry_gate_preview(
    *,
    monitor_type: Optional[str] = None,
    entry_display: str,
    take_profit_display: str,
    created_at: Any = None,
    now: Optional[datetime] = None,
    expired: bool = False,
    tp_invalidated: bool = False,
    sl_invalidated: bool = False,
    hours: int = TRIGGER_ENTRY_VALIDITY_HOURS,
) -> dict[str, Any]:
    now_dt = now or datetime.now()
    is_exp = expired or is_trigger_entry_expired(created_at, now_dt, hours=hours)
    exp_txt = trigger_entry_expires_at_text(created_at, hours=hours)
    mt = normalize_trigger_entry_monitor_type(monitor_type)
    if tp_invalidated:
        status = "止盈侧失效"
    elif sl_invalidated:
        status = "止损侧失效"
    elif is_exp:
        status = "已过期"
    elif mt == BREAKOUT_TRIGGER_ENTRY_MONITOR_TYPE:
        status = "突破待触发"
    else:
        status = "回调待触发"
    mode = "突破" if mt == BREAKOUT_TRIGGER_ENTRY_MONITOR_TYPE else "回调"
    metrics_parts: list[str] = [f"TP:{take_profit_display}"]
    if exp_txt != "—":
        metrics_parts.append(f"截至:{exp_txt}")
    return {
        "summary": f"{mode}触价 E={entry_display} {status}",
        "metrics": " ".join(metrics_parts),
        "gate_ok": not is_exp and not tp_invalidated and not sl_invalidated,
    }


# 兼容旧 import
TRIGGER_ENTRY_MONITOR_TYPE = CALLBACK_TRIGGER_ENTRY_MONITOR_TYPE
KEY_ENTRY_REASON_TRIGGER = KEY_ENTRY_REASON_CALLBACK
