"""Sample demonstrates how to place good-till-date buy bracket order
"""

import logging
import sys
import threading
from datetime import datetime, timedelta, timezone
from typing import Dict

import win32com.client
from win32com.client import constants

import CELEnvironment
from CELEnvironment import AssertMessage

assert sys.version_info >= (3, 6), "The python version should be 3.6 or later"

#region Settings

# Replace with real login and password
LOGIN = "login"
PASSWORD = "password"

# Replace it with real instrument (e.g.: "F.US.EPH20")
INSTRUMENT = "instrument"

# Set your prices
LIMIT_PRICE = 4000

# Set your offsets
TARGET_LIMIT_OFFSET = 1
STOPLOSS_STOP_OFFSET = 3
STOPLOSS_LIMIT_OFFSET = 5

# 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 BracketBuyPlacing(CELEnvironment.CELSinkBase):
    def __init__(self):
        self.celEnvironment: CELEnvironment.CELEnvironment = None
        self.eventDone = threading.Event()
        self.eventGatewayIsUp = threading.Event()
        self.eventGatewayIsDown = threading.Event()
        self.mainOrderGuid = ""
        self.targetOrderGuid = ""
        self.stopLossOrderGuid = ""
        self.ordersReady: Dict[str, bool] = {}  # used to track whether all legs are placed

    def Init(self, celEnvironment: CELEnvironment.CELEnvironment):
        self.celEnvironment = celEnvironment

    def Start(self):
        self.celEnvironment.cqgCEL.GWLogon(LOGIN, PASSWORD)
        LOG.info("Waiting for GW connection...")
        AssertMessage(self.eventGatewayIsUp.wait(TIMEOUT), "GW connection 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 order...")
        mainOrderQuantity = 2
        mainOrder = win32com.client.Dispatch(self.celEnvironment.cqgCEL.CreateOrderByInstrumentName(
            constants.otLimit, INSTRUMENT, account, mainOrderQuantity, constants.osdUndefined, LIMIT_PRICE))
        self.mainOrderGuid = mainOrder.GUID
        mainOrder.DurationType = constants.odGoodTillDate
        props = win32com.client.Dispatch(mainOrder.Properties)
        props(constants.opGTDTime).Value = datetime.now(timezone.utc) + timedelta(days=7)

        LOG.info("Dispatching 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
        self.ordersReady = {
            self.mainOrderGuid: False,
            self.targetOrderGuid: False,
            self.stopLossOrderGuid: False
        }

        LOG.info("Waiting for order placing...")
        bracket.Place()
        AssertMessage(self.eventDone.wait(TIMEOUT), "Order placing timeout!")

        input("Press ENTER to exit...\n")

        if bracket.CanBeCanceled:
            LOG.info("Canceling bracket order.")
            for key in self.ordersReady:
                self.ordersReady[key] = False

            bracket.Cancel()
            AssertMessage(self.eventDone.wait(timeout=TIMEOUT), "Order canceling timeout!")

        self.eventGatewayIsDown.clear()
        self.celEnvironment.cqgCEL.GWLogoff()
        self.eventGatewayIsDown.wait(TIMEOUT)

        LOG.info("Done!")

    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

        stopPrice = str(order.StopPrice) if order.Type in (constants.otStop, constants.otStopLimit) else "N/A"
        properties = win32com.client.Dispatch(order.Properties)
        goodTill = properties(constants.opGTDTime).Value
        LOG.info("%s: Change type: %d; GW status: %d; Quantity: %s; LMT Price: %f; "
                 "STP Price: %s, Duration type: %d, Good till: %s",
                 legName, changeType, order.GWStatus, order.QuantityFractional,
                 order.LimitPrice, stopPrice, order.DurationType, goodTill)

        if order.GWStatus not in (constants.osInOrderBook, constants.osContingent, constants.osCanceled):
            return

        self.ordersReady[order.GUID] = True
        if all(readyFlag is True for readyFlag in self.ordersReady.values()):
            LOG.info("Order is placed!")
            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()


if __name__ == "__main__":
    CELEnvironment.Start(BracketBuyPlacing)
