from enum import Enum
from typing import List

from bfxapi.rest.bfx_rest import BfxRest
from retrying_async import retry

from bfxbot.currency import Symbol


class BalanceKind(Enum):
    EXCHANGE = "exchange",
    MARGIN = "margin"

    @staticmethod
    def from_str(string: str):
        string = string.lower()

        if "margin" in string:
            return BalanceKind.MARGIN
        if "exchange" in string:
            return BalanceKind.EXCHANGE

        return None


class Balance:
    def __init__(self, currency: str, amount: int, kind: BalanceKind):
        self.currency: str = currency
        self.amount: int = amount
        self.kind: BalanceKind = kind


class Direction(Enum):
    UP = 1,
    DOWN = -1


class OrderType(Enum):
    EXCHANGE = "EXCHANGE",
    MARGIN = "MARGIN"


class BfxWrapper(BfxRest):
    def __init__(self, api_key: str, api_secret: str):
        super().__init__(API_KEY=api_key, API_SECRET=api_secret)

    #######################################
    # OVERRIDDEN METHODS TO IMPLEMENT RETRY
    #######################################

    @retry()
    async def get_public_ticker(self, symbol):
        if isinstance(symbol, Symbol):
            symbol = str(symbol)

        return await super().get_public_ticker(symbol)

    @retry()
    async def get_active_position(self):
        return await super().get_active_position()

    @retry()
    async def get_active_orders(self, symbol):
        if isinstance(symbol, Symbol):
            symbol = str(symbol)

        return await super().get_active_orders(symbol)

    @retry()
    async def get_trades(self, symbol, start, end):
        if isinstance(symbol, Symbol):
            symbol = str(symbol)

        return await super().get_trades(symbol, start, end)

    @retry()
    async def post(self, endpoint: str, data: dict = {}, params=""):
        return await super().post(endpoint, data, params)

    ################################
    # NEW METHODS
    ################################

    async def get_current_prices(self, symbol) -> (float, float, float):
        if isinstance(symbol, Symbol):
            symbol = str(symbol)

        tickers = await self.get_public_ticker(symbol)

        bid_price = tickers[0]
        ask_price = tickers[2]
        ticker_price = tickers[6]

        return bid_price, ask_price, ticker_price

    async def get_usd_balance(self):
        balance = 0.0

        wallets = await self.get_wallets()

        for w in wallets:
            if w.currency == "USD":
                balance += w.balance
            else:
                current_price = await self.get_current_prices(f"t{w.currency}USD")
                balance += current_price * w.balance

        return balance

    async def get_balances(self) -> List[Balance]:
        balances = []
        wallets = await self.get_wallets()

        for w in wallets:
            kind = BalanceKind.from_str(w.type)

            if not kind:
                continue

            balances.append(Balance(w.currency, w.balance, kind))

        return balances

    async def maximum_order_amount(self, symbol: Symbol, direction: Direction,
                                   order_type: OrderType = OrderType.EXCHANGE,
                                   rate: int = 1):
        api_path = "auth/calc/order/avail"

        return await self.post(api_path,
                               {'symbol': str(symbol), 'type': order_type.value, "dir": direction.value, "rate": rate})