"""Demonstrates how to park active order.
Sample uses metadata-only order placing, so it is required to provide valid limit price, see `PRICE` constant.
GW authorization should be done before the script run.
"""

import logging
import threading
from datetime import datetime, timezone

import win32com.client
from win32com.client import constants

import CELEnvironment
from CELEnvironment import AssertMessage

#region Settings

# Replace it with enabled symbol
SYMBOL = "EP"

# Replace with real (limit) price
PRICE = 4000

# Default value "12/30/1899 12:00:00 AM" corresponds to no `until` value (order won't be activated automatically).
# If you want to make park-until - provide some valid datetime below.
UNTIL = datetime(year=1899, month=12, day=30, tzinfo=timezone.utc)

# Events waiting time (seconds)
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 OrderParkingSample(CELEnvironment.CELSinkBase):
    def __init__(self):
        self.celEnvironment = None
        self.account = None
        self.instrument = None
        self.eventGatewayIsUp = threading.Event()
        self.eventAccountIsReady = threading.Event()
        self.eventInstrumentIsReady = threading.Event()
        self.eventOrderPlaced = threading.Event()
        self.eventOrderParked = threading.Event()
        self.guid = ""

    def Init(self, celEnvironment):
        self.celEnvironment = celEnvironment

    def Start(self):
        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.eventAccountIsReady.wait(TIMEOUT), "Accounts coming timeout!")

        LOG.info("Select the first account")
        accounts = win32com.client.Dispatch(self.celEnvironment.cqgCEL.Accounts)
        self.account = win32com.client.Dispatch(accounts.ItemByIndex(0))

        LOG.info("'%s' instrument requesting...", SYMBOL)
        request = win32com.client.Dispatch(self.celEnvironment.cqgCEL.CreateInstrumentRequest())
        request.Symbol = SYMBOL
        self.celEnvironment.cqgCEL.SubscribeNewInstrument(request)
        LOG.info("'%s' instrument waiting...", SYMBOL)
        AssertMessage(self.eventInstrumentIsReady.wait(TIMEOUT), "Instrument resolution timeout!")

        LOG.info("Create buy limit order")
        qty = -1
        order = win32com.client.Dispatch(
            self.celEnvironment.cqgCEL.CreateOrder(constants.otLimit, self.instrument, self.account,
                                                   qty, constants.osdUndefined, PRICE))
        self.guid = order.GUID

        LOG.info("Place order, GUID='%s'", self.guid)
        order.Place()
        LOG.info("Waiting for order placing...")
        AssertMessage(self.eventOrderPlaced.wait(TIMEOUT), "Order placing timeout!")

        LOG.info("Park order")
        order.Park(UNTIL)
        AssertMessage(self.eventOrderParked.wait(TIMEOUT), "Order parking timeout!")

        LOG.info("Done!")

    def OnGWConnectionStatusChanged(self, newStatus):
        if newStatus == constants.csConnectionUp:
            LOG.info("GW connection is UP!")
            self.eventGatewayIsUp.set()

    def OnAccountChanged(self, changeType, cqgAccount, cqgPosition):
        if changeType != constants.actAccountsReloaded:
            return

        LOG.info("Accounts are ready!")
        self.eventAccountIsReady.set()

    def OnInstrumentResolved(self, symbol, cqgInstrument, cqgError):
        if cqgError is not None:
            err = win32com.client.Dispatch(cqgError)
            LOG.error("OnInstrumentResolved error: Error code: %d Description: %s", err.Code, err.Description)
            return

        self.instrument = cqgInstrument
        LOG.info("Instrument '%s' is resolved!", symbol)
        self.eventInstrumentIsReady.set()

    def OnOrderChanged(self, changeType, cqgOrder, oldProperties, cqgFill, cqgError):
        if cqgError is not None:
            err = win32com.client.Dispatch(cqgError)
            LOG.error("OnOrderChanged error: Code: %d Description: %s", err.Code, err.Description)
            return

        dispatchedOrder = win32com.client.Dispatch(cqgOrder)
        if self.guid != dispatchedOrder.GUID:
            LOG.info("Ignoring order with GUID='%s'", dispatchedOrder.GUID)
            return

        properties = win32com.client.Dispatch(dispatchedOrder.Properties)
        gwStatus = properties(constants.opGWStatus).Value
        quantity = properties(constants.opQuantityFractional).Value
        instrument = properties(constants.opInstrumentName).Value
        limitPrice = properties(constants.opLimitPrice).Value

        LOG.info("OnOrderChanged: change type: %d; GW status: %d; Quantity: %s; Instrument: %s; Price: %f;",
                 changeType, gwStatus, quantity, instrument, limitPrice)

        if gwStatus == constants.osInOrderBook:
            LOG.info("Order is placed!")
            self.eventOrderPlaced.set()
        elif gwStatus == constants.osParked:
            LOG.info("Order is parked!")
            self.eventOrderParked.set()


def CustomAPIConfiguration(APIConfiguration):
    APIConfiguration.NewInstrumentMode = True


if __name__ == "__main__":
    CELEnvironment.Start(OrderParkingSample, CustomAPIConfiguration)
