from __future__ import annotations

from typing import Annotated

from fastapi import APIRouter, Depends, HTTPException
from pydantic import BaseModel, Field

from ..config import get_settings
from ..env_store import live_ready
from ..live import get_executor
from ..market import get_gateway
from ..models.db import get_db
from ..sim.ledger import Ledger
from ..strategy.clock import can_open_new, window_key
from ..strategy.group import next_group_id
from .auth import require_user

router = APIRouter(prefix="/api/sim", tags=["sim"])


class ManualOpenBody(BaseModel):
    """可选强制方向；默认按卖一比价自动选。"""
    force_option_side: str | None = Field(default=None, description="call|put")


@router.get("/ledger")
async def sim_ledger(_user: Annotated[str, Depends(require_user)]) -> dict:
    return Ledger().snapshot()


@router.get("/position")
async def sim_position(_user: Annotated[str, Depends(require_user)]) -> dict:
    m = get_executor()
    return {"position": m.current_position(), "unrealized": m.unrealized()}


@router.post("/open-group")
async def sim_open_group(
    _user: Annotated[str, Depends(require_user)],
    body: ManualOpenBody | None = None,
) -> dict:
    ok, reason = live_ready()
    if not get_settings().is_sim and not ok:
        raise HTTPException(status_code=400, detail=reason)
    from ..strategy import get_engine

    st = get_engine().state()
    if st.get("running"):
        raise HTTPException(
            status_code=409,
            detail="策略自动运行中，禁止手动开仓；请先暂停",
        )
    if not Ledger().get_setting_bool("show_manual_trade_buttons", False):
        raise HTTPException(
            status_code=403,
            detail="未开启「显示手动开仓」；请在策略设置中开启后再用",
        )
    ex = get_executor()
    if ex.has_open_position():
        raise HTTPException(status_code=409, detail="有未平仓，禁止开下一组")
    s = get_settings()
    ledger = Ledger()
    skip_weekends = ledger.get_setting_bool("skip_weekends", s.skip_weekends)
    if not can_open_new(skip_weekends=skip_weekends):
        raise HTTPException(
            status_code=409,
            detail="周六/周日跳过开仓（上海时区）",
        )
    gw = get_gateway()
    pick = await gw.pick_for_open_async()
    if pick is None:
        raise HTTPException(
            status_code=409,
            detail="无合格期权：请检查剩余时长、ATM开仓偏差(若已开启)与杠杆(现价/卖一)",
        )
    one_expiry = ledger.get_setting_bool("one_expiry_per_day", s.one_expiry_per_day)
    if one_expiry:
        from ..strategy.clock import (
            expiry_blocked_by_one_per_day,
            used_expiry_ymds_for_day,
        )

        used = used_expiry_ymds_for_day(get_db())
        if expiry_blocked_by_one_per_day(pick.pair.expiry_ymd, used, enabled=True):
            raise HTTPException(
                status_code=409,
                detail=(
                    f"同到期日一天只开一次：今日已用过 {pick.pair.expiry_ymd}，"
                    "请等下一到期日"
                ),
            )

    force = (body.force_option_side if body else None) or None
    if force in ("call", "put"):
        option_side = force
        perp_side = "short" if force == "call" else "long"
        bias = "manual_" + force
        option_ask = pick.call_ask if force == "call" else pick.put_ask
        from ..strategy.selection import option_leverage

        min_lev = Ledger().get_setting_float("min_option_leverage", s.min_option_leverage)
        lev = option_leverage(pick.underlying_px, option_ask)
        if lev is None or lev < min_lev:
            raise HTTPException(
                status_code=409,
                detail=f"强制方向杠杆不足: {lev or 0:.1f} < {min_lev:.0f}",
            )
    else:
        option_side = pick.option_side
        perp_side = pick.perp_side
        bias = pick.bias

    option_inst = (
        pick.pair.call_inst_id if option_side == "call" else pick.pair.put_inst_id
    )
    # 强制方向时用该腿卖一估权利金；否则用选向结果
    sizing_ask = float(
        option_ask
        if force in ("call", "put")
        else pick.option_ask
    )

    wkey = window_key()
    db = get_db()
    from ..strategy.open_pipeline import size_and_gate

    prep = size_and_gate(
        index_px=float(pick.underlying_px),
        option_ask=sizing_ask,
        db=db,
    )
    if not prep.ok:
        raise HTTPException(status_code=409, detail=prep.detail)

    count = len(
        db.fetchall("SELECT group_id FROM groups WHERE group_id LIKE ?", (f"G-{wkey}-%",))
    )
    gid = next_group_id(count)
    engine = get_engine()
    async with engine._lock:
        if ex.has_open_position():
            raise HTTPException(status_code=409, detail="有未平仓，禁止开下一组")
        r = ex.open_group(
            group_id=gid,
            bias=bias,
            option_side=option_side,
            perp_side=perp_side,
            option_inst_id=option_inst,
            entry_index_px=float(pick.underlying_px),
            strike=pick.pair.strike,
            expiry_ymd=pick.pair.expiry_ymd,
        )
    if not r.ok:
        raise HTTPException(status_code=400, detail=r.detail)
    try:
        from ..notify import wecom

        wecom.notify_open(
            group_id=gid,
            detail=r.detail,
            extra={
                "bias": bias,
                "option_side": option_side,
                "perp_side": perp_side,
                "option_inst_id": option_inst,
                "strike": pick.pair.strike,
                "expiry_ymd": pick.pair.expiry_ymd,
                **(r.data or {}),
            },
        )
    except Exception:
        pass
    return {
        "ok": True,
        **(r.data or {}),
        "detail": r.detail,
        "option_leverage": pick.option_leverage,
        "hours_left": pick.hours_left,
        "expiry_ymd": pick.pair.expiry_ymd,
        "strike": pick.pair.strike,
    }


@router.post("/close-group")
async def sim_close_group(_user: Annotated[str, Depends(require_user)]) -> dict:
    if not Ledger().get_setting_bool("show_manual_trade_buttons", False):
        raise HTTPException(
            status_code=403,
            detail="未开启「显示手动开仓」；请在策略设置中开启后再用",
        )
    from ..strategy import get_engine

    engine = get_engine()
    # 与策略引擎共用锁，避免与自动平仓/开仓竞态
    async with engine._lock:
        r = get_executor().close_group(reason="manual")
        if not r.ok and not r.liquidity_wait:
            raise HTTPException(status_code=400, detail=r.detail)
        if r.ok:
            # 与自动/紧急全平一致：成功全平后进入组间休息
            engine.enter_rest_after_close()
            try:
                from ..notify import wecom

                wecom.notify_close(
                    reason="manual",
                    detail=r.detail,
                    data=r.data or {},
                )
            except Exception:
                pass
    return {
        "ok": r.ok,
        "liquidity_wait": r.liquidity_wait,
        "detail": r.detail,
        "data": r.data,
    }
