"""The sample demonstrates how to request options chain by commodity and output quotes."""

import logging
import threading
from datetime import datetime

from dateutil.relativedelta import relativedelta
from tabulate import tabulate
from win32com.client import Dispatch, constants

import CELEnvironment
from CELEnvironment import AssertMessage

# region Settings

# Replace it with enabled symbol
COMMODITY = "SO3"

# Possible filters: only calls (itOptionCall), only puts (itOptionPut) or both (itAllOptions).
TYPE: int = constants.itOptionCall

# First option month
RANGE_START = datetime.today()
# Last option month
RANGE_END = RANGE_START + relativedelta(months=3)

# how many strikes (rows) to show
CHUNK_SIZE = 20

# show strikes that equal or more to the provided one:
START_WITH_STRIKE = 95_00

# quotes to show in the output
QUOTE_TYPES = [constants.qtYesterdayExchangeClose, constants.qtYesterdaySettlement]

# the value for CQGInstrumentsGroupRequest.SubscriptionLevel property; shall be eGroupSubscriptionLevel enum value
SUBSCRIPTION_LEVEL = constants.gslFullUpdates

# 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 InstrumentsGroupRequest(CELEnvironment.CELSinkBase):
    def __init__(self):
        self.eventDone = threading.Event()
        self.instruments = None
        self.lastStrike = START_WITH_STRIKE
        self.quoteNames = {}

    def Init(self, celEnvironment):
        self.celEnvironment: CELEnvironment.CELEnvironment = celEnvironment

    def Start(self):
        request = Dispatch(self.celEnvironment.cqgCEL.CreateInstrumentsGroupRequest())
        request.Commodity = COMMODITY
        request.SubscriptionLevel = SUBSCRIPTION_LEVEL
        # next 3 lines are optional, see default values on CQGInstrumentsGroupRequest doc page
        request.InstrumentTypes = TYPE
        request.RangeStart = RANGE_START
        request.RangeEnd = RANGE_END

        LOG.info("Request options chain for %s", COMMODITY)
        self.celEnvironment.cqgCEL.RequestInstrumentsGroup(request, False)
        AssertMessage(self.eventDone.wait(300000), "Timeout on instruments group resolution")
        if self.celEnvironment.cqgCEL.AllInstrumentsGroups.Count == 0:
            # resolution failed, error is reported in OnInstrumentsGroupResolved
            return

        input()

    def OnDataError(self, cqgError, errorDescription):
        if cqgError is not None:
            error = Dispatch(cqgError)
            LOG.error("OnDataError: Code: %d Description: %s", error.Code, error.Description)

        self.eventDone.set()

    def OnInstrumentsGroupResolved(self, cqgInstrumentsGroup, cqgError):
        if cqgError is not None:
            error = Dispatch(cqgError)
            LOG.error("OnInstrumentsGroupResolved error: Error code: %d Description: %s", error.Code, error.Description)
            self.eventDone.set()
            return

        instrumentGroup = Dispatch(cqgInstrumentsGroup)
        LOG.info("Resolved %d contracts.", instrumentGroup.Instruments.Count)

        self.instruments = [i for i in instrumentGroup.Instruments if i.Strike >= START_WITH_STRIKE][:CHUNK_SIZE]
        self.lastStrike = self.instruments[-1].Strike
        self.quoteNames = {quoteType: self.instruments[0].Quotes.Item(quoteType).Name for quoteType in QUOTE_TYPES}
        self.PrintQuotes()
        self.eventDone.set()

    def OnInstrumentsGroupChanged(self, changeType, cqgInstrumentsGroup, instrumentsNames):
        LOG.info(
            "OnInstrumentsGroupChanged: changeType=%d. Instruments that changed: %s", changeType, list(instrumentsNames)
        )

    def OnInstrumentChanged(self, cqgInstrument, cqgQuotes, cqgInstrumentProperties):
        if not self.eventDone.is_set():
            # ignore updates until we resolve the whole group
            return

        instrument = Dispatch(cqgInstrument)
        if not START_WITH_STRIKE <= instrument.Strike <= self.lastStrike:
            # the strike is not in our tracked range
            return

        quotes = Dispatch(cqgQuotes)
        if any(quote.Type in QUOTE_TYPES for quote in quotes):
            self.PrintQuotes()

    def PrintQuotes(self):
        def _getDisplayValue(value):
            return value if self.celEnvironment.cqgCELForCallback.IsValid(value) else "N/A"

        data = {"Contract": [instrument.FullName for instrument in self.instruments]}

        for quoteType in QUOTE_TYPES:
            data[f"{self.quoteNames[quoteType]} Time"] = [
                _getDisplayValue(i.Quotes.Item(quoteType).ServerTimestamp) for i in self.instruments
            ]
            data[f"{self.quoteNames[quoteType]} Price"] = [
                _getDisplayValue(i.Quotes.Item(quoteType).Price) for i in self.instruments
            ]

            if any(i.Quotes.Item(quoteType).HasVolume for i in self.instruments):
                data[f"{self.quoteNames[quoteType]} Volume"] = [
                    _getDisplayValue(i.Quotes.Item(quoteType).Volume) for i in self.instruments
                ]

        LOG.info("Quotes:\n%s\nPress ENTER to exit\n", tabulate(data, headers=data.keys()))


if __name__ == "__main__":
    CELEnvironment.Start(InstrumentsGroupRequest)
