"""Regression tests for the 3 bugs in the Monday 2026-07-20 live UCITS paper rebalance:

  Bug 1 (Error 478): the ISIN-qualified UCITS contract retained a conflicting `localSymbol`/`secId` so the
    order was rejected ('requested ibLocalSymbol CBU0, from contract CSBGU0') and the IEF leg got 0 fills.
    Fix: after qualify, route by the resolved `conId` + SMART and STRIP the ambiguous identity fields
    (localSymbol / secId / secIdType / symbol) so nothing conflicts with the conId IBKR already pinned.

  Bug 2 (Error 10349): the bare MarketOrder carried no TIF, so the account's order preset forced TIF=DAY and
    canceled-then-reconfirmed each order. Fix: set TIF explicitly on the MarketOrder so it matches the preset.

  Bug 3 (false-success): `plan.executed = placed > 0` counted a Cancelled/Submitted (unfilled) order as
    'placed', so a run where a whole leg didn't fill still reported EXECUTED and exited 0 — the executed
    forward track + reconciliation gate would book a rebalance that didn't happen. Fix: a leg whose status is
    not a fill marks the plan degraded; the plan is executed only if EVERY placed leg actually filled, and the
    CLI exits non-zero on a degraded run (errs false-WAIT, never phantom-EXECUTED).
"""
from __future__ import annotations

from types import SimpleNamespace

from fxhnt.adapters.broker.ibkr import IbkrBroker
from fxhnt.ports.broker import Order


class _FakeIB478:
    """Simulates IBKR: qualify populates the canonical localSymbol/conId; placeOrder REJECTS if the contract
    still carries a *different* localSymbol than the qualified one (the real Error 478)."""
    def __init__(self, *, canonical_local: str = "CSBGU0", con_id: int = 79000139):
        self._canon = canonical_local
        self._con = con_id
        self.placed: list = []

    def isConnected(self):
        return True

    def qualifyContracts(self, contract):
        # IBKR fills in the canonical identity for the ISIN.
        contract.conId = self._con
        contract.localSymbol = self._canon
        return [contract]

    def sleep(self, _s):
        return None

    def managedAccounts(self):
        return ["DU9600528"]

    def placeOrder(self, contract, order):
        local = getattr(contract, "localSymbol", "") or ""
        secid = getattr(contract, "secId", "") or ""
        # Error 478: any residual identity field that isn't the canonical localSymbol conflicts with the conId.
        if (local and local != self._canon) or secid:
            self.placed.append((contract, order, "Cancelled"))
            return SimpleNamespace(orderStatus=SimpleNamespace(status="Cancelled"), order=order, fills=[])
        self.placed.append((contract, order, "Filled"))
        return SimpleNamespace(orderStatus=SimpleNamespace(status="Filled"), order=order, fills=[object()])


def test_bug1_ucits_order_routes_by_conid_no_localsymbol_conflict():
    """The ISIN-resolved UCITS order must place clean: after qualify, the placed contract carries the conId
    and NO conflicting localSymbol/secId, so IBKR does not raise Error 478."""
    broker = IbkrBroker()
    fake = _FakeIB478(canonical_local="CSBGU0", con_id=79000139)
    broker._ib = fake
    # This is the CBU0 (IEF->Treasury) order that failed live: request localSymbol differs from canonical.
    order = Order(symbol="CBU0", side="BUY", quantity=55,
                  exchange="SMART", currency="USD", sec_id_type="ISIN", sec_id="IE00B3VWN518")
    status = broker.place_order(order)
    assert status == "Filled"                        # BEFORE fix: "Cancelled" (Error 478)
    contract, _placed_order, _ = fake.placed[0]
    assert getattr(contract, "conId", 0) == 79000139  # pinned by conId
    assert contract.exchange == "SMART"
    assert not getattr(contract, "secId", None)       # ambiguous ISIN stripped after resolution
    # localSymbol either cleared or equal to canonical — never a conflicting value.
    assert getattr(contract, "localSymbol", "") in ("", None, "CSBGU0")


def test_bug2_market_order_sets_explicit_tif():
    """The placed MarketOrder must carry an explicit TIF so the account order-preset cannot force one
    (Error 10349). We assert the order object has a non-empty tif set by place_order."""
    broker = IbkrBroker()
    fake = _FakeIB478()
    broker._ib = fake
    broker.place_order(Order(symbol="CBU0", side="BUY", quantity=5,
                             sec_id_type="ISIN", sec_id="IE00B3VWN518"))
    _contract, placed_order, _ = fake.placed[0]
    assert getattr(placed_order, "tif", "") == "DAY"   # explicit; matches the account DAY preset
