"""Sample demonstrates how to work with leg orders after bracket order execution (main leg fill).
"""

import logging
import threading
from enum import IntEnum
from typing import List

import win32com.client
from win32com.client import constants

import CELEnvironment
from CELEnvironment import AssertMessage

#region Settings

# Replace with real GW login and password
LOGIN = "login"
PASSWORD = "pass"

# Replace it with any full instrument name (should include type and country prefixes and month-year suffix)
INSTRUMENT = "F.US.EPU22"

# Set your offsets
TARGET_LIMIT_OFFSET = 100
STOPLOSS_STOP_OFFSET = 150
STOPLOSS_LIMIT_OFFSET = 170

# Events waiting time
TIMEOUT = 10

# Logging settings: specify log level and log format here.
# Set log level to DEBUG to see more internal messages.
# See more information about logging lib usage: https://docs.python.org/3/howto/logging-cookbook.html
CELEnvironment.LOG_LEVEL = logging.INFO
# CELEnvironment.LOG_FORMAT = ...

# CELEnvironment.SinkInternal uses own LOG_LEVEL (which is inherited from the global one) and own LOG_FORMAT.
# You can redefine them as follows:
# CELEnvironment.SinkInternal.LOG_LEVEL = logging.DEBUG
# CELEnvironment.SinkInternal.LOG_FORMAT = ...

LOG = CELEnvironment.GetLogger()

#endregion


class IndexEnum(IntEnum):
    """Utility enumeration to track order statuses.
    """
    MAIN_LEG = 0
    TARGET_LEG = 1
    STOPLOSS_LEG = 2


def GWStatus2String(gwStatus: int) -> str:
    if gwStatus == 2:
        return "osInTransit"
    if gwStatus == 4:
        return "osInOrderBook"
    if gwStatus == 8:
        return "osInCancel"
    if gwStatus == 9:
        return "osInModify"
    if gwStatus == 10:
        return "osCanceled"
    if gwStatus == 11:
        return "osFilled"
    if gwStatus == 14:
        return "osContingent"

    return str(gwStatus)


class BracketBuyPlacing(CELEnvironment.CELSinkBase):
    def __init__(self):
        self.celEnvironment: CELEnvironment.CELEnvironment = None
        self.eventGatewayIsUp = threading.Event()
        self.eventGatewayIsDown = threading.Event()
        self.eventDone = threading.Event()
        self.mainOrderGuid = ""
        self.targetOrderGuid = ""
        self.stopLossOrderGuid = ""
        self.ordersReady: List[bool] = [False, False, False]

    def Init(self, celEnvironment: CELEnvironment.CELEnvironment):
        """Required by CELEnvironment.
        """

        self.celEnvironment = celEnvironment

    def Start(self):
        """Sample script.
        """

        self.celEnvironment.cqgCEL.GWLogon(LOGIN, PASSWORD)
        LOG.info("Waiting for GW connection...")
        AssertMessage(self.eventGatewayIsUp.wait(TIMEOUT), "GW Logon timeout!")

        self.celEnvironment.cqgCEL.AccountSubscriptionLevel = constants.aslAccountUpdatesAndOrders
        LOG.info("Waiting for accounts coming...")
        AssertMessage(self.eventDone.wait(TIMEOUT), "Accounts coming timeout!")
        self.eventDone.clear()

        LOG.info("Select the first account")
        accounts = win32com.client.Dispatch(self.celEnvironment.cqgCEL.Accounts)
        account = win32com.client.Dispatch(accounts.ItemByIndex(0))

        LOG.info("Creating main leg order...")
        mainOrderQuantity = 1
        mainOrder = win32com.client.Dispatch(self.celEnvironment.cqgCEL.CreateOrderByInstrumentName(
            constants.otMarket, INSTRUMENT, account, mainOrderQuantity))
        self.mainOrderGuid = mainOrder.GUID

        LOG.info("Creating bracket order...")
        bracket = win32com.client.Dispatch(
            self.celEnvironment.cqgCEL.CreateBracketOrder(mainOrder, constants.otStopLimit))
        bracket.TargetLeg.LimitOffset = TARGET_LIMIT_OFFSET
        bracket.StopLossLeg.StopOffset = STOPLOSS_STOP_OFFSET
        bracket.StopLossLeg.LimitOffset = STOPLOSS_LIMIT_OFFSET

        self.targetOrderGuid = bracket.TargetLeg.GUID
        self.stopLossOrderGuid = bracket.StopLossLeg.GUID

        LOG.info("Waiting for order placing...")
        bracket.Place()
        AssertMessage(self.eventDone.wait(TIMEOUT), "Order placing timeout!")
        self.eventDone.clear()
        LOG.info("Bracket order is executed!")

        # Here main leg is filled, bracket order is executed, but we "changed our mind" and want to cancel active legs
        LOG.info("Getting target and stop-loss orders...")
        orders = win32com.client.Dispatch(self.celEnvironment.cqgCEL.Orders)
        targetLegOrder = win32com.client.Dispatch(orders.ItemByGuid(self.targetOrderGuid))
        stopLossOrder = win32com.client.Dispatch(orders.ItemByGuid(self.stopLossOrderGuid))

        # we got needed orders, we can work with them as we want, but for simplicity we just cancel them
        LOG.info("Canceling target and stop-loss orders...")
        self.ordersReady[IndexEnum.TARGET_LEG] = False
        self.ordersReady[IndexEnum.STOPLOSS_LEG] = False
        targetLegOrder.Cancel()
        stopLossOrder.Cancel()
        AssertMessage(self.eventDone.wait(TIMEOUT), "Legs canceling timeout!")
        LOG.info("Legs are canceled!")

        input("Press ENTER to exit...\n")

        self.eventGatewayIsDown.clear()
        self.celEnvironment.cqgCEL.GWLogoff()
        AssertMessage(self.eventGatewayIsDown.wait(TIMEOUT), "GW Logoff timeout!")

        LOG.info("Done!")

    #region Event handlers

    def OnAccountChanged(self, changeType, cqgAccount, cqgPosition):
        if changeType != constants.actAccountsReloaded:
            return

        LOG.info("Accounts are ready!")
        self.eventDone.set()

    def OnOrderChanged(self, changeType, cqgOrder, oldProperties, cqgFill, cqgError):
        if cqgError is not None:
            error = win32com.client.Dispatch(cqgError)
            LOG.error("OnOrderChanged error: Code: %d Description: %s", error.Code, error.Description)
            return

        order = win32com.client.Dispatch(cqgOrder)
        legName = ""
        if order.GUID == self.mainOrderGuid:
            legName = "Main leg"
        elif order.GUID == self.targetOrderGuid:
            legName = "Target leg"
        elif order.GUID == self.stopLossOrderGuid:
            legName = "Stop-loss leg"
        else:
            # change is not related to our bracket order, skip it
            return

        limitPrice = str(order.LimitPrice) if order.Type in (constants.otLimit, constants.otStopLimit) else "N/A"
        stopPrice = str(order.StopPrice) if order.Type in (constants.otStop, constants.otStopLimit) else "N/A"
        LOG.info("%s: Change type: %d; GW status: %s; Quantity: %s; LMT Price: %s; STP Price: %s",
                 legName, changeType, GWStatus2String(order.GWStatus), order.QuantityFractional, limitPrice, stopPrice)

        if order.GWStatus == constants.osFilled and order.GUID == self.mainOrderGuid:
            self.ordersReady[IndexEnum.MAIN_LEG] = True
            return

        if order.GWStatus not in (constants.osInOrderBook, constants.osCanceled):
            return

        if order.GUID == self.targetOrderGuid:
            self.ordersReady[IndexEnum.TARGET_LEG] = True
        elif order.GUID == self.stopLossOrderGuid:
            self.ordersReady[IndexEnum.STOPLOSS_LEG] = True

        if all(self.ordersReady):
            self.eventDone.set()

    def OnGWConnectionStatusChanged(self, newStatus):
        if newStatus == constants.csConnectionUp:
            LOG.info("GW connection is UP!")
            self.eventGatewayIsUp.set()
        elif newStatus == constants.csConnectionDown:
            LOG.info("GW connection is DOWN!")
            self.eventGatewayIsDown.set()

    #endregion


if __name__ == "__main__":
    CELEnvironment.Start(BracketBuyPlacing)
