"""A tiny in-memory stand-in for the MetaTrader5 Python package, ONLY for offline tests.
It mimics the calls Sailar-x uses (account, ticks, orders, positions, deal history)."""
from types import SimpleNamespace as NS

ACCOUNT_TRADE_MODE_DEMO, ACCOUNT_TRADE_MODE_REAL = 0, 2
ORDER_TYPE_BUY, ORDER_TYPE_SELL = 0, 1
POSITION_TYPE_BUY, POSITION_TYPE_SELL = 0, 1
TRADE_ACTION_DEAL, TRADE_ACTION_SLTP = 1, 6
ORDER_TIME_GTC = 0
ORDER_FILLING_FOK, ORDER_FILLING_IOC, ORDER_FILLING_RETURN = 0, 1, 2
TRADE_RETCODE_PLACED, TRADE_RETCODE_DONE = 10008, 10009
DEAL_ENTRY_IN, DEAL_ENTRY_OUT, DEAL_ENTRY_OUT_BY = 0, 1, 3
DEAL_REASON_CLIENT, DEAL_REASON_SL, DEAL_REASON_TP = 0, 4, 5
CONTRACT = 100000

S = {}


def reset(demo=True, balance=100000.0):
    S.clear()
    S.update(demo=demo, balance=balance, bid=1.10000, spread=0.00010, positions=[], deals=[], next=1000,
             reject_deal=None, reject_sltp=False, trade_allowed=True)


reset()


def _id():
    S["next"] += 1
    return S["next"]


def set_bid(b):
    S["bid"] = b


def initialize(**kw):
    return True


def last_error():
    return (1, "ok")


def terminal_info():
    return NS(trade_allowed=S["trade_allowed"], connected=True)


def _floating():
    f = 0.0
    for p in S["positions"]:
        px = S["bid"] if p.type == POSITION_TYPE_BUY else S["bid"] + S["spread"]
        f += ((px - p.price_open) if p.type == POSITION_TYPE_BUY else (p.price_open - px)) * p.volume * CONTRACT
    return f


def account_info():
    return NS(login=1, server="Fake-Demo", currency="USD", balance=S["balance"], equity=S["balance"] + _floating(),
              trade_mode=ACCOUNT_TRADE_MODE_DEMO if S["demo"] else ACCOUNT_TRADE_MODE_REAL)


def symbol_info(sym):
    if sym != "EURUSD":
        return None
    return NS(visible=True, volume_min=0.01, volume_max=100.0, volume_step=0.01, trade_contract_size=CONTRACT,
              digits=5, point=0.00001, filling_mode=2)


def symbol_select(sym, flag):
    return True


def symbol_info_tick(sym):
    return NS(bid=S["bid"], ask=S["bid"] + S["spread"], time=0)


def order_calc_profit(action, sym, vol, po, pc):
    return ((pc - po) if action == ORDER_TYPE_BUY else (po - pc)) * vol * CONTRACT


def order_calc_margin(action, sym, vol, price):
    return vol * CONTRACT * price / 30.0


def positions_get(symbol=None, ticket=None, group=None):
    return tuple(p for p in S["positions"] if (symbol is None or p.symbol == symbol) and (ticket is None or p.ticket == ticket))


def history_deals_get(date_from=None, date_to=None, group=None, ticket=None, position=None):
    return [d for d in S["deals"] if (ticket is None or d.ticket == ticket) and (position is None or d.position_id == position)]


def _close(p, price, reason):
    pnl = ((price - p.price_open) if p.type == POSITION_TYPE_BUY else (p.price_open - price)) * p.volume * CONTRACT
    S["balance"] += pnl
    S["positions"].remove(p)
    S["deals"].append(NS(ticket=_id(), order=_id(), position_id=p.ticket, entry=DEAL_ENTRY_OUT, price=price, volume=p.volume,
                         profit=pnl, commission=0.0, swap=0.0, fee=0.0, reason=reason, symbol=p.symbol,
                         magic=S.get("close_magic", p.magic), comment=S.get("close_comment", ""), time=1_700_000_000))


def trigger_sl(ticket):
    """Test helper: the broker's server hits our stop loss."""
    p = positions_get(ticket=ticket)[0]
    _close(p, p.sl, DEAL_REASON_SL)


def order_send(req):
    a = req["action"]
    if a == TRADE_ACTION_SLTP:
        if S["reject_sltp"]:
            return NS(retcode=10016, comment="Invalid stops", deal=0, order=0, volume=0, price=0)
        p = positions_get(ticket=req["position"])[0]
        p.sl, p.tp = req["sl"], req["tp"]
        return NS(retcode=TRADE_RETCODE_DONE, comment="done", deal=0, order=0, volume=0, price=0)
    if S["reject_deal"]:
        return NS(retcode=S["reject_deal"], comment="Market closed", deal=0, order=0, volume=0, price=0)
    buy = req["type"] == ORDER_TYPE_BUY
    price = S["bid"] + S["spread"] if buy else S["bid"]
    if "position" in req:
        p = positions_get(ticket=req["position"])[0]
        S["close_magic"], S["close_comment"] = req.get("magic", 0), req.get("comment", "")
        _close(p, price, DEAL_REASON_CLIENT)
        S.pop("close_magic", None), S.pop("close_comment", None)
        return NS(retcode=TRADE_RETCODE_DONE, comment="done", deal=S["next"], order=S["next"], volume=req["volume"], price=price)
    t = _id()
    p = NS(ticket=t, symbol=req["symbol"], type=POSITION_TYPE_BUY if buy else POSITION_TYPE_SELL, volume=req["volume"],
           price_open=price, sl=0.0, tp=0.0, magic=req["magic"], time=1_700_000_000, profit=0.0)
    S["positions"].append(p)
    d = _id()
    S["deals"].append(NS(ticket=d, order=t, position_id=t, entry=DEAL_ENTRY_IN, price=price, volume=req["volume"],
                         profit=0.0, commission=0.0, swap=0.0, fee=0.0, reason=DEAL_REASON_CLIENT, symbol=req["symbol"],
                         magic=req["magic"], comment=req.get("comment", ""), time=1_700_000_000))
    return NS(retcode=TRADE_RETCODE_DONE, comment="done", deal=d, order=t, volume=req["volume"], price=price)
