
import pandas as pd
import requests
from typing import Tuple, Union
import numpy as np

class BrokerPackage:
    def __init__(self, username:str) -> None:
        self.default_url = 'http://broker-api-service:7777/'
        self.file = {}
        self.get_file()

        if 'primary_url' in self.file.keys():
            self.primary_url = self.file['primary_url']
        else: self.primary_url = self.default_url

        if 'secondary_url' in self.file.keys():
            self.secondary_url = self.file['secondary_url']
        else: self.secondary_url = self.default_url

        self.quotetickcandle_url = 'http://quotetickcandle-secvice:7777/quotedata'
        self.username = username

    def get_file(self) -> None:
        data_inp = {'username': self.username}
        res = requests.post(self.default_url + 'get_file', json = data_inp)
        res_json = res.json()
        if res_json['error'] != '': raise Exception(res_json['error'])
        self.file = res_json['file']

    def login(self) -> None:
        data_inp = {'file': self.file}
        res = requests.post(self.primary_url + 'login', json = data_inp)
        res_json = res.json()
        if res_json['error'] != '': raise Exception(res_json['error'])

    def get_token(self, name:str, exchange:str = 'NSE', expiry:str = '', strike:str = '', optionType:str = '') -> str:
        data_inp = {'username': self.username,
                    'name': name,
                    'exchange': exchange,
                    'expiry': expiry,
                    'strike': strike,
                    'optionType': optionType
                    }
        try:
            res = requests.post(self.primary_url + 'get_token', json = data_inp)
        except:
            res = requests.post(self.secondary_url + 'get_token', json = data_inp)
        res_json = res.json()
        if res_json['error'] != '': raise Exception(res_json['error'])
        return res_json['token']

    def get_name(self, token:str) -> Tuple[str, str, str, str, str]:
        data_inp = {'username': self.username,
                    'token': token
                    }
        try:
            res = requests.post(self.primary_url + 'get_name', json = data_inp)
        except:
            res = requests.post(self.secondary_url + 'get_name', json = data_inp)
        res_json = res.json()
        if res_json['error'] != '': raise Exception(res_json['error'])
        return res_json['data']['name'], res_json['data']['expiry'], res_json['data']['strike'], res_json['data']['optionType'], res_json['data']['lots']

    def orders(self) -> pd.DataFrame:
        data_inp = {'username': self.username}
        try:
            res = requests.post(self.primary_url + 'orders', json = data_inp)
        except:
            res = requests.post(self.secondary_url + 'orders', json = data_inp)
        res_json = res.json()
        if res_json['error'] != '': raise Exception(res_json['error'])
        return pd.DataFrame.from_dict(res_json['data'])

    def order_update_time(self) -> Union[int, str]:
        data_inp = {'username': self.username}
        try:
            res = requests.post(self.primary_url + 'order_update_time', json = data_inp)
        except:
            res = requests.post(self.secondary_url + 'order_update_time', json = data_inp)
        res_json = res.json()
        if res_json['error'] != '': raise Exception(res_json['error'])
        return res_json['order_update_time']

    def positions(self) -> pd.DataFrame:
        data_inp = {'username': self.username}
        try:
            res = requests.post(self.primary_url + 'positions', json = data_inp)
        except:
            res = requests.post(self.secondary_url + 'positions', json = data_inp)
        res_json = res.json()
        if res_json['error'] != '': raise Exception(res_json['error'])
        return pd.DataFrame.from_dict(res_json['data'])

    def position_update_time(self) -> Union[int, str]:
        data_inp = {'username': self.username}
        try:
            res = requests.post(self.primary_url + 'position_update_time', json = data_inp)
        except:
            res = requests.post(self.secondary_url + 'position_update_time', json = data_inp)
        res_json = res.json()
        if res_json['error'] != '': raise Exception(res_json['error'])
        return res_json['position_update_time']

    def portfolio(self) -> pd.DataFrame:
        data_inp = {'username': self.username}
        try:
            res = requests.post(self.primary_url + 'portfolio', json = data_inp)
        except:
            res = requests.post(self.secondary_url + 'portfolio', json = data_inp)
        res_json = res.json()
        if res_json['error'] != '': raise Exception(res_json['error'])
        return pd.DataFrame.from_dict(res_json['data'])

    def get_available_cash(self) -> float:
        data_inp = {'username': self.username}
        try:
            res = requests.post(self.primary_url + 'get_available_cash', json = data_inp)
        except:
            res = requests.post(self.secondary_url + 'get_available_cash', json = data_inp)
        res_json = res.json()
        if res_json['error'] != '': raise Exception(res_json['error'])
        return res_json['available_cash']

    def get_required_margin(self, transaction_type:str = '', token:str = '', price_:float = 0, product:str = '') -> float:
        data_inp = {'username': self.username,
                    'transaction_type': transaction_type,
                    'token': token,
                    'price_': price_,
                    'product': product
                    }
        try:
            res = requests.post(self.primary_url + 'get_required_margin', json = data_inp)
        except:
            res = requests.post(self.secondary_url + 'get_required_margin', json = data_inp)
        res_json = res.json()
        if res_json['error'] != '': raise Exception(res_json['error'])
        return res_json['required_margin']

    def get_quote(self, token:str = '', name:str = '', exchange:str = 'NSE', expiry:str = '', strike:str = '', optionType:str = '') -> pd.DataFrame:
        data_inp = {'username': self.username,
                    'token': token,
                    'name': name,
                    'exchange': exchange,
                    'expiry': expiry,
                    'strike': strike,
                    'optionType': optionType
                    }
        try:
            res = requests.post(self.primary_url + 'get_quote', json = data_inp)
        except:
            res = requests.post(self.secondary_url + 'get_quote', json = data_inp)
        res_json = res.json()
        if res_json['error'] != '': raise Exception(res_json['error'])
        return pd.DataFrame.from_dict(res_json['data'])

    def place_order(self, transaction_type:str, price_:float, quantity:int, token:str = '', name:str = '', exchange:str = 'NSE', expiry:str = '', strike:str = '', optionType:str = '',  trigger:float = 0, product:str = '') -> str:
        data_inp = {'username': self.username,
                    'transaction_type': transaction_type,
                    'price_': price_,
                    'quantity': quantity,
                    'token': token,
                    'name': name,
                    'exchange': exchange,
                    'expiry': expiry,
                    'strike': strike,
                    'optionType': optionType,
                    'trigger': trigger,
                    'product': product
                    }
        try:
            res = requests.post(self.primary_url + 'place_order', json = data_inp)
        except:
            res = requests.post(self.secondary_url + 'place_order', json = data_inp)
        res_json = res.json()
        if res_json['error'] != '': raise Exception(res_json['error'])
        return res_json['orderid']

    def modify_order(self, order_id:str,  price:float, quantity:Union[int, str] = '', trigger:float = 0) -> str:
        data_inp = {'username': self.username,
                    'order_id': order_id,
                    'price': price,
                    'quantity': quantity,
                    'trigger': trigger
                    }
        try:
            res = requests.post(self.primary_url + 'modify_order', json = data_inp)
        except:
            res = requests.post(self.secondary_url + 'modify_order', json = data_inp)
        res_json = res.json()
        if res_json['error'] != '': raise Exception(res_json['error'])
        return res_json['orderid']

    def cancel_order(self, order_id:str) -> str:
        data_inp = {'username': self.username,
                    'order_id': order_id
                    }
        try:
            res = requests.post(self.primary_url + 'cancel_order', json = data_inp)
        except:
            res = requests.post(self.secondary_url + 'cancel_order', json = data_inp)
        res_json = res.json()
        if res_json['error'] != '': raise Exception(res_json['error'])
        return res_json['orderid']

    def get_quote_local(self, name = '', exchange = 'NSE', expiry = '', strike = '', optionType = ''):
        data_inp = {'name': name,
                'exchange': exchange,
                'expiry': expiry,
                'strike': str(strike),
                'optionType': optionType,            
                }
        try:
            res = requests.post(self.quotetickcandle_url, json = data_inp)
            df = pd.json_normalize(res.json())
            if df.ltp.iloc[0] == '': raise Exception("Unable to get quote from quotes api")
            else: return df
        except Exception as e:
            raise Exception("Unable to get quote from quotes api")

    def get_quote_api(self, name = '', exchange = 'NSE', expiry = '', strike = '', optionType = ''):
        try:
            df = self.get_quote_local(name, exchange, expiry, strike, optionType)
            return df
        except Exception as e:
            return self.get_quote(name=name, exchange=exchange, expiry=expiry, strike=strike, optionType=optionType)

    def get_ltp(self, name='', exchange='NSE', expiry='', strike='', optionType=''):
        return self.round_to(self.get_quote_api(name=name, exchange=exchange, expiry=expiry, strike=strike, optionType=optionType).iloc[0]['ltp'])

    def round_to(self, row, num_column = 'open', precision_column_val=.05):
        ''' Round given number to nearsest tick size which is .05 in FNO
            Source: https://stackoverflow.com/questions/4265546/python-round-to-nearest-05

            Parameters
            ----------
                row: int/float/Series - 12569.67 or Series with columns __ num_column, optional __ precision_column_val
                num_column: str - 'date' Column name to round
                precision_column_val: float, int, str 100, 2.5 or 'roundvalue' tick_size of the item or  Column name contaning tick_size

        '''
        if type(row) in [int, float, np.float64, np.float32, np.float16, np.int8, np.int16, np.int32, np.int64, str]:
            n = row
        else:
            n = row[num_column]

        if type(precision_column_val)==str:
            if type(row) == int or type(row) == float:
                precision = .05
            else:
                precision = row[precision_column_val]
        else:
            precision = precision_column_val

        n = float(n)
        correction = 0.5 if n >= 0 else -0.5
        return round(int( n/precision+correction ) * precision, 2)