"""IBKR broker adapter — implements the Broker port via ib_async. The application/domain never see
ib_async; swapping brokers is just another adapter here.

Bakes in hard-won lessons: paper detection via the DU* account prefix; whole-share market orders
(the API rejects fractional, error 10243); a single managed connection reused across a rebalance
(reconnecting per order causes clientId churn). ib_async is an optional dependency (extra: ibkr).
"""
from __future__ import annotations

from types import TracebackType

from fxhnt.ports.broker import AccountState, Order

_SUMMARY_TAGS = ("NetLiquidation", "TotalCashValue", "GrossPositionValue")


class IbkrBroker:
    name = "ibkr"

    def __init__(self, host: str = "127.0.0.1", port: int = 4002, client_id: int = 17, timeout: int = 15) -> None:
        self._host, self._port, self._cid, self._timeout = host, port, client_id, timeout
        self._ib = None  # type: ignore[var-annotated]

    def _ensure(self):  # type: ignore[no-untyped-def]
        from ib_async import IB

        if self._ib is None or not self._ib.isConnected():
            ib = IB()
            ib.connect(self._host, self._port, clientId=self._cid, timeout=self._timeout)
            self._ib = ib
        return self._ib

    def account_state(self) -> AccountState:
        ib = self._ensure()
        accts = ib.managedAccounts()
        acct = accts[0] if accts else "?"
        summ = {v.tag: float(v.value) for v in ib.accountSummary() if v.tag in _SUMMARY_TAGS}
        positions = {p.contract.symbol: float(p.position) for p in ib.positions()}
        return AccountState(
            nlv=summ.get("NetLiquidation", 0.0),
            cash=summ.get("TotalCashValue", 0.0),
            positions=positions,
            is_paper=acct.startswith("DU"),
            gross_position_value=summ.get("GrossPositionValue"),
        )

    def place_order(self, order: Order) -> str:
        from ib_async import MarketOrder, Stock

        ib = self._ensure()
        contract = Stock(order.symbol, "SMART", "USD")
        ib.qualifyContracts(contract)
        market_order = MarketOrder(order.side, int(round(order.quantity)))  # whole shares
        accts = ib.managedAccounts()
        if accts:
            market_order.account = accts[0]
        trade = ib.placeOrder(contract, market_order)
        ib.sleep(2)
        return str(trade.orderStatus.status)

    def close(self) -> None:
        if self._ib is not None and self._ib.isConnected():
            self._ib.disconnect()

    def __enter__(self) -> "IbkrBroker":
        self._ensure()
        return self

    def __exit__(self, exc_type: type[BaseException] | None, exc: BaseException | None, tb: TracebackType | None) -> None:
        self.close()
