"""The sample demonstrates how to request orders and open positions and observe changes on every price change
Press Enter to interrupt the observing and stop the sample
"""

import itertools
import logging
import threading
from datetime import datetime, timezone

import win32com.client
from tabulate import tabulate
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"

# 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 OpenPositionsReceivingSampleNew(CELEnvironment.CELSinkBase):
    def __init__(self):
        self.eventGatewayIsUp = threading.Event()
        self.eventGatewayIsDown = threading.Event()
        self.eventAccountIsReady = threading.Event()
        self.eventOrdersQueryIsReady = 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("Subscribe to open positions of all accounts")
        accounts = win32com.client.Dispatch(self.celEnvironment.cqgCEL.Accounts)
        for account in accounts:
            account.AutoSubscribeInstruments = True
            account.PositionSubcriptionLevel = constants.pslSnapshotAndUpdates
            LOG.info("GW Account id: %d name: %s", account.GWAccountID, account.GWAccountName)

        LOG.info("Select the first account")
        desiredAccount = win32com.client.Dispatch(accounts.ItemByIndex(0))

        LOG.info(
            "Query orders in NotFinal state for account (gw id: %d name: %s)",
            desiredAccount.GWAccountID,
            desiredAccount.GWAccountName,
        )

        ordersRequest = self.celEnvironment.cqgCEL.CreateOrdersRequest()
        ordersRequest.Account = desiredAccount
        ordersRequest.StatusFilter = constants.osfNotFinal
        ordersRequest.Date = datetime.now(timezone.utc)
        ordersRequest.Side = constants.osdUndefined

        self.orderQuery = self.celEnvironment.cqgCEL.RequestOrders(ordersRequest)

        LOG.info("Waiting for orders query completeness...")
        AssertMessage(self.eventOrdersQueryIsReady.wait(TIMEOUT), "Orders query timeout!")

        LOG.info("press Enter to stop..")
        input()

        LOG.info("Logoff from GW")
        self.eventGatewayIsDown.clear()
        self.celEnvironment.cqgCEL.GWLogoff()
        AssertMessage(self.eventGatewayIsDown.wait(TIMEOUT), "GW disconnection timeout!")

        LOG.info("Done!")

    def OnQueryProgress(self, cqgOrdersQuery, cqgError):
        if cqgError is not None:
            error = win32com.client.Dispatch(cqgError)
            LOG.error("OnQueryProgress - error: Code: %d Description: %s", error.Code, error.Description)
            self.eventOrdersQueryIsReady.set()
            return

        ordersQuery = win32com.client.Dispatch(cqgOrdersQuery)
        LOG.info(
            "OnQueryProgress - account %s has %d order(s)", ordersQuery.Account.GWAccountName, ordersQuery.Orders.Count
        )

        for order in ordersQuery.Orders:
            LOG.info(
                "     ORDER for instrument: %s limit price: %f stop price: %f",
                order.InstrumentName,
                order.LimitPrice,
                order.StopPrice,
            )

        if ordersQuery.Orders.Count == 0 or ordersQuery.LastChunk:
            LOG.info("Orders query is completed!")
            self.eventOrdersQueryIsReady.set()

    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.actPositionAdded:
            account = win32com.client.Dispatch(cqgAccount)
            position = win32com.client.Dispatch(cqgPosition)
            LOG.info(
                "OnAccountChanged - open position is added for %s account - "
                "instrument: %s average price: %f quantity: %d OTE: %f PL: %f Trades:\n%s\n",
                account.GWAccountName,
                position.InstrumentName,
                position.AveragePrice,
                position.Quantity,
                position.OTE,
                position.ProfitLoss,
                self.getTradesTable(position.OpenTrades, position.MatchedTrades),
            )
        elif changeType == constants.actPositionChanged:
            account = win32com.client.Dispatch(cqgAccount)
            position = win32com.client.Dispatch(cqgPosition)
            LOG.info(
                "OnAccountChanged - open position is changed for %s account - "
                "instrument: %s average price: %f quantity: %d OTE: %f PL: %f Trades:\n%s\n",
                account.GWAccountName,
                position.InstrumentName,
                position.AveragePrice,
                position.Quantity,
                position.OTE,
                position.ProfitLoss,
                self.getTradesTable(position.OpenTrades, position.MatchedTrades),
            )
        elif changeType == constants.actAccountsReloaded:
            LOG.info("OnAccountChanged - Accounts are ready!")
            self.eventAccountIsReady.set()

    def getTradesTable(self, openTrades, matchedTrades) -> str:
        headers = ["Date", "Quantity", "Side", "Price", "MatchGroupID"]
        data = [self.getTradeRow(trade) for trade in itertools.chain(openTrades, matchedTrades)]
        return tabulate(data, headers)

    def getTradeRow(self, trade) -> str:
        return [
            trade.TradeDate.date(),
            trade.Quantity,
            "L" if trade.Side == constants.osdBuy else "S",
            trade.Price,
            trade.GroupID if self.celEnvironment.cqgCELForCallback.IsValid(trade.GroupID) else "-",
        ]


# 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
    APIConfiguration.PositionDetailing = constants.pdAllTrades
    # If you do not want to see positions change on each price change then comment the row below
    APIConfiguration.FireEventOnChangedPrices = True


if __name__ == "__main__":
    CELEnvironment.Start(OpenPositionsReceivingSampleNew, CustomAPIConfiguration)
