from ibapi.client import *
from ibapi.wrapper import *
from ibapi.contract import Contract

import pandas as pd
from datetime import datetime
import threading
from time import sleep
from queue import Queue
from tradingapp.orders import Orders


class TradeApp(EWrapper, EClient):
    def __init__(self):
        EClient.__init__(self, self)
        

        # Attributes
        self.account_balances = {
            'TotalCashBalance': None,
            'NetLiquidationByCurrency': None,
        }
        self.next_order_id =  None
        self.portfolio = {}
        self.data_queue = Queue()
        self.scanner_data = []
        self.data_ready = threading.Event()
        self.historical_mkt_data = {}
        self.current_mkt_data = {}
        
    ##########################################################################
    # Connect to TWS and launch threading
    ##########################################################################
    
    def launch(self,DEFAULT_HOST, DEFAULT_TRADING_PORT,DEFAULT_CLIENT_ID):
        self.connect(DEFAULT_HOST, DEFAULT_TRADING_PORT,DEFAULT_CLIENT_ID)
        sleep(1)
        thread = threading.Thread(target=self.run, daemon=True)
        thread.start()
    
    def error(self, reqId: int, errorTime: int, errorCode: int, errorString: str, advancedOrderRejectJson="") -> None:
        print(f"reqId: {reqId}, errorCode: {errorCode}, errorString: {errorString}")
        
    ##########################################################################
    # PORTFOLIO DATA
    ##########################################################################
    def updateAccountValue(self, key: str, val: str, currency: str, accountName:str) -> None:
        result = {}
        if key == 'TotalCashBalance' and currency == 'BASE':
            self.account_balances['TotalCashBalance'] = float(val)
    
        if key == 'NetLiquidationByCurrency' and currency == 'BASE':
            self.account_balances['NetLiquidationByCurrency'] = float(val)
    
    def updatePortfolio(self, contract, position, marketPrice, marketValue, averageCost, unrealizedPNL, realizedPNL, accountName):
        self.portfolio[contract.localSymbol] = {
            "SecType": contract.secType,
            "Exchange": contract.exchange,
            'Position': decimalMaxString(position),
            'MarketPrice': floatMaxString(marketPrice),
            'MarketValue':floatMaxString(marketValue),
            'AverageCost': floatMaxString(averageCost),
            'UnrealizedPNL': floatMaxString(unrealizedPNL),
            'RealizedPNL': floatMaxString(realizedPNL)
            
        }
    # def updateAccountTime(self, timeStamp):
    #     print(f"UpdateAccountTime. Time: {timeStamp}")
    # def accountDownloadEnd(self, accountName):
    #     print(f"AccountDowloadedEnd. AccountName: {accountName}")
        
    ##########################################################################
    # MARKET SCANNERS
    ##########################################################################
    def scannerParameters(self, xml: str):
        open('scanner.xml', 'w').write(xml)
        print("ScannerParameters received.")
    
    def scannerData(self, req_id, rank, details, distance, benchmark, projection, legsStr):
        self.scanner_data.append({
            "symbol": details.contract.symbol,
            "secType": details.contract.secType,
            "exchange": details.contract.exchange,
            "currency": details.contract.currency,
            "rank": rank,
            "distance": distance,
            "projection": projection
        })
        print(f"Scanner: {details.contract.symbol} rank {rank}")
    
    def scannerDataEnd(self, reqId):
        print("ScannerDataEnd")
        self.data_ready.set()
    ##########################################################################
    # NEWS
    ##########################################################################
    def newsProviders(self, newsProviders: ListOfNewsProviders):
        print("NewsProviders: ", newsProviders)

    def tickNews(self, tickerId: int, timeStamp: int, providerCode: str, articleId: str, headline: str, extraData: str):
        print("TickNews. TickerId:", tickerId, "TimeStamp:", timeStamp, "ProviderCode:", providerCode, "ArticleId:", articleId, "Headline:", headline, "ExtraData:", extraData)

    def historicalNews(self, requestId: int, time: int, providerCode: str, articleId: str, headline: str):
        print("historicalNews. RequestId:", requestId, "Time:", time, "ProviderCode:", providerCode, "ArticleId:", articleId, "Headline:", headline)
    
    ##########################################################################
    # MARKET DATA  - CURRENT
    ##########################################################################
    # def realtimeBar(self, reqId: TickerId, time:int, open_: float, high: float, low: float, close: float, volume: Decimal, wap: Decimal, count: int):
    #     print("RealTimeBar. TickerId:", reqId, RealTimeBar(time, -1, open_, high, low, close, volume, wap, count))
    
    def tickPrice(self, reqId, tickType, price, attrib)-> None:
        if tickType == 9: 
            self.current_mkt_data['close_price']= price
        if tickType == 1:
            self.current_mkt_data['bid_price'] = price
        if tickType == 2: 
            self.current_mkt_data['ask_price']= price
        if tickType == 4:
            self.current_mkt_data['last_price']= price
        
    ##########################################################################
    # MARKET DATA  -  HISTORICAL
    ##########################################################################  
    
    def historicalData(self, reqId: int, bar: BarData) -> None:
        data = {
            'reqId': reqId,
            'time': pd.to_datetime(bar.date, unit = "s"),
            'open': bar.open,
            'high': bar.high,
            'low': bar.low,
            'close': bar.close,
            'volume': float(decimalMaxString(bar.volume))
            
        }
        self.data_queue.put(data)

    ##########################################################################
    # ORDERS
    ##########################################################################
    
    def nextValidId(self, orderId):
        self.next_order_id = orderId
    
    @ property
    def get_next_valid_id(self):
        self.next_order_id += 1
        return self.next_order_id
    
    def orderStatus(self, orderId, status, filled, remaining, avgFillPrice,
                    permId, parentId, lastFillPrice, clientId, whyHeld,mktCapPrice):
        print(f"OrderStatus - Id: {orderId}, Status: {status}, "
              f"Filled: {filled}, Remaining: {remaining}, AvgFillPrice: {avgFillPrice}")
    
    def openOrder(self, orderId, contract, order, orderState):
        print(f"OpenOrder - Id: {orderId}, Symbol: {contract.symbol}, Action: {order.action}, "
              f"OrderType: {order.orderType}, TotalQty: {order.totalQuantity}")
    
    def execDetails(self, reqId, contract, execution):
        print(f"ExecDetails - {contract.symbol}, {execution.shares} shares at {execution.price}")
    
    ##########################################################################
    # CUSTOM FUNCTIONS
    ##########################################################################
    def get_historical_data(self, contract: Contract, reqId: int,
                            durationStr: str, barSizeSetting: str, whatToShow: str, **kwargs) -> pd.DataFrame:
        
        self.reqHistoricalData(
            reqId = reqId,
            contract = contract,
            endDateTime = "",
            durationStr = durationStr,
            barSizeSetting = barSizeSetting,
            whatToShow = whatToShow,
            useRTH = 0,
            formatDate = 2,
            keepUpToDate = True,
            chartOptions = [],
            )
        sleep(2)
        return pd.DataFrame(list(self.data_queue.queue))
    
    def get_live_data(self,contract: Contract, reqId: int, **kwargs):
        self.reqMktData(reqId=reqId,contract=contract,
                        genericTickList="",snapshot=False,
                        regulatorySnapshot=False,mktDataOptions=[])
        sleep(1)
        return self.current_mkt_data
    
    def send_mkt_order(self, contract: Contract, order_params: dict) -> dict:
        order = Orders.MarketOrder(**order_params)
        orderId = self.get_next_valid_id
        self.placeOrder(orderId, contract, order)
        return {'order_id': orderId}
    
    def send_limit_order(self, contract: Contract, order_params: dict) -> dict:
        order = Orders.LimitOrder(**order_params)
        orderId = self.get_next_valid_id
        self.placeOrder(orderId, contract, order)
        return {'order_id': orderId}
    
    def send_stop_order(self, contract: Contract, order_params: dict) -> dict:
        order = Orders.Stop(**order_params)
        orderId = self.get_next_valid_id
        self.placeOrder(orderId, contract, order)
        return {'order_id': orderId}
 

    