"""LIVE 开仓对账：占槽、交易所持仓核对、平永续数量解析。"""

from __future__ import annotations

import logging
from typing import Any

from ..config import get_settings
from ..exchange.runtime import load_runtime_settings
from ..sim.matcher import BLOCKING_STATUSES
from .symbols import resolve_perp_inst_id

logger = logging.getLogger(__name__)

_PERP_EPS = 1e-8


def claim_open_slot(db) -> tuple[bool, str]:
    """原子占用开仓槽：flat/空 → opening；已有 blocking 状态则拒绝。"""
    with db._lock:
        row = db._conn.execute("SELECT status FROM positions WHERE id=1").fetchone()
        st = str(row["status"] or "") if row else ""
        if st in BLOCKING_STATUSES:
            return False, f"已有持仓/半仓状态({st})，请先修复或平仓"
        cur = db._conn.execute(
            """UPDATE positions SET status='opening'
               WHERE id=1 AND (status IS NULL OR status='' OR status='flat')"""
        )
        if cur.rowcount != 1:
            st2 = st or "unknown"
            return False, f"无法占用开仓槽(当前 status={st2})"
        db._conn.commit()
    return True, "ok"


def release_open_slot_if_opening(db) -> None:
    """开仓失败且未落 half_open/open 时，释放 opening 占槽。"""
    with db._lock:
        db._conn.execute(
            "UPDATE positions SET status='flat' WHERE id=1 AND status='opening'"
        )
        db._conn.commit()


def exchange_perp_abs_size(
    client: Any,
    exchange: str,
    perp_inst_id: str,
    perp_side: str,
) -> float | None:
    """查询交易所永续绝对持仓：OKX 张数，Binance ETH。"""
    ex = (exchange or "").strip().lower()
    try:
        if ex == "binance":
            ps = "LONG" if perp_side == "long" else "SHORT"
            return client.get_perp_pos_sz(perp_inst_id, position_side=ps)
        ps = "long" if perp_side == "long" else "short"
        return client.get_perp_pos_sz(perp_inst_id, pos_side=ps)
    except Exception as e:
        logger.warning("exchange_perp_abs_size failed exchange=%s: %s", ex, e)
        return None


def assert_safe_to_open_live(executor) -> tuple[bool, str]:
    """LIVE 开仓前：本地无 blocking 仓，且交易所无残留永续（flat/opening 时）。"""
    if get_settings().is_sim:
        return True, "ok"

    pos = executor.current_position()
    st = str(pos.get("status") or "")
    if st in BLOCKING_STATUSES and st != "opening":
        return False, f"已有持仓/半仓状态({st})，请先修复或平仓"

    client, ex_name = _executor_client_and_exchange(executor)
    if client is None:
        return False, "无法核对交易所持仓"

    perp_inst = resolve_perp_inst_id(executor.db)
    # flat/opening：两侧都查，避免只查默认 long 漏掉 short 残留
    if st in ("flat", "", "opening"):
        sides = ("long", "short")
    else:
        sides = (str(pos.get("perp_side") or "long"),)
    total = 0.0
    for side in sides:
        ex_sz = exchange_perp_abs_size(client, ex_name or "", perp_inst, side)
        if ex_sz is None:
            return False, "无法核对交易所持仓"
        total += float(ex_sz)

    if total > _PERP_EPS and st in ("flat", "", "opening"):
        return (
            False,
            "交易所有永续仓但本地无持仓，禁止新开，请人工核对",
        )
    return True, "ok"


def log_exchange_db_mismatch(executor) -> None:
    """LIVE 启动时记录交易所 vs 本地持仓不一致（仅日志，不阻断）。"""
    if get_settings().is_sim:
        return
    ok, msg = assert_safe_to_open_live(executor)
    if ok:
        logger.info("LIVE startup reconcile: exchange/DB perp OK")
    else:
        logger.warning("LIVE startup reconcile mismatch: %s", msg)


def perp_close_contracts_okx(
    client: Any,
    *,
    perp_inst: str,
    perp_side: str,
    perp_qty_eth: float,
    ct_val: float,
) -> int:
    """平永续张数：优先交易所持仓，否则 DB qty/ct_val。"""
    ps = "long" if perp_side == "long" else "short"
    ex_sz = client.get_perp_pos_sz(perp_inst, pos_side=ps)
    if ex_sz is not None and ex_sz > _PERP_EPS:
        return max(1, int(round(ex_sz)))
    return max(1, int(round(perp_qty_eth / ct_val)))


def perp_close_qty_eth_binance(
    client: Any,
    *,
    perp_inst: str,
    perp_side: str,
    perp_qty_eth: float,
) -> float:
    """平永续 ETH 数量：优先交易所持仓，否则 DB qty。"""
    ps = "LONG" if perp_side == "long" else "SHORT"
    ex_sz = client.get_perp_pos_sz(perp_inst, position_side=ps)
    if ex_sz is not None and ex_sz > _PERP_EPS:
        return float(ex_sz)
    return float(perp_qty_eth)


def _executor_client_and_exchange(executor) -> tuple[Any | None, str | None]:
    if get_settings().is_sim:
        return None, None
    try:
        ex_name = load_runtime_settings().exchange
    except Exception:
        ex_name = get_settings().exchange
    client = None
    if hasattr(executor, "_client"):
        try:
            client = executor._client()
        except Exception as e:
            logger.warning("live executor client unavailable: %s", e)
            return None, ex_name
    return client, ex_name
