# -*- 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

from ccxt.async_support.base.exchange import Exchange
import hashlib
from ccxt.base.errors import ExchangeError
from ccxt.base.errors import AuthenticationError
from ccxt.base.errors import ArgumentsRequired
from ccxt.base.decimal_to_precision import TICK_SIZE
from ccxt.base.precise import Precise


class latoken2(Exchange):

    def describe(self):
        return self.deep_extend(super(latoken2, self).describe(), {
            'id': 'latoken2',
            'name': 'Latoken',
            'countries': ['KY'],  # Cayman Islands
            'version': 'v2',
            'rateLimit': 1000,
            'has': {
                'cancelAllOrders': True,
                'cancelOrder': None,
                'createOrder': None,
                'fetchBalance': True,
                'fetchCurrencies': True,
                'fetchMarkets': True,
                'fetchMyTrades': True,
                'fetchOpenOrders': True,
                'fetchOrderBook': True,
                'fetchOrder': True,
                'fetchOrders': True,
                'fetchTicker': True,
                'fetchTickers': True,
                'fetchTrades': True,
                'fetchTransactions': True,
            },
            'urls': {
                'logo': 'https://user-images.githubusercontent.com/1294454/61511972-24c39f00-aa01-11e9-9f7c-471f1d6e5214.jpg',
                'api': 'https://api.latoken.com',
                'www': 'https://latoken.com',
                'doc': [
                    'https://api.latoken.com',
                ],
                'fees': 'https://latoken.com/fees',
                'referral': 'https://latoken.com/invite?r=mvgp2djk',
            },
            'api': {
                'public': {
                    'get': {
                        'book/{currency}/{quote}': 1,
                        'chart/week': 1,
                        'chart/week/{currency}/{quote}': 1,
                        'currency': 1,
                        'currency/available': 1,
                        'currency/quotes': 1,
                        'currency/{currency}': 1,
                        'pair': 1,
                        'pair/available': 1,
                        'ticker': 1,
                        'ticker/{base}/{quote}': 1,
                        'time': 1,
                        'trade/history/{currency}/{quote}': 1,
                        'trade/fee/{currency}/{quote}': 1,
                        'trade/feeLevels': 1,
                        'transaction/bindings': 1,
                    },
                },
                'private': {
                    'get': {
                        'auth/account': 1,
                        'auth/account/currency/{currency}/{type}': 1,
                        'auth/order': 1,
                        'auth/order/getOrder/{id}': 1,
                        'auth/order/pair/{currency}/{quote}': 1,
                        'auth/order/pair/{currency}/{quote}/active': 1,
                        'auth/stopOrder': 1,
                        'auth/stopOrder/getOrder/{id}': 1,
                        'auth/stopOrder/pair/{currency}/{quote}': 1,
                        'auth/stopOrder/pair/{currency}/{quote}/active': 1,
                        'auth/trade': 1,
                        'auth/trade/pair/{currency}/{quote}': 1,
                        'auth/trade/fee/{currency}/{quote}': 1,
                        'auth/transaction': 1,
                        'auth/transaction/bindings': 1,
                        'auth/transaction/bindings/{currency}': 1,
                        'auth/transaction/{id}': 1,
                        'auth/transfer': 1,
                    },
                    'post': {
                        'auth/order/cancel': 1,
                        'auth/order/cancelAll': 1,
                        'auth/order/cancelAll/{currency}/{quote}': 1,
                        'auth/order/place': 1,
                        'auth/spot/deposit': 1,
                        'auth/spot/withdraw': 1,
                        'auth/stopOrder/cancel': 1,
                        'auth/stopOrder/cancelAll': 1,
                        'auth/stopOrder/cancelAll/{currency}/{quote}': 1,
                        'auth/stopOrder/place': 1,
                        'auth/transaction/depositAddress': 1,
                        'auth/transaction/withdraw': 1,
                        'auth/transaction/withdraw/cancel': 1,
                        'auth/transaction/withdraw/confirm': 1,
                        'auth/transaction/withdraw/resendCode': 1,
                        'auth/transfer/email': 1,
                        'auth/transfer/id': 1,
                        'auth/transfer/phone': 1,
                    },
                },
            },
            'precisionMode': TICK_SIZE,
            'fees': {
                'trading': {
                    'feeSide': 'get',
                    'tierBased': False,
                    'percentage': True,
                    'maker': self.parse_number('0.0049'),
                    'taker': self.parse_number('0.0049'),
                },
            },
            'commonCurrencies': {
                'MT': 'Monarch',
                'TPAY': 'Tetra Pay',
                'TSL': 'Treasure SL',
            },
            'exceptions': {
                'exact': {
                    # INTERNAL_ERROR - internal server error. You can contact our support to solve self problem.
                    # SERVICE_UNAVAILABLE - requested information currently not available. You can contact our support to solve self problem or retry later.
                    # NOT_AUTHORIZED - user's query not authorized. Check if you are logged in.
                    # FORBIDDEN - you don't have enough access rights.
                    # BAD_REQUEST - some bad request, for example bad fields values or something else. Read response message for more information.
                    # NOT_FOUND - entity not found. Read message for more information.
                    # ACCESS_DENIED - access is denied. Probably you don't have enough access rights, you contact our support.
                    # REQUEST_REJECTED - user's request rejected for some reasons. Check error message.
                    # HTTP_MEDIA_TYPE_NOT_SUPPORTED - http media type not supported.
                    # MEDIA_TYPE_NOT_ACCEPTABLE - media type not acceptable
                    # METHOD_ARGUMENT_NOT_VALID - one of method argument is invalid. Check argument types and error message for more information.
                    # VALIDATION_ERROR - check errors field to get reasons.
                    # ACCOUNT_EXPIRED - restore your account or create a new one.
                    # BAD_CREDENTIALS - invalid username or password.
                    # COOKIE_THEFT - cookie has been stolen. Let's try reset your cookies.
                    # CREDENTIALS_EXPIRED - credentials expired.
                    # INSUFFICIENT_AUTHENTICATION - for example, 2FA required.
                    # UNKNOWN_LOCATION - user logged from unusual location, email confirmation required.
                    # TOO_MANY_REQUESTS - too many requests at the time. A response header X-Rate-Limit-Remaining indicates the number of allowed request per a period.
                },
                'broad': {
                    'invalid API key, signature or digest': AuthenticationError,
                },
            },
            'options': {
                'defaultType': 'spot',
                'types': {
                    'wallet': 'ACCOUNT_TYPE_WALLET',
                    'spot': 'ACCOUNT_TYPE_SPOT',
                },
                'accounts': {
                    'ACCOUNT_TYPE_WALLET': 'wallet',
                    'ACCOUNT_TYPE_SPOT': 'spot',
                },
            },
        })

    def nonce(self):
        return self.milliseconds() - self.options['timeDifference']

    async def fetch_time(self, params={}):
        response = await self.publicGetTime(params)
        #
        #     {
        #         "serverTime": 1570615577321
        #     }
        #
        return self.safe_integer(response, 'serverTime')

    async def load_time_difference(self, params={}):
        serverTime = await self.fetch_time(params)
        after = self.milliseconds()
        self.options['timeDifference'] = after - serverTime
        return self.options['timeDifference']

    async def fetch_markets(self, params={}):
        currencies = await self.fetch_currencies_from_cache(params)
        #
        #     [
        #         {
        #             "id":"1a075819-9e0b-48fc-8784-4dab1d186d6d",
        #             "status":"CURRENCY_STATUS_ACTIVE",
        #             "type":"CURRENCY_TYPE_ALTERNATIVE",  # CURRENCY_TYPE_CRYPTO, CURRENCY_TYPE_IEO
        #             "name":"MyCryptoBank",
        #             "tag":"MCB",
        #             "description":"",
        #             "logo":"",
        #             "decimals":18,
        #             "created":1572912000000,
        #             "tier":1,
        #             "assetClass":"ASSET_CLASS_UNKNOWN",
        #             "minTransferAmount":0
        #         },
        #         {
        #             "id":"db02758e-2507-46a5-a805-7bc60355b3eb",
        #             "status":"CURRENCY_STATUS_ACTIVE",
        #             "type":"CURRENCY_TYPE_FUTURES_CONTRACT",
        #             "name":"BTC USDT Futures Contract",
        #             "tag":"BTCUSDT",
        #             "description":"",
        #             "logo":"",
        #             "decimals":8,
        #             "created":1589459984395,
        #             "tier":1,
        #             "assetClass":"ASSET_CLASS_UNKNOWN",
        #             "minTransferAmount":0
        #         },
        #     ]
        #
        response = await self.publicGetPair(params)
        #
        #     [
        #         {
        #             "id":"dba4289b-6b46-4d94-bf55-49eec9a163ad",
        #             "status":"PAIR_STATUS_ACTIVE",  # CURRENCY_STATUS_INACTIVE
        #             "baseCurrency":"fb9b53d6-bbf6-472f-b6ba-73cc0d606c9b",
        #             "quoteCurrency":"620f2019-33c0-423b-8a9d-cde4d7f8ef7f",
        #             "priceTick":"0.000000100000000000",
        #             "priceDecimals":7,
        #             "quantityTick":"0.010000000",
        #             "quantityDecimals":2,
        #             "costDisplayDecimals":7,
        #             "created":1572957210501,
        #             "minOrderQuantity":"0",
        #             "maxOrderCostUsd":"999999999999999999",
        #             "minOrderCostUsd":"0",
        #             "externalSymbol":""
        #         }
        #     ]
        #
        if self.safe_value(self.options, 'adjustForTimeDifference', True):
            await self.load_time_difference()
        currenciesById = self.index_by(currencies, 'id')
        result = []
        for i in range(0, len(response)):
            market = response[i]
            id = self.safe_string(market, 'id')
            # the exchange shows them inverted
            baseId = self.safe_string(market, 'baseCurrency')
            quoteId = self.safe_string(market, 'quoteCurrency')
            baseCurrency = self.safe_value(currenciesById, baseId)
            quoteCurrency = self.safe_value(currenciesById, quoteId)
            if baseCurrency is not None and quoteCurrency is not None:
                base = self.safe_currency_code(self.safe_string(baseCurrency, 'tag'))
                quote = self.safe_currency_code(self.safe_string(quoteCurrency, 'tag'))
                symbol = base + '/' + quote
                precision = {
                    'price': self.safe_number(market, 'priceTick'),
                    'amount': self.safe_number(market, 'quantityTick'),
                }
                lowercaseQuote = quote.lower()
                capitalizedQuote = self.capitalize(lowercaseQuote)
                limits = {
                    'amount': {
                        'min': self.safe_number(market, 'minOrderQuantity'),
                        'max': None,
                    },
                    'price': {
                        'min': None,
                        'max': None,
                    },
                    'cost': {
                        'min': self.safe_number(market, 'minOrderCost' + capitalizedQuote),
                        'max': self.safe_number(market, 'maxOrderCost' + capitalizedQuote),
                    },
                }
                status = self.safe_string(market, 'status')
                active = (status == 'PAIR_STATUS_ACTIVE')
                result.append({
                    'id': id,
                    'info': market,
                    'symbol': symbol,
                    'base': base,
                    'quote': quote,
                    'baseId': baseId,
                    'quoteId': quoteId,
                    'type': 'spot',
                    'spot': True,
                    'active': active,  # assuming True
                    'precision': precision,
                    'limits': limits,
                })
        return result

    async def fetch_currencies_from_cache(self, params={}):
        # self method is now redundant
        # currencies are now fetched before markets
        options = self.safe_value(self.options, 'fetchCurrencies', {})
        timestamp = self.safe_integer(options, 'timestamp')
        expires = self.safe_integer(options, 'expires', 1000)
        now = self.milliseconds()
        if (timestamp is None) or ((now - timestamp) > expires):
            response = await self.publicGetCurrency(params)
            self.options['fetchCurrencies'] = self.extend(options, {
                'response': response,
                'timestamp': now,
            })
        return self.safe_value(self.options['fetchCurrencies'], 'response')

    async def fetch_currencies(self, params={}):
        response = await self.fetch_currencies_from_cache(params)
        #
        #     [
        #         {
        #             "id":"1a075819-9e0b-48fc-8784-4dab1d186d6d",
        #             "status":"CURRENCY_STATUS_ACTIVE",
        #             "type":"CURRENCY_TYPE_ALTERNATIVE",  # CURRENCY_TYPE_CRYPTO, CURRENCY_TYPE_IEO
        #             "name":"MyCryptoBank",
        #             "tag":"MCB",
        #             "description":"",
        #             "logo":"",
        #             "decimals":18,
        #             "created":1572912000000,
        #             "tier":1,
        #             "assetClass":"ASSET_CLASS_UNKNOWN",
        #             "minTransferAmount":0
        #         },
        #         {
        #             "id":"db02758e-2507-46a5-a805-7bc60355b3eb",
        #             "status":"CURRENCY_STATUS_ACTIVE",
        #             "type":"CURRENCY_TYPE_FUTURES_CONTRACT",
        #             "name":"BTC USDT Futures Contract",
        #             "tag":"BTCUSDT",
        #             "description":"",
        #             "logo":"",
        #             "decimals":8,
        #             "created":1589459984395,
        #             "tier":1,
        #             "assetClass":"ASSET_CLASS_UNKNOWN",
        #             "minTransferAmount":0
        #         },
        #     ]
        #
        result = {}
        for i in range(0, len(response)):
            currency = response[i]
            id = self.safe_string(currency, 'id')
            tag = self.safe_string(currency, 'tag')
            code = self.safe_currency_code(tag)
            decimals = self.safe_string(currency, 'decimals')
            precision = self.parse_number('1e-' + decimals)
            fee = self.safe_number(currency, 'fee')
            currencyType = self.safe_string(currency, 'type')
            parts = currencyType.split('_')
            numParts = len(parts)
            lastPart = self.safe_value(parts, numParts - 1)
            type = lastPart.lower()
            status = self.safe_string(currency, 'status')
            active = (status == 'CURRENCY_STATUS_ACTIVE')
            name = self.safe_string(currency, 'name')
            result[code] = {
                'id': id,
                'code': code,
                'info': currency,
                'name': name,
                'type': type,
                'active': active,
                'fee': fee,
                'precision': precision,
                'limits': {
                    'amount': {
                        'min': self.safe_number(currency, 'minTransferAmount'),
                        'max': None,
                    },
                    'withdraw': {
                        'min': None,
                        'max': None,
                    },
                },
            }
        return result

    async def fetch_balance(self, params={}):
        await self.load_markets()
        response = await self.privateGetAuthAccount(params)
        #
        #     [
        #         {
        #             id: "e5852e02-8711-431c-9749-a6f5503c6dbe",
        #             status: "ACCOUNT_STATUS_ACTIVE",
        #             type: "ACCOUNT_TYPE_WALLET",
        #             timestamp: "1635920106506",
        #             currency: "0c3a106d-bde3-4c13-a26e-3fd2394529e5",
        #             available: "100.000000",
        #             blocked: "0.000000"
        #         },
        #         {
        #             id: "369df204-acbc-467e-a25e-b16e3cc09cf6",
        #             status: "ACCOUNT_STATUS_ACTIVE",
        #             type: "ACCOUNT_TYPE_SPOT",
        #             timestamp: "1635920106504",
        #             currency: "0c3a106d-bde3-4c13-a26e-3fd2394529e5",
        #             available: "100.000000",
        #             blocked: "0.000000"
        #         }
        #     ]
        #
        result = {
            'info': response,
            'timestamp': None,
            'datetime': None,
        }
        maxTimestamp = None
        defaultType = self.safe_string_2(self.options, 'fetchBalance', 'defaultType', 'spot')
        type = self.safe_string(params, 'type', defaultType)
        types = self.safe_value(self.options, 'types', {})
        accountType = self.safe_string(types, type, type)
        balancesByType = self.group_by(response, 'type')
        balances = self.safe_value(balancesByType, accountType, [])
        for i in range(0, len(balances)):
            balance = balances[i]
            currencyId = self.safe_string(balance, 'currency')
            timestamp = self.safe_integer(balance, 'timestamp')
            if timestamp is not None:
                if maxTimestamp is None:
                    maxTimestamp = timestamp
                else:
                    maxTimestamp = max(maxTimestamp, timestamp)
            code = self.safe_currency_code(currencyId)
            account = self.account()
            account['free'] = self.safe_string(balance, 'available')
            account['used'] = self.safe_string(balance, 'blocked')
            result[code] = account
        result['timestamp'] = maxTimestamp
        result['datetime'] = self.iso8601(maxTimestamp)
        return self.parse_balance(result)

    async def fetch_order_book(self, symbol, limit=None, params={}):
        await self.load_markets()
        market = self.market(symbol)
        request = {
            'currency': market['baseId'],
            'quote': market['quoteId'],
        }
        if limit is not None:
            request['limit'] = limit  # max 1000
        response = await self.publicGetBookCurrencyQuote(self.extend(request, params))
        #
        #     {
        #         "ask":[
        #             {"price":"4428.76","quantity":"0.08136","cost":"360.3239136","accumulated":"360.3239136"},
        #             {"price":"4429.77","quantity":"1.11786","cost":"4951.8626922","accumulated":"5312.1866058"},
        #             {"price":"4430.94","quantity":"1.78418","cost":"7905.5945292","accumulated":"13217.781135"},
        #         ],
        #         "bid":[
        #             {"price":"4428.43","quantity":"0.13675","cost":"605.5878025","accumulated":"605.5878025"},
        #             {"price":"4428.19","quantity":"0.03619","cost":"160.2561961","accumulated":"765.8439986"},
        #             {"price":"4428.15","quantity":"0.02926","cost":"129.567669","accumulated":"895.4116676"},
        #         ],
        #         "totalAsk":"53.14814",
        #         "totalBid":"112216.9029791"
        #     }
        #
        return self.parse_order_book(response, symbol, None, 'bid', 'ask', 'price', 'quantity')

    def parse_ticker(self, ticker, market=None):
        #
        #     {
        #         "symbol":"620f2019-33c0-423b-8a9d-cde4d7f8ef7f/0c3a106d-bde3-4c13-a26e-3fd2394529e5",
        #         "baseCurrency":"620f2019-33c0-423b-8a9d-cde4d7f8ef7f",
        #         "quoteCurrency":"0c3a106d-bde3-4c13-a26e-3fd2394529e5",
        #         "volume24h":"76411867.852585600000000000",
        #         "volume7d":"637809926.759451100000000000",
        #         "change24h":"2.5300",
        #         "change7d":"5.1300",
        #         "lastPrice":"4426.9"
        #     }
        #
        marketId = self.safe_string(ticker, 'symbol')
        symbol = self.safe_symbol(marketId, market)
        last = self.safe_number(ticker, 'lastPrice')
        change = self.safe_number(ticker, 'change24h')
        timestamp = self.nonce()
        return self.safe_ticker({
            'symbol': symbol,
            'timestamp': timestamp,
            'datetime': self.iso8601(timestamp),
            'low': self.safe_number(ticker, 'low'),
            'high': self.safe_number(ticker, 'high'),
            'bid': None,
            'bidVolume': None,
            'ask': None,
            'askVolume': None,
            'vwap': None,
            'open': None,
            'close': last,
            'last': last,
            'previousClose': None,
            'change': change,
            'percentage': None,
            'average': None,
            'baseVolume': None,
            'quoteVolume': self.safe_number(ticker, 'volume24h'),
            'info': ticker,
        })

    async def fetch_ticker(self, symbol, params={}):
        await self.load_markets()
        market = self.market(symbol)
        request = {
            'base': market['baseId'],
            'quote': market['quoteId'],
        }
        response = await self.publicGetTickerBaseQuote(self.extend(request, params))
        #
        #     {
        #         "symbol":"620f2019-33c0-423b-8a9d-cde4d7f8ef7f/0c3a106d-bde3-4c13-a26e-3fd2394529e5",
        #         "baseCurrency":"620f2019-33c0-423b-8a9d-cde4d7f8ef7f",
        #         "quoteCurrency":"0c3a106d-bde3-4c13-a26e-3fd2394529e5",
        #         "volume24h":"76411867.852585600000000000",
        #         "volume7d":"637809926.759451100000000000",
        #         "change24h":"2.5300",
        #         "change7d":"5.1300",
        #         "lastPrice":"4426.9"
        #     }
        #
        return self.parse_ticker(response, market)

    async def fetch_tickers(self, symbols=None, params={}):
        await self.load_markets()
        response = await self.publicGetTicker(params)
        #
        #     [
        #         {
        #             "symbol":"DASH/BTC",
        #             "baseCurrency":"ed75c263-4ab9-494b-8426-031dab1c7cc1",
        #             "quoteCurrency":"92151d82-df98-4d88-9a4d-284fa9eca49f",
        #             "volume24h":"1.977753278000000000",
        #             "volume7d":"18.964342670000000000",
        #             "change24h":"-1.4800",
        #             "change7d":"-5.5200",
        #             "lastPrice":"0.003066"
        #         },
        #     ]
        #
        return self.parse_tickers(response, symbols)

    def parse_trade(self, trade, market=None):
        #
        # fetchTrades(public)
        #
        #     {
        #         "id":"c152f814-8eeb-44f0-8f3f-e5c568f2ffcf",
        #         "isMakerBuyer":false,
        #         "baseCurrency":"620f2019-33c0-423b-8a9d-cde4d7f8ef7f",
        #         "quoteCurrency":"0c3a106d-bde3-4c13-a26e-3fd2394529e5",
        #         "price":"4435.56",
        #         "quantity":"0.32534",
        #         "cost":"1443.0650904",
        #         "timestamp":1635854642725,
        #         "makerBuyer":false
        #     }
        #
        # fetchMyTrades(private)
        #
        #     {
        #         "id":"02e02533-b4bf-4ba9-9271-24e2108dfbf7",
        #         "isMakerBuyer":false,
        #         "direction":"TRADE_DIRECTION_BUY",
        #         "baseCurrency":"620f2019-33c0-423b-8a9d-cde4d7f8ef7f",
        #         "quoteCurrency":"0c3a106d-bde3-4c13-a26e-3fd2394529e5",
        #         "price":"4564.32",
        #         "quantity":"0.01000",
        #         "cost":"45.6432",
        #         "fee":"0.223651680000000000",
        #         "order":"c9cac6a0-484c-4892-88e7-ad51b39f2ce1",
        #         "timestamp":1635921580399,
        #         "makerBuyer":false
        #     }
        #
        type = None
        timestamp = self.safe_integer(trade, 'timestamp')
        priceString = self.safe_string(trade, 'price')
        amountString = self.safe_string(trade, 'quantity')
        price = self.parse_number(priceString)
        amount = self.parse_number(amountString)
        costString = self.safe_string(trade, 'cost')
        if costString is None:
            costString = Precise.string_mul(priceString, amountString)
        cost = self.parse_number(costString)
        makerBuyer = self.safe_value(trade, 'makerBuyer')
        side = self.safe_string(trade, 'direction')
        if side is None:
            side = 'sell' if makerBuyer else 'buy'
        else:
            if side == 'TRADE_DIRECTION_BUY':
                side = 'buy'
            elif side == 'TRADE_DIRECTION_SELL':
                side = 'sell'
        isBuy = (side == 'buy')
        takerOrMaker = 'maker' if (makerBuyer and isBuy) else 'taker'
        baseId = self.safe_string(trade, 'baseCurrency')
        quoteId = self.safe_string(trade, 'quoteCurrency')
        base = self.safe_currency_code(baseId)
        quote = self.safe_currency_code(quoteId)
        symbol = base + '/' + quote
        if symbol in self.markets:
            market = self.market(symbol)
        id = self.safe_string(trade, 'id')
        orderId = self.safe_string(trade, 'order')
        feeCost = self.safe_number(trade, 'fee')
        fee = None
        if feeCost is not None:
            fee = {
                'cost': feeCost,
                'currency': quote,
            }
        return {
            'info': trade,
            'timestamp': timestamp,
            'datetime': self.iso8601(timestamp),
            'symbol': symbol,
            'id': id,
            'order': orderId,
            'type': type,
            'takerOrMaker': takerOrMaker,
            'side': side,
            'price': price,
            'amount': amount,
            'cost': cost,
            'fee': fee,
        }

    async def fetch_trades(self, symbol, since=None, limit=None, params={}):
        await self.load_markets()
        market = self.market(symbol)
        request = {
            'currency': market['baseId'],
            'quote': market['quoteId'],
            # 'from': str(since),  # milliseconds
            # 'limit': limit,  # default 100
        }
        if since is not None:
            request['from'] = str(since)
        if limit is not None:
            request['limit'] = limit  # default 50, max 100
        response = await self.publicGetTradeHistoryCurrencyQuote(self.extend(request, params))
        #
        #     [
        #         {"id":"c152f814-8eeb-44f0-8f3f-e5c568f2ffcf","isMakerBuyer":false,"baseCurrency":"620f2019-33c0-423b-8a9d-cde4d7f8ef7f","quoteCurrency":"0c3a106d-bde3-4c13-a26e-3fd2394529e5","price":"4435.56","quantity":"0.32534","cost":"1443.0650904","timestamp":1635854642725,"makerBuyer":false},
        #         {"id":"cfecbefb-3d11-43d7-b9d4-fa16211aad8a","isMakerBuyer":false,"baseCurrency":"620f2019-33c0-423b-8a9d-cde4d7f8ef7f","quoteCurrency":"0c3a106d-bde3-4c13-a26e-3fd2394529e5","price":"4435.13","quantity":"0.26540","cost":"1177.083502","timestamp":1635854641114,"makerBuyer":false},
        #         {"id":"f43d3ec8-db94-49f3-b534-91dbc2779296","isMakerBuyer":true,"baseCurrency":"620f2019-33c0-423b-8a9d-cde4d7f8ef7f","quoteCurrency":"0c3a106d-bde3-4c13-a26e-3fd2394529e5","price":"4435.00","quantity":"0.41738","cost":"1851.0803","timestamp":1635854640323,"makerBuyer":true},
        #     ]
        #
        return self.parse_trades(response, market, since, limit)

    async def fetch_my_trades(self, symbol=None, since=None, limit=None, params={}):
        await self.load_markets()
        request = {
            # 'currency': market['baseId'],
            # 'quote': market['quoteId'],
            # 'from': self.milliseconds(),
            # 'limit': limit,  # default '100'
        }
        method = 'privateGetAuthTrade'
        market = None
        if symbol is not None:
            market = self.market(symbol)
            request['currency'] = market['baseId']
            request['quote'] = market['quoteId']
            method = 'privateGetAuthTradePairCurrencyQuote'
        if limit is not None:
            request['limit'] = limit  # default 100
        response = await getattr(self, method)(self.extend(request, params))
        #
        #     [
        #         {
        #             "id":"02e02533-b4bf-4ba9-9271-24e2108dfbf7",
        #             "isMakerBuyer":false,
        #             "direction":"TRADE_DIRECTION_BUY",
        #             "baseCurrency":"620f2019-33c0-423b-8a9d-cde4d7f8ef7f",
        #             "quoteCurrency":"0c3a106d-bde3-4c13-a26e-3fd2394529e5",
        #             "price":"4564.32",
        #             "quantity":"0.01000",
        #             "cost":"45.6432",
        #             "fee":"0.223651680000000000",
        #             "order":"c9cac6a0-484c-4892-88e7-ad51b39f2ce1",
        #             "timestamp":1635921580399,
        #             "makerBuyer":false
        #         }
        #     ]
        #
        return self.parse_trades(response, market, since, limit)

    def parse_order_status(self, status):
        statuses = {
            'ORDER_STATUS_PLACED': 'open',
            'ORDER_STATUS_CLOSED': 'closed',
            'ORDER_STATUS_CANCELLED': 'canceled',
        }
        return self.safe_string(statuses, status, status)

    def parse_order_type(self, status):
        statuses = {
            'ORDER_TYPE_MARKET': 'market',
            'ORDER_TYPE_LIMIT': 'limit',
        }
        return self.safe_string(statuses, status, status)

    def parse_time_in_force(self, timeInForce):
        timeInForces = {
            'ORDER_CONDITION_GOOD_TILL_CANCELLED': 'GTC',
            'ORDER_CONDITION_IMMEDIATE_OR_CANCEL': 'IOC',
            'ORDER_CONDITION_FILL_OR_KILL': 'FOK',
        }
        return self.safe_string(timeInForces, timeInForce, timeInForce)

    def parse_order(self, order, market=None):
        #
        # createOrder
        #
        #     {
        #         "orderId":"1563460093.134037.704945@0370:2",
        #         "cliOrdId":"",
        #         "pairId":370,
        #         "symbol":"ETHBTC",
        #         "side":"sell",
        #         "orderType":"limit",
        #         "price":1.0,
        #         "amount":1.0
        #     }
        #
        # fetchOrder, fetchOpenOrders, fetchOrders
        #
        #     {
        #         "id":"a76bd262-3560-4bfb-98ac-1cedd394f4fc",
        #         "status":"ORDER_STATUS_PLACED",
        #         "side":"ORDER_SIDE_BUY",
        #         "condition":"ORDER_CONDITION_GOOD_TILL_CANCELLED",
        #         "type":"ORDER_TYPE_LIMIT",
        #         "baseCurrency":"620f2019-33c0-423b-8a9d-cde4d7f8ef7f",
        #         "quoteCurrency":"0c3a106d-bde3-4c13-a26e-3fd2394529e5",
        #         "clientOrderId":"web-macos_chrome_1a6a6659-6f7c-4fac-be0b-d1d7ac06d",
        #         "price":"4000.00",
        #         "quantity":"0.01",
        #         "cost":"40.000000000000000000",
        #         "filled":"0",
        #         "trader":"7244bb3a-b6b2-446a-ac78-fa4bce5b59a9",
        #         "creator":"ORDER_CREATOR_USER",
        #         "creatorId":"",
        #         "timestamp":1635920767648
        #     }
        #
        # cancelOrder
        #
        #     {
        #         "message":"cancellation request successfully submitted",
        #         "status":"SUCCESS",
        #         "id":"a631426d-3543-45ba-941e-75f7825afb0f"
        #     }
        #
        id = self.safe_string(order, 'id')
        timestamp = self.safe_integer(order, 'timestamp')
        baseId = self.safe_string(order, 'baseCurrency')
        quoteId = self.safe_string(order, 'quoteCurrency')
        base = self.safe_currency_code(baseId)
        quote = self.safe_currency_code(quoteId)
        symbol = None
        if (base is not None) and (quote is not None):
            symbol = base + '/' + quote
            if symbol in self.markets:
                market = self.market(symbol)
        orderSide = self.safe_string(order, 'side')
        side = None
        if orderSide is not None:
            parts = orderSide.split('_')
            side = self.safe_string_lower(parts, len(parts) - 1)
        type = self.parse_order_type(self.safe_string(order, 'type'))
        price = self.safe_string(order, 'price')
        amount = self.safe_string(order, 'quantity')
        filled = self.safe_string(order, 'filled')
        cost = self.safe_string(order, 'cost')
        status = self.parse_order_status(self.safe_string(order, 'status'))
        message = self.safe_string(order, 'message')
        if message.find('cancel') >= 0:
            status = 'canceled'
        elif message.find('accept') >= 0:
            status = 'open'
        clientOrderId = self.safe_string(order, 'clientOrderId')
        timeInForce = self.parse_time_in_force(self.safe_string(order, 'condition'))
        return self.safe_order({
            'id': id,
            'clientOrderId': clientOrderId,
            'info': order,
            'timestamp': timestamp,
            'datetime': self.iso8601(timestamp),
            'lastTradeTimestamp': None,
            'status': status,
            'symbol': symbol,
            'type': type,
            'timeInForce': timeInForce,
            'postOnly': None,
            'side': side,
            'price': price,
            'stopPrice': None,
            'cost': cost,
            'amount': amount,
            'filled': filled,
            'average': None,
            'remaining': None,
            'fee': None,
            'trades': None,
        })

    async def fetch_open_orders(self, symbol=None, since=None, limit=None, params={}):
        if symbol is None:
            raise ArgumentsRequired(self.id + ' fetchOpenOrders() requires a symbol argument')
        await self.load_markets()
        market = self.market(symbol)
        request = {
            'currency': market['baseId'],
            'quote': market['quoteId'],
        }
        response = await self.privateGetAuthOrderPairCurrencyQuoteActive(self.extend(request, params))
        #
        #     [
        #         {
        #             "id":"a76bd262-3560-4bfb-98ac-1cedd394f4fc",
        #             "status":"ORDER_STATUS_PLACED",
        #             "side":"ORDER_SIDE_BUY",
        #             "condition":"ORDER_CONDITION_GOOD_TILL_CANCELLED",
        #             "type":"ORDER_TYPE_LIMIT",
        #             "baseCurrency":"620f2019-33c0-423b-8a9d-cde4d7f8ef7f",
        #             "quoteCurrency":"0c3a106d-bde3-4c13-a26e-3fd2394529e5",
        #             "clientOrderId":"web-macos_chrome_1a6a6659-6f7c-4fac-be0b-d1d7ac06d",
        #             "price":"4000.00",
        #             "quantity":"0.01000",
        #             "cost":"40.00",
        #             "filled":"0.00000",
        #             "trader":"7244bb3a-b6b2-446a-ac78-fa4bce5b59a9",
        #             "creator":"USER",
        #             "creatorId":"",
        #             "timestamp":1635920767648
        #         }
        #     ]
        #
        return self.parse_orders(response, market, since, limit)

    async def fetch_orders(self, symbol=None, since=None, limit=None, params={}):
        await self.load_markets()
        request = {
            # 'currency': market['baseId'],
            # 'quote': market['quoteId'],
            # 'from': self.milliseconds(),
            # 'limit': limit,  # default '100'
        }
        method = 'privateGetAuthOrder'
        market = None
        if symbol is not None:
            market = self.market(symbol)
            request['currency'] = market['baseId']
            request['quote'] = market['quoteId']
            method = 'privateGetAuthOrderPairCurrencyQuote'
        if limit is not None:
            request['limit'] = limit  # default 100
        response = await getattr(self, method)(self.extend(request, params))
        #
        #     [
        #         {
        #             "id":"a76bd262-3560-4bfb-98ac-1cedd394f4fc",
        #             "status":"ORDER_STATUS_PLACED",
        #             "side":"ORDER_SIDE_BUY",
        #             "condition":"ORDER_CONDITION_GOOD_TILL_CANCELLED",
        #             "type":"ORDER_TYPE_LIMIT",
        #             "baseCurrency":"620f2019-33c0-423b-8a9d-cde4d7f8ef7f",
        #             "quoteCurrency":"0c3a106d-bde3-4c13-a26e-3fd2394529e5",
        #             "clientOrderId":"web-macos_chrome_1a6a6659-6f7c-4fac-be0b-d1d7ac06d",
        #             "price":"4000.00",
        #             "quantity":"0.01000",
        #             "cost":"40.00",
        #             "filled":"0.00000",
        #             "trader":"7244bb3a-b6b2-446a-ac78-fa4bce5b59a9",
        #             "creator":"USER",
        #             "creatorId":"",
        #             "timestamp":1635920767648
        #         }
        #     ]
        #
        return self.parse_orders(response, market, since, limit)

    async def fetch_order(self, id, symbol=None, params={}):
        await self.load_markets()
        request = {
            'id': id,
        }
        response = await self.privateGetAuthOrderGetOrderId(self.extend(request, params))
        #
        #     {
        #         "id":"a76bd262-3560-4bfb-98ac-1cedd394f4fc",
        #         "status":"ORDER_STATUS_PLACED",
        #         "side":"ORDER_SIDE_BUY",
        #         "condition":"ORDER_CONDITION_GOOD_TILL_CANCELLED",
        #         "type":"ORDER_TYPE_LIMIT",
        #         "baseCurrency":"620f2019-33c0-423b-8a9d-cde4d7f8ef7f",
        #         "quoteCurrency":"0c3a106d-bde3-4c13-a26e-3fd2394529e5",
        #         "clientOrderId":"web-macos_chrome_1a6a6659-6f7c-4fac-be0b-d1d7ac06d",
        #         "price":"4000.00",
        #         "quantity":"0.01",
        #         "cost":"40.000000000000000000",
        #         "filled":"0",
        #         "trader":"7244bb3a-b6b2-446a-ac78-fa4bce5b59a9",
        #         "creator":"ORDER_CREATOR_USER",
        #         "creatorId":"",
        #         "timestamp":1635920767648
        #     }
        #
        return self.parse_order(response)

    async def create_order(self, symbol, type, side, amount, price=None, params={}):
        await self.load_markets()
        market = self.market(symbol)
        uppercaseType = type.upper()
        request = {
            'baseCurrency': market['baseId'],
            'quoteCurrency': market['quoteId'],
            'side': side.upper(),  # "BUY", "BID", "SELL", "ASK"
            'condition': 'GTC',  # "GTC", "GOOD_TILL_CANCELLED", "IOC", "IMMEDIATE_OR_CANCEL", "FOK", "FILL_OR_KILL"
            'type': uppercaseType,  # "LIMIT", "MARKET"
            'clientOrderId': self.uuid(),  # 50 characters max
            # 'price': self.price_to_precision(symbol, price),
            # 'quantity': self.amount_to_precision(symbol, amount),
        }
        if uppercaseType == 'LIMIT':
            request['price'] = self.price_to_precision(symbol, price)
        request['quantity'] = self.amount_to_precision(symbol, amount)
        request['timestamp'] = self.seconds()
        response = await self.privatePostAuthOrderPlace(self.extend(request, params))
        #
        #     {
        #         "orderId":"1563460093.134037.704945@0370:2",
        #         "cliOrdId":"",
        #         "pairId":370,
        #         "symbol":"ETHBTC",
        #         "side":"sell",
        #         "orderType":"limit",
        #         "price":1.0,
        #         "amount":1.0
        #     }
        #
        return self.parse_order(response, market)

    async def cancel_order(self, id, symbol=None, params={}):
        await self.load_markets()
        request = {
            'id': id,
        }
        response = await self.privatePostAuthOrderCancel(self.extend(request, params))
        #
        #     {
        #         "id": "12345678-1234-1244-1244-123456789012",
        #         "message": "cancellation request successfully submitted",
        #         "status": "SUCCESS",
        #         "error": "",
        #         "errors": {}
        #     }
        #
        return self.parse_order(response)

    async def cancel_all_orders(self, symbol=None, params={}):
        await self.load_markets()
        request = {
            # 'currency': market['baseId'],
            # 'quote': market['quoteId'],
        }
        method = 'privatePostAuthOrderCancelAll'
        market = None
        if symbol is not None:
            market = self.market(symbol)
            request['currency'] = market['baseId']
            request['quote'] = market['quoteId']
            method = 'privatePostAuthOrderCancelAllCurrencyQuote'
        response = await getattr(self, method)(self.extend(request, params))
        #
        #     {
        #         "message":"cancellation request successfully submitted",
        #         "status":"SUCCESS"
        #     }
        #
        return response

    async def fetch_transactions(self, code=None, since=None, limit=None, params={}):
        await self.load_markets()
        request = {
            # 'page': '1',
            # 'size': 100,
        }
        response = await self.privateGetAuthTransaction(self.extend(request, params))
        #
        #     {
        #         "hasNext":false,
        #         "content":[
        #             {
        #                 "id":"fbf7d0d1-2629-4ad8-9def-7a1dba423362",
        #                 "status":"TRANSACTION_STATUS_CONFIRMED",
        #                 "type":"TRANSACTION_TYPE_DEPOSIT",
        #                 "senderAddress":"",
        #                 "recipientAddress":"0x3c46fa2e3f9023bc4897828ed173f8ecb3a554bc",
        #                 "amount":"200.000000000000000000",
        #                 "transactionFee":"0.000000000000000000",
        #                 "timestamp":1635893208404,
        #                 "transactionHash":"0x28bad3b74a042df13d64ddfbca855566a51bf7f190b8cd565c236a18d5cd493f#42",
        #                 "blockHeight":13540262,
        #                 "currency":"0c3a106d-bde3-4c13-a26e-3fd2394529e5",
        #                 "memo":null,
        #                 "paymentProvider":"a8d6d1cb-f84a-4e9d-aa82-c6a08b356ee1",
        #                 "requiresCode":false
        #             }
        #         ],
        #         "first":true,
        #         "hasContent":true,
        #         "pageSize":10
        #     }
        #
        currency = None
        if code is not None:
            currency = self.currency(code)
        content = self.safe_value(response, 'content', [])
        return self.parse_transactions(content, currency, since, limit)

    def parse_transaction(self, transaction, currency=None):
        #
        #     {
        #         "id":"fbf7d0d1-2629-4ad8-9def-7a1dba423362",
        #         "status":"TRANSACTION_STATUS_CONFIRMED",
        #         "type":"TRANSACTION_TYPE_DEPOSIT",
        #         "senderAddress":"",
        #         "recipientAddress":"0x3c46fa2e3f9023bc4897828ed173f8ecb3a554bc",
        #         "amount":"200.000000000000000000",
        #         "transactionFee":"0.000000000000000000",
        #         "timestamp":1635893208404,
        #         "transactionHash":"0x28bad3b74a042df13d64ddfbca855566a51bf7f190b8cd565c236a18d5cd493f#42",
        #         "blockHeight":13540262,
        #         "currency":"0c3a106d-bde3-4c13-a26e-3fd2394529e5",
        #         "memo":null,
        #         "paymentProvider":"a8d6d1cb-f84a-4e9d-aa82-c6a08b356ee1",
        #         "requiresCode":false
        #     }
        #
        id = self.safe_string(transaction, 'id')
        timestamp = self.safe_integer(transaction, 'timestamp')
        currencyId = self.safe_string(transaction, 'currency')
        code = self.safe_currency_code(currencyId, currency)
        status = self.parse_transaction_status(self.safe_string(transaction, 'status'))
        amount = self.safe_number(transaction, 'amount')
        addressFrom = self.safe_string(transaction, 'senderAddress')
        if addressFrom == '':
            addressFrom = None
        addressTo = self.safe_string(transaction, 'recipientAddress')
        if addressTo == '':
            addressTo = None
        txid = self.safe_string(transaction, 'transactionHash')
        tagTo = self.safe_string(transaction, 'memo')
        fee = None
        feeCost = self.safe_number(transaction, 'transactionFee')
        if feeCost is not None:
            fee = {
                'cost': feeCost,
                'currency': code,
            }
        type = self.parse_transaction_type(self.safe_string(transaction, 'type'))
        return {
            'info': transaction,
            'id': id,
            'txid': txid,
            'timestamp': timestamp,
            'datetime': self.iso8601(timestamp),
            'addressFrom': addressFrom,
            'addressTo': addressTo,
            'address': addressTo,
            'tagFrom': None,
            'tagTo': tagTo,
            'tag': tagTo,
            'type': type,
            'amount': amount,
            'currency': code,
            'status': status,
            'updated': None,
            'fee': fee,
        }

    def parse_transaction_status(self, status):
        statuses = {
            'TRANSACTION_STATUS_CONFIRMED': 'ok',
            'TRANSACTION_STATUS_EXECUTED': 'ok',
        }
        return self.safe_string(statuses, status, status)

    def parse_transaction_type(self, type):
        types = {
            'TRANSACTION_TYPE_DEPOSIT': 'deposit',
            'TRANSACTION_TYPE_WITHDRAWAL': 'withdrawal',
        }
        return self.safe_string(types, type, type)

    def sign(self, path, api='public', method='GET', params=None, headers=None, body=None):
        request = '/' + self.version + '/' + self.implode_params(path, params)
        requestString = request
        query = self.omit(params, self.extract_params(path))
        urlencodedQuery = self.urlencode(query)
        if method == 'GET':
            if query:
                requestString += '?' + urlencodedQuery
        if api == 'private':
            self.check_required_credentials()
            auth = method + request + urlencodedQuery
            signature = self.hmac(self.encode(auth), self.encode(self.secret), hashlib.sha512)
            headers = {
                'X-LA-APIKEY': self.apiKey,
                'X-LA-SIGNATURE': signature,
                'X-LA-DIGEST': 'HMAC-SHA512',  # HMAC-SHA384, HMAC-SHA512, optional
            }
            if method == 'POST':
                headers['Content-Type'] = 'application/json'
                body = self.json(query)
        url = self.urls['api'] + requestString
        return {'url': url, 'method': method, 'body': body, 'headers': headers}

    def handle_errors(self, code, reason, url, method, headers, body, response, requestHeaders, requestBody):
        if not response:
            return
        #
        # {"result":false,"message":"invalid API key, signature or digest","error":"BAD_REQUEST","status":"FAILURE"}
        # {"result":false,"message":"request expired or bad <timeAlive>/<timestamp> format","error":"BAD_REQUEST","status":"FAILURE"}
        # {"message":"Internal Server Error","error":"INTERNAL_ERROR","status":"FAILURE"}
        # {"result":false,"message":"Internal error","error":"For input string: \"NaN\"","status":"FAILURE"}
        #
        message = self.safe_string(response, 'message')
        feedback = self.id + ' ' + body
        if message is not None:
            self.throw_exactly_matched_exception(self.exceptions['exact'], message, feedback)
            self.throw_broadly_matched_exception(self.exceptions['broad'], message, feedback)
        error = self.safe_value(response, 'error', {})
        errorMessage = self.safe_string(error, 'message')
        if errorMessage is not None:
            self.throw_exactly_matched_exception(self.exceptions['exact'], errorMessage, feedback)
            self.throw_broadly_matched_exception(self.exceptions['broad'], errorMessage, feedback)
            raise ExchangeError(feedback)  # unknown message
