# -*- 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 ccxt.async_support
from ccxt.async_support.base.ws.cache import ArrayCache, ArrayCacheBySymbolById
from ccxt.base.types import Any, Balances, Int, Order, OrderBook, Str, Ticker, Trade
from ccxt.async_support.base.ws.client import Client
from typing import List
from ccxt.base.errors import ExchangeError
from ccxt.base.errors import NotSupported


class probit(ccxt.async_support.probit):

    def describe(self) -> Any:
        return self.deep_extend(super(probit, self).describe(), {
            'has': {
                'ws': True,
                'watchBalance': True,
                'watchTicker': True,
                'watchTickers': False,
                'watchTrades': True,
                'watchTradesForSymbols': False,
                'watchMyTrades': True,
                'watchOrders': True,
                'watchOrderBook': True,
                'watchOHLCV': False,
            },
            'urls': {
                'api': {
                    'ws': 'wss://api.probit.com/api/exchange/v1/ws',
                },
                'test': {
                    'ws': 'wss://demo-api.probit.com/api/exchange/v1/ws',
                },
            },
            'options': {
                'watchOrderBook': {
                    'filter': 'order_books_l2',
                    'interval': 100,  # or 500
                },
            },
            'streaming': {
            },
        })

    async def watch_balance(self, params={}) -> Balances:
        """
        watch balance and get the amount of funds available for trading or funds locked in orders

        https://docs-en.probit.com/reference/balance-1

        :param dict [params]: extra parameters specific to the exchange API endpoint
        :returns dict: a `balance structure <https://docs.ccxt.com/#/?id=balance-structure>`
        """
        await self.authenticate(params)
        messageHash = 'balance'
        return await self.subscribe_private(messageHash, 'balance', params)

    def handle_balance(self, client: Client, message):
        #
        #     {
        #         "channel": "balance",
        #         "reset": False,
        #         "data": {
        #             "USDT": {
        #                 "available": "15",
        #                 "total": "15"
        #             }
        #         }
        #     }
        #
        messageHash = 'balance'
        self.parse_ws_balance(message)
        client.resolve(self.balance, messageHash)

    def parse_ws_balance(self, message):
        #
        #     {
        #         "channel": "balance",
        #         "reset": False,
        #         "data": {
        #             "USDT": {
        #                 "available": "15",
        #                 "total": "15"
        #             }
        #         }
        #     }
        #
        reset = self.safe_bool(message, 'reset', False)
        data = self.safe_value(message, 'data', {})
        currencyIds = list(data.keys())
        if reset:
            self.balance = {}
        for i in range(0, len(currencyIds)):
            currencyId = currencyIds[i]
            entry = data[currencyId]
            code = self.safe_currency_code(currencyId)
            account = self.account()
            account['free'] = self.safe_string(entry, 'available')
            account['total'] = self.safe_string(entry, 'total')
            self.balance[code] = account
        self.balance = self.safe_balance(self.balance)

    async def watch_ticker(self, symbol: str, params={}) -> Ticker:
        """
        watches a price ticker, a statistical calculation with the information calculated over the past 24 hours for a specific market

        https://docs-en.probit.com/reference/marketdata

        :param str symbol: unified symbol of the market to fetch the ticker for
        :param dict [params]: extra parameters specific to the exchange API endpoint
        :param int [params.interval]: Unit time to synchronize market information(ms). Available units: 100, 500
        :returns dict: a `ticker structure <https://docs.ccxt.com/#/?id=ticker-structure>`
        """
        channel = 'ticker'
        return await self.subscribe_public('watchTicker', symbol, 'ticker', channel, params)

    def handle_ticker(self, client: Client, message):
        #
        #     {
        #         "channel": "marketdata",
        #         "market_id": "BTC-USDT",
        #         "status": "ok",
        #         "lag": 0,
        #         "ticker": {
        #             "time": "2022-07-21T14:18:04.000Z",
        #             "last": "22591.3",
        #             "low": "22500.1",
        #             "high": "39790.7",
        #             "change": "-1224",
        #             "base_volume": "1002.32005445",
        #             "quote_volume": "23304489.385351021"
        #         },
        #         "reset": True
        #     }
        #
        marketId = self.safe_string(message, 'market_id')
        symbol = self.safe_symbol(marketId)
        ticker = self.safe_value(message, 'ticker', {})
        market = self.safe_market(marketId)
        parsedTicker = self.parse_ticker(ticker, market)
        messageHash = 'ticker:' + symbol
        self.tickers[symbol] = parsedTicker
        client.resolve(parsedTicker, messageHash)

    async def watch_trades(self, symbol: str, since: Int = None, limit: Int = None, params={}) -> List[Trade]:
        """
        get the list of most recent trades for a particular symbol

        https://docs-en.probit.com/reference/trade_history

        :param str symbol: unified symbol of the market to fetch trades for
        :param int [since]: timestamp in ms of the earliest trade to fetch
        :param int [limit]: the maximum amount of trades to fetch
        :param dict [params]: extra parameters specific to the exchange API endpoint
        :param int [params.interval]: Unit time to synchronize market information(ms). Available units: 100, 500
        :returns dict[]: a list of `trade structures <https://docs.ccxt.com/#/?id=public-trades>`
        """
        channel = 'recent_trades'
        symbol = self.safe_symbol(symbol)
        trades = await self.subscribe_public('watchTrades', symbol, 'trades', channel, params)
        if self.newUpdates:
            limit = trades.getLimit(symbol, limit)
        return self.filter_by_symbol_since_limit(trades, symbol, since, limit, True)

    def handle_trades(self, client: Client, message):
        #
        #     {
        #         "channel": "marketdata",
        #         "market_id": "BTC-USDT",
        #         "status": "ok",
        #         "lag": 0,
        #         "recent_trades": [
        #             {
        #                 "id": "BTC-USDT:8010233",
        #                 "price": "22701.4",
        #                 "quantity": "0.011011",
        #                 "time": "2022-07-21T13:40:40.983Z",
        #                 "side": "buy",
        #                 "tick_direction": "up"
        #             }
        #             ...
        #         ]
        #         "reset": True
        #     }
        #
        marketId = self.safe_string(message, 'market_id')
        symbol = self.safe_symbol(marketId)
        market = self.safe_market(marketId)
        trades = self.safe_value(message, 'recent_trades', [])
        if self.safe_bool(message, 'reset', False):
            return  # see comment in handleMessage
        messageHash = 'trades:' + symbol
        stored = self.safe_value(self.trades, symbol)
        if stored is None:
            limit = self.safe_integer(self.options, 'tradesLimit', 1000)
            stored = ArrayCache(limit)
            self.trades[symbol] = stored
        for i in range(0, len(trades)):
            trade = trades[i]
            parsed = self.parse_trade(trade, market)
            stored.append(parsed)
        self.trades[symbol] = stored
        client.resolve(self.trades[symbol], messageHash)

    async def watch_my_trades(self, symbol: Str = None, since: Int = None, limit: Int = None, params={}) -> List[Trade]:
        """
        get the list of trades associated with the user

        https://docs-en.probit.com/reference/trade_history

        :param str symbol: unified symbol of the market to fetch trades for
        :param int [since]: timestamp in ms of the earliest trade to fetch
        :param int [limit]: the maximum amount of trades to fetch
        :param dict [params]: extra parameters specific to the exchange API endpoint
        :returns dict[]: a list of `trade structures <https://docs.ccxt.com/#/?id=public-trades>`
        """
        await self.load_markets()
        await self.authenticate(params)
        messageHash = 'trades'
        if symbol is not None:
            symbol = self.safe_symbol(symbol)
            messageHash = messageHash + ':' + symbol
        trades = await self.subscribe_private(messageHash, 'trade_history', params)
        if self.newUpdates:
            limit = trades.getLimit(symbol, limit)
        return self.filter_by_symbol_since_limit(trades, symbol, since, limit, True)

    def handle_my_trades(self, client: Client, message):
        #
        #     {
        #         "channel": "trade_history",
        #         "reset": False,
        #         "data": [{
        #             "id": "BTC-USDT:8010722",
        #             "order_id": "4124999207",
        #             "side": "buy",
        #             "fee_amount": "0.0134999868096",
        #             "fee_currency_id": "USDT",
        #             "status": "settled",
        #             "price": "23136.7",
        #             "quantity": "0.00032416",
        #             "cost": "7.499992672",
        #             "time": "2022-07-21T17:09:33.056Z",
        #             "market_id": "BTC-USDT"
        #         }]
        #     }
        #
        rawTrades = self.safe_value(message, 'data', [])
        length = len(rawTrades)
        if length == 0:
            return
        if self.safe_bool(message, 'reset', False):
            return  # see comment in handleMessage
        messageHash = 'trades'
        stored = self.myTrades
        if stored is None:
            limit = self.safe_integer(self.options, 'tradesLimit', 1000)
            stored = ArrayCacheBySymbolById(limit)
            self.myTrades = stored
        trades = self.parse_trades(rawTrades)
        tradeSymbols: dict = {}
        for j in range(0, len(trades)):
            trade = trades[j]
            # don't include 'executed' state, because it's just blanket state of the trade, emited before actual trade event
            if self.safe_string(trade['info'], 'status') == 'executed':
                continue
            tradeSymbols[trade['symbol']] = True
            stored.append(trade)
        unique = list(tradeSymbols.keys())
        uniqueLength = len(unique)
        if uniqueLength == 0:
            return
        for i in range(0, len(unique)):
            symbol = unique[i]
            symbolSpecificMessageHash = messageHash + ':' + symbol
            client.resolve(stored, symbolSpecificMessageHash)
        client.resolve(stored, messageHash)

    async def watch_orders(self, symbol: Str = None, since: Int = None, limit: Int = None, params={}) -> List[Order]:
        """
        watches information on an order made by the user

        https://docs-en.probit.com/reference/open_order

        :param str symbol: unified symbol of the market the order was made in
        :param int [since]: timestamp in ms of the earliest order to watch
        :param int [limit]: the maximum amount of orders to watch
        :param dict [params]: extra parameters specific to the exchange API endpoint
        :param str [params.channel]: choose what channel to use. Can open_order or order_history.
        :returns dict: An `order structure <https://docs.ccxt.com/#/?id=order-structure>`
        """
        await self.authenticate(params)
        messageHash = 'orders'
        if symbol is not None:
            symbol = self.safe_symbol(symbol)
            messageHash = messageHash + ':' + symbol
        orders = await self.subscribe_private(messageHash, 'open_order', params)
        if self.newUpdates:
            limit = orders.getLimit(symbol, limit)
        return self.filter_by_symbol_since_limit(orders, symbol, since, limit, True)

    def handle_orders(self, client: Client, message):
        #
        #     {
        #         "channel": "order_history",
        #         "reset": True,
        #         "data": [{
        #                 "id": "4124999207",
        #                 "user_id": "633dc56a-621b-4680-8a4e-85a823499b6d",
        #                 "market_id": "BTC-USDT",
        #                 "type": "market",
        #                 "side": "buy",
        #                 "limit_price": "0",
        #                 "time_in_force": "ioc",
        #                 "filled_cost": "7.499992672",
        #                 "filled_quantity": "0.00032416",
        #                 "open_quantity": "0",
        #                 "status": "filled",
        #                 "time": "2022-07-21T17:09:33.056Z",
        #                 "client_order_id": '',
        #                 "cost": "7.5"
        #             },
        #             ...
        #         ]
        #     }
        #
        rawOrders = self.safe_value(message, 'data', [])
        length = len(rawOrders)
        if length == 0:
            return
        messageHash = 'orders'
        reset = self.safe_bool(message, 'reset', False)
        stored = self.orders
        if stored is None or reset:
            limit = self.safe_integer(self.options, 'ordersLimit', 1000)
            stored = ArrayCacheBySymbolById(limit)
            self.orders = stored
        orderSymbols: dict = {}
        for i in range(0, len(rawOrders)):
            rawOrder = rawOrders[i]
            order = self.parse_order(rawOrder)
            orderSymbols[order['symbol']] = True
            stored.append(order)
        unique = list(orderSymbols.keys())
        for i in range(0, len(unique)):
            symbol = unique[i]
            symbolSpecificMessageHash = messageHash + ':' + symbol
            client.resolve(stored, symbolSpecificMessageHash)
        client.resolve(stored, messageHash)

    async def watch_order_book(self, symbol: str, limit: Int = None, params={}) -> OrderBook:
        """
        watches information on open orders with bid(buy) and ask(sell) prices, volumes and other data

        https://docs-en.probit.com/reference/marketdata

        :param str symbol: unified symbol of the market to fetch the order book for
        :param int [limit]: the maximum amount of order book entries to return
        :param dict [params]: extra parameters specific to the exchange API endpoint
        :returns dict: A dictionary of `order book structures <https://docs.ccxt.com/#/?id=order-book-structure>` indexed by market symbols
        """
        channel = None
        channel, params = self.handle_option_and_params(params, 'watchOrderBook', 'filter', 'order_books')
        orderbook = await self.subscribe_public('watchOrderBook', symbol, 'orderbook', channel, params)
        return orderbook.limit()

    async def subscribe_private(self, messageHash, channel, params):
        url = self.urls['api']['ws']
        subscribe: dict = {
            'type': 'subscribe',
            'channel': channel,
        }
        request = self.extend(subscribe, params)
        subscribeHash = messageHash
        return await self.watch(url, messageHash, request, subscribeHash)

    async def subscribe_public(self, methodName: str, symbol: str, dataType, filter, params={}):
        await self.load_markets()
        market = self.market(symbol)
        symbol = market['symbol']
        url = self.urls['api']['ws']
        client = self.client(url)
        subscribeHash = 'marketdata:' + symbol
        messageHash = dataType + ':' + symbol
        filters = {}
        if subscribeHash in client.subscriptions:
            # already subscribed
            filters = client.subscriptions[subscribeHash]
            if not (filter in filters):
                # resubscribe
                del client.subscriptions[subscribeHash]
        filters[filter] = True
        keys = list(filters.keys())
        interval = None
        interval, params = self.handle_option_and_params(params, methodName, 'interval', 100)
        request: dict = {
            'type': 'subscribe',
            'channel': 'marketdata',
            'market_id': market['id'],
            'filter': keys,
            'interval': interval,
        }
        request = self.extend(request, params)
        return await self.watch(url, messageHash, request, subscribeHash, filters)

    def handle_order_book(self, client: Client, message, orderBook):
        #
        #     {
        #         "channel": "marketdata",
        #         "market_id": "BTC-USDT",
        #         "status": "ok",
        #         "lag": 0,
        #         "order_books": [
        #           {side: "buy", price: '1420.7', quantity: "0.057"},
        #           ...
        #         ],
        #         "reset": True
        #     }
        #
        marketId = self.safe_string(message, 'market_id')
        symbol = self.safe_symbol(marketId)
        dataBySide = self.group_by(orderBook, 'side')
        messageHash = 'orderbook:' + symbol
        # orderbook = self.safe_value(self.orderbooks, symbol)
        if not (symbol in self.orderbooks):
            self.orderbooks[symbol] = self.order_book({})
        orderbook = self.orderbooks[symbol]
        reset = self.safe_bool(message, 'reset', False)
        if reset:
            snapshot = self.parse_order_book(dataBySide, symbol, None, 'buy', 'sell', 'price', 'quantity')
            orderbook.reset(snapshot)
        else:
            self.handle_delta(orderbook, dataBySide)
        client.resolve(orderbook, messageHash)

    def handle_bid_asks(self, bookSide, bidAsks):
        for i in range(0, len(bidAsks)):
            bidAsk = bidAsks[i]
            parsed = self.parse_bid_ask(bidAsk, 'price', 'quantity')
            bookSide.storeArray(parsed)

    def handle_delta(self, orderbook, delta):
        storedBids = orderbook['bids']
        storedAsks = orderbook['asks']
        asks = self.safe_value(delta, 'sell', [])
        bids = self.safe_value(delta, 'buy', [])
        self.handle_bid_asks(storedBids, bids)
        self.handle_bid_asks(storedAsks, asks)

    def handle_error_message(self, client: Client, message):
        #
        #     {
        #         "errorCode": "INVALID_ARGUMENT",
        #         "message": '',
        #         "details": {
        #             "interval": "invalid"
        #         }
        #     }
        #
        code = self.safe_string(message, 'errorCode')
        errMessage = self.safe_string(message, 'message', '')
        details = self.safe_value(message, 'details')
        feedback = self.id + ' ' + code + ' ' + errMessage + ' ' + self.json(details)
        if 'exact' in self.exceptions:
            self.throw_exactly_matched_exception(self.exceptions['exact'], code, feedback)
        if 'broad' in self.exceptions:
            self.throw_broadly_matched_exception(self.exceptions['broad'], errMessage, feedback)
        raise ExchangeError(feedback)

    def handle_authenticate(self, client: Client, message):
        #
        #     {type: "authorization", result: "ok"}
        #
        result = self.safe_string(message, 'result')
        future = client.subscriptions['authenticated']
        if result == 'ok':
            messageHash = 'authenticated'
            client.resolve(message, messageHash)
        else:
            future.reject(message)
            del client.subscriptions['authenticated']

    def handle_market_data(self, client: Client, message):
        ticker = self.safe_value(message, 'ticker')
        if ticker is not None:
            self.handle_ticker(client, message)
        trades = self.safe_value(message, 'recent_trades', [])
        tradesLength = len(trades)
        if tradesLength:
            self.handle_trades(client, message)
        orderBook = self.safe_value_n(message, ['order_books', 'order_books_l1', 'order_books_l2', 'order_books_l3', 'order_books_l4'], [])
        orderBookLength = len(orderBook)
        if orderBookLength:
            self.handle_order_book(client, message, orderBook)

    def handle_message(self, client: Client, message):
        #
        #     {
        #         "errorCode": "INVALID_ARGUMENT",
        #         "message": '',
        #         "details": {
        #             "interval": "invalid"
        #         }
        #     }
        #
        # Note about 'reset' field
        # 'reset': True field - it happens once after initial subscription, which just returns old items by the moment of subscription(like "fetchMyTrades" does)
        #
        errorCode = self.safe_string(message, 'errorCode')
        if errorCode is not None:
            self.handle_error_message(client, message)
            return
        type = self.safe_string(message, 'type')
        if type == 'authorization':
            self.handle_authenticate(client, message)
            return
        handlers: dict = {
            'marketdata': self.handle_market_data,
            'balance': self.handle_balance,
            'trade_history': self.handle_my_trades,
            'open_order': self.handle_orders,
            'order_history': self.handle_orders,
        }
        channel = self.safe_string(message, 'channel')
        handler = self.safe_value(handlers, channel)
        if handler is not None:
            handler(client, message)
            return
        error = NotSupported(self.id + ' handleMessage: unknown message: ' + self.json(message))
        client.reject(error)

    async def authenticate(self, params={}):
        url = self.urls['api']['ws']
        client = self.client(url)
        messageHash = 'authenticated'
        expires = self.safe_integer(self.options, 'expires', 0)
        future = self.safe_value(client.subscriptions, messageHash)
        if (future is None) or (self.milliseconds() > expires):
            response = await self.sign_in()
            #
            #     {
            #         "access_token": "0ttDv/2hTTn3bLi8GP1gKaneiEQ6+0hOBenPrxNQt2s=",
            #         "token_type": "bearer",
            #         "expires_in": 900
            #     }
            #
            accessToken = self.safe_string(response, 'access_token')
            request: dict = {
                'type': 'authorization',
                'token': accessToken,
            }
            future = await self.watch(url, messageHash, self.extend(request, params), messageHash)
            client.subscriptions[messageHash] = future
        return future
