# -*- coding: utf-8 -*-

# PLEASE DO NOT EDIT THIS FILE, IT IS GENERATED AND WILL BE OVERWRITTEN:
# https://github.com/ccxt/ccxt/blob/master/CONTRIBUTING.md#how-to-contribute-code


import base64
import hashlib
import hmac
import time

from ccxt.base.exchange import Exchange

# -----------------------------------------------------------------------------

try:
    basestring  # Python 3
except NameError:
    basestring = str  # Python 2
# from ccxt.base.errors import ExchangeError
from ccxt.base.errors import BadSymbol


# from ccxt.base.errors import DDoSProtection


class whitebit(Exchange):

    def describe(self):
        return self.deep_extend(super(whitebit, self).describe(), {
            'id': 'whitebit',
            'name': 'WhiteBit',
            'version': 'v2',
            'countries': ['EE'],
            'rateLimit': 500,
            'has': {
                'cancelOrder': False,
                'CORS': False,
                'createDepositAddress': False,
                'createLimitOrder': False,
                'createMarketOrder': False,
                'createOrder': True,
                'deposit': False,
                'editOrder': False,
                'fetchBalance': True,
                'fetchBidsAsks': False,
                'fetchCurrencies': True,
                'fetchMarkets': True,
                'fetchOHLCV': True,
                'fetchOrderBook': True,
                'fetchStatus': True,
                'fetchTicker': True,
                'fetchTickers': True,
                'fetchTrades': True,
                'privateAPI': False,
                'publicAPI': True,
            },
            'timeframes': {
                '1m': '1m',
                '3m': '3m',
                '5m': '5m',
                '15m': '15m',
                '30m': '30m',
                '1h': '1h',
                '2h': '2h',
                '4h': '4h',
                '6h': '6h',
                '8h': '8h',
                '12h': '12h',
                '1d': '1d',
                '3d': '3d',
                '1w': '1w',
                '1M': '1M',
            },
            'urls': {
                'logo': 'https://user-images.githubusercontent.com/1294454/66732963-8eb7dd00-ee66-11e9-849b-10d9282bb9e0.jpg',
                'api': {
                    'web': 'https://whitebit.com/',
                    'publicV2': 'https://whitebit.com/api/v2/public',
                    'publicV1': 'https://whitebit.com/api/v1/public',
                    'privateV4': 'https://whitebit.com/api/v4'
                },
                'www': 'https://www.whitebit.com',
                'doc': 'https://documenter.getpostman.com/view/7473075/SVSPomwS?version=latest#intro',
                'fees': 'https://whitebit.com/fee-schedule',
                'referral': 'https://whitebit.com/referral/d9bdf40e-28f2-4b52-b2f9-cd1415d82963',
            },
            'api': {
                'web': {
                    'get': [
                        'v1/healthcheck',
                    ],
                },
                'publicV1': {
                    'get': [
                        'markets',
                        'tickers',
                        'ticker',
                        'symbols',
                        'depth/result',
                        'history',
                        'kline',
                    ],
                },
                'publicV2': {
                    'get': [
                        'markets',
                        'ticker',
                        'assets',
                        'fee',
                        'depth/{market}',
                        'trades/{market}',
                    ],
                },
                'privateV4': {
                    'post': [
                        'trade-account/balance',                # Trade balance
                        'order/new',                            # Create order
                        'order/cancel',                         # Cancel order
                        'orders',                               # Open orders
                        'trade-account/executed-history',       # Closed orders

                        # 'trade-account/order',                  # Deals/trades for order
                        # 'trade-account/order/history',          # Deals/trades for market
                    ],
                },
            },
            'fees': {
                'trading': {
                    'tierBased': False,
                    'percentage': True,
                    'taker': 0.001,
                    'maker': 0.001,
                },
            },
            'options': {
                'fetchTradesMethod': 'fetchTradesV2',
            },
            'exceptions': {
                'exact': {
                },
                'broad': {
                    'Market is not available': BadSymbol,
                    # {"success":false,"message":{"market":["Market is not available"]},"result":[]}
                },
            },
        })

    def fetch_markets(self, params={}):
        response = self.publicV2GetMarkets(params)
        #
        #     {
        #         "success":true,
        #         "message":"",
        #         "result":[
        #             {
        #                 "name":"BTC_USD",
        #                 "moneyPrec":"2",
        #                 "stock":"BTC",
        #                 "money":"USD",
        #                 "stockPrec":"6",
        #                 "feePrec":"4",
        #                 "minAmount":"0.001",
        #                 "tradesEnabled":true,
        #                 "minTotal":"0.001"
        #             }
        #         ]
        #     }
        #
        markets = self.safe_value(response, 'result')
        result = []
        for i in range(0, len(markets)):
            market = markets[i]
            id = self.safe_string(market, 'name')
            baseId = self.safe_string(market, 'stock')
            quoteId = self.safe_string(market, 'money')
            base = self.safe_currency_code(baseId)
            quote = self.safe_currency_code(quoteId)
            symbol = base + '/' + quote
            active = self.safe_value(market, 'tradesEnabled')
            entry = {
                'id': id,
                'symbol': symbol,
                'base': base,
                'quote': quote,
                'baseId': baseId,
                'quoteId': quoteId,
                'info': market,
                'active': active,
                'precision': {
                    'amount': self.safe_integer(market, 'stockPrec'),
                    'price': self.safe_integer(market, 'moneyPrec'),
                },
                'limits': {
                    'amount': {
                        'min': self.safe_float(market, 'minAmount'),
                        'max': None,
                    },
                    'price': {
                        'min': None,
                        'max': None,
                    },
                    'cost': {
                        'min': self.safe_float(market, 'minTotal'),
                        'max': None,
                    },
                },
            }
            result.append(entry)
        return result

    def fetch_currencies(self, params={}):
        response = self.publicV2GetAssets(params)
        #
        #     {
        #         "success":true,
        #         "message":"",
        #         "result":{
        #             "BTC":{
        #                 "id":"4f37bc79-f612-4a63-9a81-d37f7f9ff622",
        #                 "lastUpdateTimestamp":"2019-10-12T04:40:05.000Z",
        #                 "name":"Bitcoin",
        #                 "canWithdraw":true,
        #                 "canDeposit":true,
        #                 "minWithdrawal":"0.001",
        #                 "maxWithdrawal":"0",
        #                 "makerFee":"0.1",
        #                 "takerFee":"0.1"
        #             }
        #         }
        #     }
        #
        currencies = self.safe_value(response, 'result')
        ids = list(currencies.keys())
        result = {}
        for i in range(0, len(ids)):
            id = ids[i]
            currency = currencies[id]
            # breaks down in Python due to utf8 encoding issues on the exchange side
            # name = self.safe_string(currency, 'name')
            canDeposit = self.safe_value(currency, 'canDeposit', True)
            canWithdraw = self.safe_value(currency, 'canWithdraw', True)
            active = canDeposit and canWithdraw
            code = self.safe_currency_code(id)
            result[code] = {
                'id': id,
                'code': code,
                'info': currency,  # the original payload
                'name': None,  # see the comment above
                'active': active,
                'fee': None,
                'precision': None,
                'limits': {
                    'amount': {
                        'min': None,
                        'max': None,
                    },
                    'price': {
                        'min': None,
                        'max': None,
                    },
                    'cost': {
                        'min': None,
                        'max': None,
                    },
                    'withdraw': {
                        'min': self.safe_float(currency, 'minWithdrawal'),
                        'max': self.safe_float(currency, 'maxWithdrawal'),
                    },
                },
            }
        return result

    def fetch_trading_fees(self, params={}):
        response = self.publicV2GetFee(params)
        fees = self.safe_value(response, 'result')
        return {
            'maker': self.safe_float(fees, 'makerFee'),
            'taker': self.safe_float(fees, 'takerFee'),
        }

    def fetch_ticker(self, symbol, params={}):
        self.load_markets()
        market = self.market(symbol)
        request = {
            'market': market['id'],
        }
        response = self.publicV1GetTicker(self.extend(request, params))
        #
        #     {
        #         "success":true,
        #         "message":"",
        #         "result": {
        #             "bid":"0.021979",
        #             "ask":"0.021996",
        #             "open":"0.02182",
        #             "high":"0.022039",
        #             "low":"0.02161",
        #             "last":"0.021987",
        #             "volume":"2810.267",
        #             "deal":"61.383565474",
        #             "change":"0.76",
        #         },
        #     }
        #
        ticker = self.safe_value(response, 'result', {})
        return self.parse_ticker(ticker, market)

    def parse_ticker(self, ticker, market=None):
        #
        # fetchTicker
        #
        #     {
        #         "bid":"0.021979",
        #         "ask":"0.021996",
        #         "open":"0.02182",
        #         "high":"0.022039",
        #         "low":"0.02161",
        #         "last":"0.021987",
        #         "volume":"2810.267",
        #         "deal":"61.383565474",
        #         "change":"0.76",
        #     }
        #
        # fetchTickers v1
        #
        #     {
        #         "at":1571022144,
        #         "ticker": {
        #             "bid":"0.022024",
        #             "ask":"0.022042",
        #             "low":"0.02161",
        #             "high":"0.022062",
        #             "last":"0.022036",
        #             "vol":"2813.503",
        #             "deal":"61.457279261",
        #             "change":"0.95"
        #         }
        #     }
        #
        timestamp = self.safe_timestamp(ticker, 'at', self.milliseconds())
        ticker = self.safe_value(ticker, 'ticker', ticker)
        symbol = None
        if market is not None:
            symbol = market['symbol']
        last = self.safe_float(ticker, 'last')
        percentage = self.safe_float(ticker, 'change')
        change = None
        if percentage is not None:
            change = self.number_to_string(percentage * 0.01)
        return {
            'symbol': symbol,
            'timestamp': timestamp,
            'datetime': self.iso8601(timestamp),
            'high': self.safe_float(ticker, 'high'),
            'low': self.safe_float(ticker, 'low'),
            'bid': self.safe_float(ticker, 'bid'),
            'bidVolume': None,
            'ask': self.safe_float(ticker, 'ask'),
            'askVolume': None,
            'vwap': None,
            'open': self.safe_float(ticker, 'open'),
            'close': last,
            'last': last,
            'previousClose': None,
            'change': change,
            'percentage': percentage,
            'average': None,
            'baseVolume': self.safe_float(ticker, 'volume'),
            'quoteVolume': self.safe_float(ticker, 'deal'),
            'info': ticker,
        }

    def fetch_tickers(self, symbols=None, params={}):
        self.load_markets()
        response = self.publicV1GetTickers(params)
        #
        #     {
        #         "success":true,
        #         "message":"",
        #         "result": {
        #             "ETH_BTC": {
        #                 "at":1571022144,
        #                 "ticker": {
        #                     "bid":"0.022024",
        #                     "ask":"0.022042",
        #                     "low":"0.02161",
        #                     "high":"0.022062",
        #                     "last":"0.022036",
        #                     "vol":"2813.503",
        #                     "deal":"61.457279261",
        #                     "change":"0.95"
        #                 }
        #             },
        #         },
        #     }
        #
        data = self.safe_value(response, 'result')
        marketIds = list(data.keys())
        result = {}
        for i in range(0, len(marketIds)):
            marketId = marketIds[i]
            market = None
            symbol = marketId
            if marketId in self.markets_by_id:
                market = self.markets_by_id[marketId]
                symbol = market['symbol']
            else:
                baseId, quoteId = marketId.split('_')
                base = self.safe_currency_code(baseId)
                quote = self.safe_currency_code(quoteId)
                symbol = base + '/' + quote
            ticker = self.parse_ticker(data[marketId], market)
            result[symbol] = self.extend(ticker, {'symbol': symbol})
        return self.filter_by_array(result, 'symbol', symbols)

    def fetch_order_book(self, symbol, limit=None, params={}):
        self.load_markets()
        market = self.market(symbol)
        request = {
            'market': market['id'],
        }
        if limit is not None:
            request['limit'] = limit  # default = 50, maximum = 100
        response = self.publicV2GetDepthMarket(self.extend(request, params))
        #
        #     {
        #         "success":true,
        #         "message":"",
        #         "result":{
        #             "lastUpdateTimestamp":"2019-10-14T03:15:47.000Z",
        #             "asks":[
        #                 ["0.02204","2.03"],
        #                 ["0.022041","2.492"],
        #                 ["0.022042","2.254"],
        #             ],
        #             "bids":[
        #                 ["0.022018","2.327"],
        #                 ["0.022017","1.336"],
        #                 ["0.022015","2.089"],
        #             ],
        #         }
        #     }
        #
        result = self.safe_value(response, 'result', {})
        timestamp = self.parse8601(self.safe_string(result, 'lastUpdateTimestamp'))
        return self.parse_order_book(result, timestamp)

    def fetch_trades_v1(self, symbol, since=None, limit=None, params={}):
        self.load_markets()
        market = self.market(symbol)
        request = {
            'market': market['id'],
            'lastId': 1,  # todo add since
        }
        if limit is not None:
            request['limit'] = limit  # default = 50, maximum = 10000
        response = self.publicV1GetHistory(self.extend(request, params))
        #
        #     {
        #         "success":true,
        #         "message":"",
        #         "result":[
        #             {
        #                 "id":11887426,
        #                 "type":"buy",
        #                 "time":1571023057.413769,
        #                 "amount":"0.171",
        #                 "price":"0.022052"
        #             }
        #         ],
        #     }
        #
        result = self.safe_value(response, 'result', [])
        return self.parse_trades(result, market, since, limit)

    def fetch_trades_v2(self, symbol, since=None, limit=None, params={}):
        self.load_markets()
        market = self.market(symbol)
        request = {
            'market': market['id'],
        }
        if limit is not None:
            request['limit'] = limit  # default = 50, maximum = 10000
        response = self.publicV2GetTradesMarket(self.extend(request, params))
        #
        #     {
        #         "success":true,
        #         "message":"",
        #         "result": [
        #             {
        #                 "tradeId":11903347,
        #                 "price":"0.022044",
        #                 "volume":"0.029",
        #                 "time":"2019-10-14T06:30:57.000Z",
        #                 "isBuyerMaker":false
        #             },
        #         ],
        #     }
        #
        result = self.safe_value(response, 'result', [])
        return self.parse_trades(result, market, since, limit)

    def fetch_trades(self, symbol, since=None, limit=None, params={}):
        method = self.safe_string(self.options, 'fetchTradesMethod', 'fetchTradesV2')
        return getattr(self, method)(symbol, since, limit, params)

    def parse_trade(self, trade, market=None):
        #
        # fetchTradesV1
        #
        #     {
        #         "id":11887426,
        #         "type":"buy",
        #         "time":1571023057.413769,
        #         "amount":"0.171",
        #         "price":"0.022052"
        #     }
        #
        # fetchTradesV2
        #
        #     {
        #         "tradeId":11903347,
        #         "price":"0.022044",
        #         "volume":"0.029",
        #         "time":"2019-10-14T06:30:57.000Z",
        #         "isBuyerMaker":false
        #     }
        #
        timestamp = self.safe_value(trade, 'time')
        if isinstance(timestamp, basestring):
            timestamp = self.parse8601(timestamp)
        else:
            timestamp = int(timestamp * 1000)
        price = self.safe_float(trade, 'price')
        amount = self.safe_float_2(trade, 'amount', 'volume')
        id = self.safe_string_2(trade, 'id', 'tradeId')
        side = self.safe_string(trade, 'type')
        if side is None:
            isBuyerMaker = self.safe_value(trade, 'isBuyerMaker')
            side = 'buy' if isBuyerMaker else 'sell'
        symbol = None
        if market is not None:
            symbol = market['symbol']
        cost = None
        if amount is not None and price is not None:
            cost = amount * price
        return {
            'info': trade,
            'timestamp': timestamp,
            'datetime': self.iso8601(timestamp),
            'symbol': symbol,
            'id': id,
            'order': None,
            'type': None,
            'takerOrMaker': None,
            'side': side,
            'price': price,
            'amount': amount,
            'cost': cost,
            'fee': None,
        }

    def fetch_ohlcv(self, symbol, timeframe='1m', since=None, limit=None, params={}):
        self.load_markets()
        market = self.market(symbol)
        request = {
            'market': market['id'],
            'interval': self.timeframes[timeframe],
        }
        if since is not None:
            request['start'] = int(since / 1000)
        if limit is not None:
            request['limit'] = limit  # default == max == 500
        response = self.publicV1GetKline(self.extend(request, params))
        result = self.safe_value(response, 'result')
        return self.parse_ohlcvs(result, market, timeframe, since, limit)

    def parse_ohlcv(self, ohlcv, market=None, timeframe='1m', since=None, limit=None):
        return [
            ohlcv[0] * 1000,  # timestamp
            float(ohlcv[1]),  # open
            float(ohlcv[3]),  # high
            float(ohlcv[4]),  # low
            float(ohlcv[2]),  # close
            float(ohlcv[5]),  # volume
        ]

    def fetch_status(self, params={}):
        response = self.webGetV1Healthcheck()
        status = self.safe_integer(response, 'status')
        formattedStatus = 'ok'
        if status == 503:
            formattedStatus = 'maintenance'
        self.status = self.extend(self.status, {
            'status': formattedStatus,
            'updated': self.milliseconds(),
        })
        return self.status

    def fetch_balance(self, params={}):
        balances = self.privateV4PostTradeAccountBalance(params)
        result = {'info': balances}

        for balance in balances:
            free_amount = float(balances[balance]['available'])
            used_amount = float(balances[balance]['freeze'])
            account = {
                'free': free_amount,
                'used': used_amount,
                'total': free_amount + used_amount,
            }
            result[balance] = account

        return self.parse_balance(result)

    def parse_order(self, order, market=None):
        # Open Orders:
        # [{'orderId': 192999293, 'market': 'XST_BTC', 'price': '1', 'side': 'sell', 'type': 'limit',
        #   'timestamp': 1578638387.475501, 'dealMoney': '0', 'dealStock': '0', 'amount': '1', 'takerFee': '0.001',
        #   'makerFee': '0.001', 'left': '1', 'dealFee': '0'}]

        # Closed Orders:
        # [{'id': 30319397, 'time': 1578643101.989374, 'side': 'sell', 'role': 2, 'amount': '14', 'price': '0.0000075',
        #   'deal': '0.000105', 'fee': '0.000000105'}]

        if 'orderId' in order:
            order_id = self.safe_string(order, 'orderId')
        else:
            order_id = self.safe_string(order, 'id')

        if 'timestamp' in order:
            timestamp = int(order['timestamp'] * 1000)
        else:
            timestamp = int(order['time'] * 1000)

        if 'market' in order:
            market = self.markets_by_id[order['market']]

        symbol = market['symbol']
        side = order['side']
        order_type = order.get('type', 'limit')

        price = self.safe_float(order, 'price')
        amount = self.safe_float(order, 'amount')

        if 'left' in order:
            remaining = self.safe_float(order, 'left')
            status = 'open'
        else:
            remaining = 0
            status = 'closed'

        filled = amount - remaining
        cost = filled * price

        return {
            'id': order_id,
            'datetime': self.iso8601(timestamp),
            'timestamp': timestamp,
            'lastTradeTimestamp': None,
            'status': status,
            'symbol': symbol,
            'type': order_type,
            'side': side,
            'price': price,
            'amount': amount,
            'filled': filled,
            'remaining': remaining,
            'cost': cost,
            'info': order.copy(),
        }

    def fetch_orders(self, symbol=None, since=None, limit=None, params={}):
        open_orders = self.fetch_open_orders(symbol, since, limit, params)
        closed_orders = self.fetch_closed_orders(symbol, since, limit, params)

        orders = open_orders + closed_orders

        return orders

    def fetch_open_orders(self, symbol=None, since=None, limit=None, params={}):
        if not symbol:
            raise BadSymbol('symbol is required')

        self.load_markets()

        request = dict()

        market = self.market(symbol)
        request['market'] = market['id']

        if limit:
            request['limit'] = limit

        raw_orders = self.privateV4PostOrders(self.extend(request, params))

        return self.parse_orders(raw_orders, market, since, limit)

    def fetch_closed_orders(self, symbol=None, since=None, limit=None, params={}):
        if not symbol:
            raise BadSymbol('symbol is required')

        self.load_markets()

        request = dict()

        market = self.market(symbol)
        request['market'] = market['id']

        if limit:
            request['limit'] = limit

        raw_orders = self.privateV4PostTradeAccountExecutedHistory(self.extend(request, params))

        return self.parse_orders(raw_orders, market, since, limit)

    def create_order(self, symbol, type, side, amount, price, params={}):  # request timeout ....
        self.load_markets()
        market = self.market(symbol)

        request = dict()
        request['market'] = market['id']
        request['side'] = side
        request['amount'] = str(self.amount_to_precision(symbol, amount))
        request['price'] = str(self.price_to_precision(symbol, price))

        data = self.privateV4PostOrderNew(self.extend(request, params))

        return data

    def cancel_order(self, id, symbol, params={}):
        self.load_markets()
        market = self.market(symbol)

        request = dict()
        request['market'] = market['id']
        request['orderId'] = id

        result = self.privateV4PostOrderCancel(self.extend(request, params))

        return result

    def sign(self, path, api='privateV4', method='POST', params={}, headers=None, body=None):
        """
        Private API:
            X-TXC-APIKEY	Your WhiteBit API key
            X-TXC-PAYLOAD	The base64-encoded JSON payload
            X-TXC-SIGNATURE	hex(HMAC_SHA512(base64(payload), key=api_secret))

        "request": "{{request}}",
        "nonce": "{{nonce}}"
        """

        url = self.urls['api'][api] + '/' + self.implode_params(path, params)

        if api == 'publicV1' or api == 'publicV2':
            query = self.omit(params, self.extract_params(path))

            if query:
                url += '?' + self.urlencode(query)
        else:
            body_data = dict()
            body_data['request'] = '/api/v4/' + path
            body_data['nonce'] = int(time.time())

            # query = self.omit(params, self.extract_params(path))

            for param in params:
                body_data[param] = params[param]

            body = self.json(body_data)
            payload = base64.b64encode(body.encode('utf-8'))

            msg_hmac = hmac.new(self.secret.encode(), payload, hashlib.sha512)
            signature = msg_hmac.hexdigest()

            if not headers:
                headers = dict()

            headers['X-TXC-APIKEY'] = self.apiKey
            headers['X-TXC-PAYLOAD'] = payload.decode()
            headers['X-TXC-SIGNATURE'] = signature

        return {'url': url, 'method': method, 'body': body, 'headers': headers}

    # def handle_errors(self, code, reason, url, method, headers, body, response, requestHeaders, requestBody):
    #     if (code == 418) or (code == 429):
    #         raise DDoSProtection(self.id + ' ' + str(code) + ' ' + reason + ' ' + body)
    #     if code == 404:
    #         raise ExchangeError(self.id + ' ' + str(code) + ' endpoint not found')
    #     if code == 2:
    #         raise ExchangeError(self.id + ' ' + str(code) + ' endpoint not found')
    #     if response is not None:
    #         success = self.safe_value(response, 'success')
    #         if not success:
    #             feedback = self.id + ' ' + body
    #             message = self.safe_value(response, 'message')
    #             # if isinstance(message, basestring):
    #             #     self.throw_exactly_matched_exception(self.exceptions['exact'], message, feedback)
    #             # self.throw_broadly_matched_exception(self.exceptions['broad'], body, feedback)
    #             raise ExchangeError(feedback)
