"""The sample demonstrates how to request open positions statement for a date. CQG IC must be logged to GW first."""

import logging
import threading
from datetime import datetime, timezone

import win32com.client
from win32com.client import constants

import CELEnvironment
from CELEnvironment import AssertMessage

#region Constants

# Special values to request position statement for current day.
CURRENT_DAY = datetime(year=3000, month=1, day=1, tzinfo=timezone.utc)

#endregion

#region Settings

# Replace it with real login before run
LOGIN = "your_gw_login"
PASSWORD = "your_gw_password"

# Replace with some day you want to request position statement for.
POSITION_STATEMENT_DAY = CURRENT_DAY

# 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


def StatementDateToStr(statementDate: datetime) -> str:
    """Represents statement date value as string.

    Args:
        statementDate (datetime): Date positions statement were requested for.

    Returns:
        str: Date value as string or "Current Day" if `statementDate` equals to `CURRENT_DAY`.
    """
    return "Current Day" if statementDate == CURRENT_DAY else str(statementDate)


class QueryPositionsStatementSample(CELEnvironment.CELSinkBase):
    def __init__(self):
        self.celEnvironment = None
        self.eventGatewayIsUp = threading.Event()
        self.eventGatewayIsDown = threading.Event()
        self.account = None
        self.eventAccountIsReady = threading.Event()
        self.eventPositionsStatementReady = 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.aslNone
        self.celEnvironment.cqgCEL.AccountSubscriptionLevel = constants.aslAccountsOnly
        LOG.info("Waiting for accounts coming...")
        AssertMessage(self.eventAccountIsReady.wait(TIMEOUT), "Accounts coming timeout!")

        accounts = win32com.client.Dispatch(self.celEnvironment.cqgCEL.Accounts)
        self.account = win32com.client.Dispatch(accounts.ItemByIndex(0))
        for account in accounts:
            LOG.info("GW Account id: %d name: %s", account.GWAccountID, account.GWAccountName)

        LOG.info("Request positions statement for GW account ID = %d for date: %s",
                 self.account.GWAccountID, StatementDateToStr(POSITION_STATEMENT_DAY))

        self.celEnvironment.cqgCEL.QueryPositionsStatement(self.account.GWAccountID, POSITION_STATEMENT_DAY)

        LOG.info("Waiting for positions statement coming...")
        AssertMessage(self.eventPositionsStatementReady.wait(TIMEOUT), "Positions statement coming 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 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 OnPositionsStatementResolved(self, cqgPositionsStatement, cqgError):
        if cqgError is not None:
            error = win32com.client.Dispatch(cqgError)
            LOG.error("OnPositionsStatementResolved error: Code: %d Description: %s", error.Code, error.Description)
            self.eventPositionsStatementReady.set()
            return

        positionsStatement = win32com.client.Dispatch(cqgPositionsStatement)
        LOG.info("Positions statement for account: %d for date: %s received",
                 positionsStatement.GWAccountID, StatementDateToStr(positionsStatement.StatementDate))

        for position in positionsStatement.Positions:
            LOG.info("  open position for instrument: %s with average price: %f quantity: %d",
                     position.InstrumentName, position.AveragePrice, position.Quantity)

        self.eventPositionsStatementReady.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(QueryPositionsStatementSample, CustomAPIConfiguration)
