"""Stock order helpers for IBKR (paper / live)."""

from __future__ import annotations

import asyncio
from dataclasses import dataclass
from typing import Any, Literal

Action = Literal["BUY", "SELL"]


@dataclass(frozen=True)
class EquityOrderIntent:
    symbol: str
    action: Action
    shares: int
    notional_usd: float
    order_type: str  # MKT | MOC | LMT
    reason: str
    limit_price: float | None = None
    tif: str = "DAY"


async def last_price(ib: Any, symbol: str) -> float:
    from ib_insync import Stock

    c = Stock(str(symbol).upper(), "SMART", "USD")
    qualified = await ib.qualifyContractsAsync(c)
    if not qualified:
        raise RuntimeError(f"Cannot qualify {symbol}")
    tickers = await ib.reqTickersAsync(c)
    if not tickers:
        raise RuntimeError(f"No ticker for {symbol}")
    t = tickers[0]
    for attr in ("marketPrice", "last", "close"):
        px = getattr(t, attr, None)
        if px is not None and not (isinstance(px, float) and px != px) and float(px) > 0:
            return float(px)
    raise RuntimeError(f"No usable price for {symbol}")


def shares_for_notional(notional_usd: float, price: float) -> int:
    if price <= 0 or notional_usd <= 0:
        return 0
    return max(1, int(notional_usd / price))


async def place_stock_order(
    ib: Any,
    intent: EquityOrderIntent,
    *,
    recommend_only: bool,
    timeout_sec: float = 30.0,
) -> dict:
    """Place a stock order; in recommend_only mode return ticket dict only."""
    from ib_insync import MarketOrder, Order, Stock

    ticket = {
        "symbol": intent.symbol,
        "action": intent.action,
        "shares": int(intent.shares),
        "notional_usd": float(intent.notional_usd),
        "order_type": intent.order_type,
        "limit_price": intent.limit_price,
        "tif": intent.tif,
        "reason": intent.reason,
        "recommend_only": recommend_only,
        "status": "recommended",
    }
    if recommend_only or intent.shares <= 0:
        return ticket

    contract = Stock(intent.symbol.upper(), "SMART", "USD")
    qualified = await ib.qualifyContractsAsync(contract)
    if not qualified:
        ticket["status"] = "error"
        ticket["error"] = "qualify failed"
        return ticket

    ot = intent.order_type.upper()
    if ot == "MOC":
        order = Order(
            action=intent.action,
            totalQuantity=int(intent.shares),
            orderType="MOC",
            tif=str(intent.tif or "DAY"),
        )
    elif ot == "LMT":
        from ib_insync import LimitOrder

        if intent.limit_price is None or float(intent.limit_price) <= 0:
            ticket["status"] = "error"
            ticket["error"] = "LMT requires limit_price > 0"
            return ticket
        order = LimitOrder(intent.action, int(intent.shares), float(intent.limit_price))
        order.tif = str(intent.tif or "DAY")
    else:
        order = MarketOrder(intent.action, int(intent.shares))
        order.tif = str(intent.tif or "DAY")

    trade = ib.placeOrder(contract, order)
    deadline = asyncio.get_event_loop().time() + float(timeout_sec)
    while asyncio.get_event_loop().time() < deadline:
        st = trade.orderStatus.status
        if st in ("Filled", "Cancelled", "Inactive"):
            break
        await asyncio.sleep(0.5)

    ticket["status"] = trade.orderStatus.status
    ticket["filled"] = float(trade.orderStatus.filled or 0)
    ticket["avg_fill_price"] = float(trade.orderStatus.avgFillPrice or 0)
    ticket["order_id"] = int(trade.order.orderId or 0)
    return ticket


def stock_position_qty(portfolio_positions: tuple, symbol: str) -> float:
    sym = symbol.upper()
    qty = 0.0
    for leg in portfolio_positions:
        if leg.sec_type == "STK" and leg.symbol.upper() == sym:
            qty += float(leg.position)
    return qty
