"""The sample demonstrates how to request all manual fills by account and to add a manual fill."""

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 real login before run
LOGIN = "your_gw_login"
PASSWORD = "your_gw_password"

# Replace it with enabled symbol
SYMBOL = "DD"

# 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 ManualFillSample(CELEnvironment.CELSinkBase):
    def __init__(self):
        self.account = None
        self.instrument = None
        self.eventGatewayIsUp = threading.Event()
        self.eventGatewayIsDown = threading.Event()
        self.eventAccountIsReady = threading.Event()
        self.eventInstrumentIsReady = threading.Event()
        self.eventManualFillUpdateResolved = threading.Event()
        self.eventManualFillsResolved = threading.Event()

    def Init(self, celEnvironment):
        self.celEnvironment = celEnvironment

    def Start(self):
        LOG.info("Connecting to GW")
        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.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("Requesting instrument %s", SYMBOL)
        self.celEnvironment.cqgCEL.NewInstrument(SYMBOL)
        LOG.info("Waiting for instrument %s ...", SYMBOL)
        AssertMessage(self.eventInstrumentIsReady.wait(TIMEOUT), "Instrument resolution timeout!")

        instrument = win32com.client.Dispatch(self.instrument)

        bestBid = instrument.Bid
        AssertMessage(bestBid.IsValid, "Error! Can't set a fill price due to invalid BBA")

        LOG.info("Best bid value is %f", bestBid.Price)
        LOG.info("Calculating a price that above the market by 10 tick sizes")
        buyOrderPrice = bestBid.Price - 10 * instrument.TickSize
        LOG.info("It is %f", buyOrderPrice)

        statementTime = datetime.now(timezone.utc)

        LOG.info("Creating a request for manual fill adding and fill it")
        manualFillRequest = win32com.client.Dispatch(
            self.celEnvironment.cqgCEL.CreateManualFillRequest(constants.mfutAdd, None))
        manualFillRequest.GWAccountID = self.account.GWAccountID
        manualFillRequest.InstrumentName = instrument.FullName
        manualFillRequest.Note = "Hi from API!"
        manualFillRequest.Price = buyOrderPrice
        manualFillRequest.Quantity = 1
        manualFillRequest.Side = constants.osdUndefined
        manualFillRequest.StatementDate = statementTime
        manualFillRequest.TradeTimestamp = statementTime
        manualFillRequest.SpeculationType = constants.sptSpeculation

        LOG.info("Invoking the request")
        self.celEnvironment.cqgCEL.RequestManualFillUpdate(manualFillRequest)

        LOG.info("Waiting for manual fill adding...")
        AssertMessage(self.eventManualFillUpdateResolved.wait(TIMEOUT), "Manual fill adding timeout!")

        LOG.info("Requesting manual fills for account %d", self.account.GWAccountID)
        self.celEnvironment.cqgCEL.RequestManualFills(self.account.GWAccountID, constants.mfdlSnapshot)

        LOG.info("Waiting for manual fills receiving...")
        AssertMessage(self.eventManualFillsResolved.wait(TIMEOUT), "Manual fills receiving timeout!")

        LOG.info("Logoff from GW")
        self.eventGatewayIsDown.clear()
        self.celEnvironment.cqgCEL.GWLogoff()
        AssertMessage(self.eventGatewayIsDown.wait(TIMEOUT), "GW disconnection timeout!")

        LOG.info("Done!")

    def OnDataError(self, cqgError, errorDescription):
        if cqgError is not None:
            error = win32com.client.Dispatch(cqgError)
            LOG.error(
                "OnDataError: Code: %d Description: %s", error.Code, error.Description)

    def OnGWConnectionStatusChanged(self, newStatus):
        if newStatus == constants.csConnectionUp:
            LOG.info("GW connection is UP!")
            self.eventGatewayIsUp.set()
        if newStatus == constants.csConnectionDown:
            LOG.info("GW connection is DOWN!")
            self.eventGatewayIsDown.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:
            error = win32com.client.Dispatch(cqgError)
            LOG.error("OnInstrumentResolved error: Error code: %d Description: %s", error.Code, error.Description)
            return

        self.instrument = cqgInstrument
        LOG.info("Symbol %s is resolved!", symbol)
        self.eventInstrumentIsReady.set()

    def OnManualFillsResolved(self, cqgManualFills, cqgError):
        if cqgError is not None:
            error = win32com.client.Dispatch(cqgError)
            LOG.error("OnManualFillsResolved error: Code: %d Description: %s", error.Code, error.Description)
            return

        LOG.info("Manual fills are received!")

        manualFills = win32com.client.Dispatch(cqgManualFills)
        for manualFill in manualFills:
            LOG.info(
                "Manual fill: instrument: %s GW account id: %d trade id: %d note: %s display price: %s",
                manualFill.InstrumentName,
                manualFill.GWAccountID,
                manualFill.TradeId,
                manualFill.Note,
                manualFill.DisplayPrice
            )

        self.eventManualFillsResolved.set()

    def OnManualFillUpdateResolved(self, cqgManualFillRequest, cqgError):
        if cqgError is not None:
            error = win32com.client.Dispatch(cqgError)
            LOG.error("OnManualFillUpdateResolved error: Code: %d Description: %s", error.Code, error.Description)
            return

        LOG.info("Manual fill is added!")
        self.eventManualFillUpdateResolved.set()


# If CQGCEL.APIConfiguration must be customized then do it in this function
def CustomAPIConfiguration(APIConfiguration):
    LOG.info("Set UTC time zone. All time values passed to (received from) CQG API should correspond to this setting.")
    APIConfiguration.TimeZoneCode = constants.tzUTC


if __name__ == "__main__":
    CELEnvironment.Start(ManualFillSample, CustomAPIConfiguration)
