"""各交易所 app 模块 → strategy_register 配置(统一工厂)."""
from __future__ import annotations

import sys
from typing import Any


def resolve_trading_app_module(app_module: Any = None) -> Any:
    """
    须在 login_required 定义之后调用.
    PM2 / python app.py 时 __name__ 为 __main__,请传入 sys.modules[__name__].
    """
    if app_module is None:
        main = sys.modules.get("__main__")
        if main is not None and hasattr(main, "login_required"):
            m = main
        else:
            import inspect

            m = None
            for fr in inspect.stack():
                g = fr.frame.f_globals
                if callable(g.get("login_required")) and callable(g.get("get_db")):
                    m = g
                    break
            if m is None:
                raise RuntimeError(
                    "策略交易注册失败:请使用 install_strategy_trading(app, repo_root, app_module=sys.modules[__name__])"
                )
    else:
        m = app_module
    if not hasattr(m, "login_required"):
        raise RuntimeError(
            "策略交易注册须在 login_required 定义之后执行(将 install_strategy_trading 放在 app.py 末尾)"
        )
    return m


def build_strategy_config(
    app_module: Any = None, *, trend_enabled: bool = False, trend_disabled_note: str = ""
) -> dict:
    m = resolve_trading_app_module(app_module)

    def get_trading_capital_usdt(conn):
        if hasattr(m, "get_exchange_capitals"):
            _, tc = m.get_exchange_capitals(force=True)
            if tc is not None:
                return float(tc)
        if hasattr(m, "get_available_trading_usdt"):
            snap = m.get_available_trading_usdt()
            if snap is not None:
                return float(snap)
        day = m.get_trading_day(m.app_now())
        row = m.ensure_session(conn, day)
        return float(row["current_capital"])

    def get_position(ex_sym, direction):
        from lib.hub.hub_position_metrics import normalize_contracts_qty

        qty = m.get_live_position_contracts(ex_sym, direction)
        entry = None
        try:
            rows = m.exchange.fetch_positions([ex_sym])
            for p in rows or []:
                matcher = getattr(m, "_row_matches_monitor_direction", None)
                if matcher and not matcher(direction, p):
                    continue
                contracts = getattr(m, "_position_row_effective_contracts", lambda x: abs(float(x.get("contracts") or 0)))(p)
                if contracts <= 0:
                    continue
                coerce = getattr(m, "_coerce_float", None)
                if coerce:
                    entry = coerce(
                        p.get("entryPrice"),
                        p.get("average"),
                        (p.get("info") or {}).get("entryPrice"),
                    )
                if entry:
                    break
        except Exception:
            pass
        return {"contracts": normalize_contracts_qty(qty or 0), "entry_price": entry}

    def amount_to_precision(ex_sym, amount):
        try:
            return float(m.exchange.amount_to_precision(ex_sym, float(amount)))
        except Exception:
            return None

    def price_to_precision(ex_sym, price):
        try:
            return float(m.exchange.price_to_precision(ex_sym, float(price)))
        except Exception:
            return None

    def market_add(ex_sym, direction, amount, leverage):
        return m.place_exchange_order(ex_sym, direction, amount, leverage, stop_loss=None, take_profit=None)

    def limit_add(ex_sym, direction, amount, price, leverage):
        m.exchange.set_leverage(int(leverage), ex_sym)
        side = "buy" if direction == "long" else "sell"
        if hasattr(m, "build_okx_order_params"):
            params = m.build_okx_order_params(direction, reduce_only=False)
        elif hasattr(m, "build_binance_order_params"):
            params = m.build_binance_order_params(direction, reduce_only=False)
        elif hasattr(m, "build_gate_order_params"):
            params = m.build_gate_order_params(direction, reduce_only=False)
        else:
            params = {}
        return m.exchange.create_order(
            ex_sym, "limit", side, float(amount), float(price), params if params is not None else {}
        )

    def replace_tpsl(ex_sym, direction, sl, tp, order_row):
        row = order_row or {"symbol": ex_sym, "exchange_symbol": ex_sym, "direction": direction}
        m.replace_active_monitor_tpsl_on_exchange(row, sl, tp)

    def count_trends(conn):
        try:
            return int(
                conn.execute(
                    "SELECT COUNT(*) FROM trend_pullback_plans WHERE status='active'"
                ).fetchone()[0]
            )
        except Exception:
            return 0

    def friendly_error(err):
        fn = getattr(m, "friendly_exchange_error", None) or getattr(
            m, "friendly_okx_error", None
        )
        if not callable(fn):
            return str(err)
        try:
            snap = m.get_available_trading_usdt()
        except Exception:
            snap = None
        try:
            return fn(err, available_usdt=snap)
        except TypeError:
            return fn(err)

    def limit_order_status(ex_sym, order_id):
        fn = getattr(m, "fib_limit_order_status", None)
        if callable(fn):
            return fn(ex_sym, order_id)
        return "unknown"

    def cancel_limit_order(ex_sym, order_id):
        fn = getattr(m, "cancel_fib_limit_order", None)
        if callable(fn):
            try:
                return fn(ex_sym, order_id)
            except Exception:
                pass
        if not order_id:
            return False
        try:
            m.exchange.cancel_order(str(order_id), ex_sym)
            return True
        except Exception:
            return False

    def get_mark_price(symbol):
        fn = getattr(m, "get_symbol_mark_price", None) or getattr(m, "get_price", None)
        if not callable(fn):
            return None
        try:
            return fn(symbol)
        except Exception:
            return None

    def wechat_account_label():
        fn = getattr(m, "_wechat_account_label", None)
        if callable(fn):
            try:
                return fn()
            except Exception:
                pass
        return getattr(m, "EXCHANGE_DISPLAY_NAME", "") or ""

    def wechat_direction_text(direction):
        fn = getattr(m, "_wechat_direction_text", None)
        if callable(fn):
            try:
                return fn(direction)
            except Exception:
                pass
        d = (direction or "long").strip().lower()
        return "做多" if d == "long" else "做空"

    def send_wechat(content):
        fn = getattr(m, "send_wechat_msg", None)
        if callable(fn):
            fn(content)

    note = trend_disabled_note or (
        "趋势回调(自动补仓)请在 Gate机器人实例使用:/strategy/trend"
    )
    return {
        "app_module": m,
        "exchange_display": getattr(m, "EXCHANGE_DISPLAY_NAME", ""),
        "trend_enabled": trend_enabled,
        "trend_disabled_note": note,
        "login_required": m.login_required,
        "get_db": m.get_db,
        "normalize_symbol_input": m.normalize_symbol_input,
        "normalize_exchange_symbol": m.normalize_exchange_symbol,
        "get_price": m.get_price,
        "get_trading_capital_usdt": get_trading_capital_usdt,
        "get_position": get_position,
        "amount_to_precision": amount_to_precision,
        "price_to_precision": price_to_precision,
        "market_add": market_add,
        "limit_add": limit_add,
        "replace_tpsl": replace_tpsl,
        "ensure_live_ready": m.ensure_exchange_live_ready,
        "default_risk_percent": float(getattr(m, "RISK_PERCENT", 2)),
        "default_leverage": m.infer_leverage,
        "friendly_error": friendly_error,
        "app_now_str": m.app_now_str,
        "resolve_fill_price": m.resolve_order_entry_price,
        "price_fmt": m.format_price_for_symbol,
        "count_active_trend_plans": count_trends if trend_enabled else count_trends,
        "limit_order_status": limit_order_status,
        "cancel_limit_order": cancel_limit_order,
        "get_mark_price": get_mark_price,
        "send_wechat": send_wechat,
        "format_price": getattr(m, "format_price_for_symbol", None),
        "wechat_account_label": wechat_account_label,
        "wechat_direction_text": wechat_direction_text,
    }
