# -*- 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
import hashlib
from ccxt.base.types import Any, Bool, Int, Order, OrderBook, Str, Strings, Ticker, Tickers, 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 AuthenticationError
from ccxt.base.errors import ArgumentsRequired
from ccxt.base.errors import BadRequest
from ccxt.base.errors import BadSymbol


class coinbaseexchange(ccxt.async_support.coinbaseexchange):

    def describe(self) -> Any:
        return self.deep_extend(super(coinbaseexchange, self).describe(), {
            'has': {
                'ws': True,
                'watchOHLCV': False,  # missing on the exchange side
                'watchOrderBook': True,
                'watchOrderBookForSymbols': True,
                'watchTicker': True,
                'watchTickers': True,
                'watchTrades': True,
                'watchTradesForSymbols': True,
                'watchMyTradesForSymbols': True,
                'watchBalance': False,
                'watchStatus': False,  # for now
                'watchOrders': True,
                'watchOrdersForSymbols': True,
                'watchMyTrades': True,
            },
            'urls': {
                'api': {
                    'ws': 'wss://ws-feed.exchange.coinbase.com',
                },
                'test': {
                    'ws': 'wss://ws-feed-public.sandbox.exchange.coinbase.com',
                },
            },
            'options': {
                'tradesLimit': 1000,
                'ordersLimit': 1000,
                'myTradesLimit': 1000,
            },
        })

    def authenticate(self):
        self.check_required_credentials()
        path = '/users/self/verify'
        nonce = self.nonce()
        payload = str(nonce) + 'GET' + path
        signature = self.hmac(self.encode(payload), self.base64_to_binary(self.secret), hashlib.sha256, 'base64')
        return {
            'timestamp': nonce,
            'key': self.apiKey,
            'signature': signature,
            'passphrase': self.password,
        }

    async def subscribe(self, name, symbol=None, messageHashStart=None, params={}):
        await self.load_markets()
        market = None
        messageHash = messageHashStart
        productIds = []
        if symbol is not None:
            market = self.market(symbol)
            messageHash += ':' + market['id']
            productIds.append(market['id'])
        url = self.urls['api']['ws']
        if 'signature' in params:
            # need to distinguish between public trades and user trades
            url = url + '?'
        subscribe: dict = {
            'type': 'subscribe',
            'product_ids': productIds,
            'channels': [
                name,
            ],
        }
        request = self.extend(subscribe, params)
        return await self.watch(url, messageHash, request, messageHash)

    async def subscribe_multiple(self, name, symbols=[], messageHashStart=None, params={}):
        await self.load_markets()
        market = None
        symbols = self.market_symbols(symbols)
        messageHashes = []
        productIds = []
        for i in range(0, len(symbols)):
            symbol = symbols[i]
            market = self.market(symbol)
            productIds.append(market['id'])
            messageHashes.append(messageHashStart + ':' + market['symbol'])
        url = self.urls['api']['ws']
        if 'signature' in params:
            # need to distinguish between public trades and user trades
            url = url + '?'
        subscribe: dict = {
            'type': 'subscribe',
            'product_ids': productIds,
            'channels': [
                name,
            ],
        }
        request = self.extend(subscribe, params)
        return await self.watch_multiple(url, messageHashes, request, messageHashes)

    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
        :param str symbol: unified symbol of the market to fetch the ticker for
        :param dict [params]: extra parameters specific to the exchange API endpoint
        :returns dict: a `ticker structure <https://docs.ccxt.com/#/?id=ticker-structure>`
        """
        name = 'ticker'
        return await self.subscribe(name, symbol, name, params)

    async def watch_tickers(self, symbols: Strings = None, params={}) -> Tickers:
        """
        watches a price ticker, a statistical calculation with the information calculated over the past 24 hours for all markets of a specific list
        :param str[] [symbols]: unified symbol of the market to fetch the ticker for
        :param dict [params]: extra parameters specific to the exchange API endpoint
        :param str [params.channel]: the channel to subscribe to, tickers by default. Can be tickers, sprd-tickers, index-tickers, block-tickers
        :returns dict: a `ticker structure <https://docs.ccxt.com/#/?id=ticker-structure>`
        """
        await self.load_markets()
        symbolsLength = len(symbols)
        if symbolsLength == 0:
            raise BadSymbol(self.id + ' watchTickers requires a non-empty symbols array')
        channel = 'ticker'
        messageHash = 'ticker'
        ticker = await self.subscribe_multiple(channel, symbols, messageHash, params)
        if self.newUpdates:
            result: dict = {}
            result[ticker['symbol']] = ticker
            return result
        return self.filter_by_array(self.tickers, 'symbol', symbols)

    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
        :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()
        symbol = self.symbol(symbol)
        name = 'matches'
        trades = await self.subscribe(name, symbol, name, params)
        if self.newUpdates:
            limit = trades.getLimit(symbol, limit)
        return self.filter_by_since_limit(trades, since, limit, 'timestamp', True)

    async def watch_trades_for_symbols(self, symbols: List[str], since: Int = None, limit: Int = None, params={}) -> List[Trade]:
        """
        get the list of most recent trades for a particular symbol
        :param str[] symbols: 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>`
        """
        symbolsLength = len(symbols)
        if symbolsLength == 0:
            raise BadRequest(self.id + ' watchTradesForSymbols() requires a non-empty array of symbols')
        await self.load_markets()
        symbols = self.market_symbols(symbols)
        name = 'matches'
        trades = await self.subscribe_multiple(name, symbols, name, params)
        if self.newUpdates:
            first = self.safe_value(trades, 0)
            tradeSymbol = self.safe_string(first, 'symbol')
            limit = trades.getLimit(tradeSymbol, limit)
        return self.filter_by_since_limit(trades, since, limit, 'timestamp', True)

    async def watch_my_trades(self, symbol: Str = None, since: Int = None, limit: Int = None, params={}) -> List[Trade]:
        """
        watches information on multiple trades made by the user
        :param str symbol: unified market symbol of the market trades were made in
        :param int [since]: the earliest time in ms to fetch trades for
        :param int [limit]: the maximum number of trade structures to retrieve
        :param dict [params]: extra parameters specific to the exchange API endpoint
        :returns dict[]: a list of `trade structures <https://docs.ccxt.com/#/?id=trade-structure>`
        """
        if symbol is None:
            raise ArgumentsRequired(self.id + ' watchMyTrades() requires a symbol argument')
        await self.load_markets()
        symbol = self.symbol(symbol)
        name = 'user'
        messageHash = 'myTrades'
        authentication = self.authenticate()
        trades = await self.subscribe(name, symbol, messageHash, self.extend(params, authentication))
        if self.newUpdates:
            limit = trades.getLimit(symbol, limit)
        return self.filter_by_since_limit(trades, since, limit, 'timestamp', True)

    async def watch_my_trades_for_symbols(self, symbols: List[str], since: Int = None, limit: Int = None, params={}) -> List[Trade]:
        """
        watches information on multiple trades made by the user
        :param str[] symbols: unified symbol of the market to fetch trades for
        :param int [since]: the earliest time in ms to fetch trades for
        :param int [limit]: the maximum number of trade structures to retrieve
        :param dict [params]: extra parameters specific to the exchange API endpoint
        :returns dict[]: a list of `trade structures <https://docs.ccxt.com/#/?id=trade-structure>`
        """
        symbols = self.market_symbols(symbols, None, False)
        await self.load_markets()
        name = 'user'
        messageHash = 'myTrades'
        authentication = self.authenticate()
        trades = await self.subscribe_multiple(name, symbols, messageHash, self.extend(params, authentication))
        if self.newUpdates:
            first = self.safe_value(trades, 0)
            tradeSymbol = self.safe_string(first, 'symbol')
            limit = trades.getLimit(tradeSymbol, limit)
        return self.filter_by_since_limit(trades, since, limit, 'timestamp', True)

    async def watch_orders_for_symbols(self, symbols: List[str], since: Int = None, limit: Int = None, params={}) -> List[Order]:
        """
        watches information on multiple orders made by the user
        :param str[] symbols: unified symbol of the market to fetch orders for
        :param int [since]: the earliest time in ms to fetch orders for
        :param int [limit]: the maximum number of trade structures to retrieve
        :param dict [params]: extra parameters specific to the exchange API endpoint
        :returns dict[]: a list of `order structures <https://docs.ccxt.com/#/?id=order-structure>`
        """
        await self.load_markets()
        symbols = self.market_symbols(symbols, None, False)
        name = 'user'
        messageHash = 'orders'
        authentication = self.authenticate()
        orders = await self.subscribe_multiple(name, symbols, messageHash, self.extend(params, authentication))
        if self.newUpdates:
            first = self.safe_value(orders, 0)
            tradeSymbol = self.safe_string(first, 'symbol')
            limit = orders.getLimit(tradeSymbol, limit)
        return self.filter_by_since_limit(orders, since, limit, 'timestamp', True)

    async def watch_orders(self, symbol: Str = None, since: Int = None, limit: Int = None, params={}) -> List[Order]:
        """
        watches information on multiple orders made by the user
        :param str symbol: unified market symbol of the market orders were made in
        :param int [since]: the earliest time in ms to fetch orders for
        :param int [limit]: the maximum number of order structures to retrieve
        :param dict [params]: extra parameters specific to the exchange API endpoint
        :returns dict[]: a list of `order structures <https://docs.ccxt.com/#/?id=order-structure>`
        """
        if symbol is None:
            raise BadSymbol(self.id + ' watchMyTrades requires a symbol')
        await self.load_markets()
        symbol = self.symbol(symbol)
        name = 'user'
        messageHash = 'orders'
        authentication = self.authenticate()
        orders = await self.subscribe(name, symbol, messageHash, self.extend(params, authentication))
        if self.newUpdates:
            limit = orders.getLimit(symbol, limit)
        return self.filter_by_since_limit(orders, since, limit, 'timestamp', True)

    async def watch_order_book_for_symbols(self, symbols: List[str], limit: Int = None, params={}) -> OrderBook:
        """
        watches information on open orders with bid(buy) and ask(sell) prices, volumes and other data
        :param str[] symbols: unified array of symbols
        :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
        """
        symbolsLength = len(symbols)
        if symbolsLength == 0:
            raise BadRequest(self.id + ' watchOrderBookForSymbols() requires a non-empty array of symbols')
        name = 'level2'
        await self.load_markets()
        symbols = self.market_symbols(symbols)
        marketIds = self.market_ids(symbols)
        messageHashes = []
        for i in range(0, symbolsLength):
            marketId = marketIds[i]
            messageHashes.append(name + ':' + marketId)
        url = self.urls['api']['ws']
        subscribe: dict = {
            'type': 'subscribe',
            'product_ids': marketIds,
            'channels': [
                name,
            ],
        }
        request = self.extend(subscribe, params)
        subscription: dict = {
            'messageHash': name,
            'symbols': symbols,
            'marketIds': marketIds,
            'limit': limit,
        }
        authentication = self.authenticate()
        orderbook = await self.watch_multiple(url, messageHashes, self.extend(request, authentication), messageHashes, subscription)
        return orderbook.limit()

    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
        :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
        """
        name = 'level2'
        await self.load_markets()
        market = self.market(symbol)
        symbol = market['symbol']
        messageHash = name + ':' + market['id']
        url = self.urls['api']['ws']
        subscribe: dict = {
            'type': 'subscribe',
            'product_ids': [
                market['id'],
            ],
            'channels': [
                name,
            ],
        }
        request = self.extend(subscribe, params)
        subscription: dict = {
            'messageHash': messageHash,
            'symbol': symbol,
            'marketId': market['id'],
            'limit': limit,
        }
        authentication = self.authenticate()
        orderbook = await self.watch(url, messageHash, self.extend(request, authentication), messageHash, subscription)
        return orderbook.limit()

    def handle_trade(self, client: Client, message):
        #
        #     {
        #         "type": "match",
        #         "trade_id": 82047307,
        #         "maker_order_id": "0f358725-2134-435e-be11-753912a326e0",
        #         "taker_order_id": "252b7002-87a3-425c-ac73-f5b9e23f3caf",
        #         "side": "sell",
        #         "size": "0.00513192",
        #         "price": "9314.78",
        #         "product_id": "BTC-USD",
        #         "sequence": 12038915443,
        #         "time": "2020-01-31T20:03:41.158814Z"
        #     }
        #
        marketId = self.safe_string(message, 'product_id')
        if marketId is not None:
            trade = self.parse_ws_trade(message)
            symbol = trade['symbol']
            # the exchange sends type = 'match'
            # but requires 'matches' upon subscribing
            # therefore we resolve 'matches' here instead of 'match'
            type = 'matches'
            messageHash = type + ':' + marketId
            tradesArray = self.safe_value(self.trades, symbol)
            if tradesArray is None:
                tradesLimit = self.safe_integer(self.options, 'tradesLimit', 1000)
                tradesArray = ArrayCache(tradesLimit)
                self.trades[symbol] = tradesArray
            tradesArray.append(trade)
            client.resolve(tradesArray, messageHash)
        return message

    def handle_my_trade(self, client: Client, message):
        marketId = self.safe_string(message, 'product_id')
        if marketId is not None:
            trade = self.parse_ws_trade(message)
            type = 'myTrades'
            messageHash = type + ':' + marketId
            tradesArray = self.myTrades
            if tradesArray is None:
                limit = self.safe_integer(self.options, 'myTradesLimit', 1000)
                tradesArray = ArrayCacheBySymbolById(limit)
                self.myTrades = tradesArray
            tradesArray.append(trade)
            client.resolve(tradesArray, messageHash)
        return message

    def parse_ws_trade(self, trade, market=None):
        #
        # private trades
        # {
        #     "type": "match",
        #     "trade_id": 10,
        #     "sequence": 50,
        #     "maker_order_id": "ac928c66-ca53-498f-9c13-a110027a60e8",
        #     "taker_order_id": "132fb6ae-456b-4654-b4e0-d681ac05cea1",
        #     "time": "2014-11-07T08:19:27.028459Z",
        #     "product_id": "BTC-USD",
        #     "size": "5.23512",
        #     "price": "400.23",
        #     "side": "sell",
        #     "taker_user_id: "5844eceecf7e803e259d0365",
        #     "user_id": "5844eceecf7e803e259d0365",
        #     "taker_profile_id": "765d1549-9660-4be2-97d4-fa2d65fa3352",
        #     "profile_id": "765d1549-9660-4be2-97d4-fa2d65fa3352",
        #     "taker_fee_rate": "0.005"
        # }
        #
        # {
        #     "type": "match",
        #     "trade_id": 10,
        #     "sequence": 50,
        #     "maker_order_id": "ac928c66-ca53-498f-9c13-a110027a60e8",
        #     "taker_order_id": "132fb6ae-456b-4654-b4e0-d681ac05cea1",
        #     "time": "2014-11-07T08:19:27.028459Z",
        #     "product_id": "BTC-USD",
        #     "size": "5.23512",
        #     "price": "400.23",
        #     "side": "sell",
        #     "maker_user_id: "5844eceecf7e803e259d0365",
        #     "maker_id": "5844eceecf7e803e259d0365",
        #     "maker_profile_id": "765d1549-9660-4be2-97d4-fa2d65fa3352",
        #     "profile_id": "765d1549-9660-4be2-97d4-fa2d65fa3352",
        #     "maker_fee_rate": "0.001"
        # }
        #
        # public trades
        # {
        #     "type": "received",
        #     "time": "2014-11-07T08:19:27.028459Z",
        #     "product_id": "BTC-USD",
        #     "sequence": 10,
        #     "order_id": "d50ec984-77a8-460a-b958-66f114b0de9b",
        #     "size": "1.34",
        #     "price": "502.1",
        #     "side": "buy",
        #     "order_type": "limit"
        # }
        parsed = super(coinbaseexchange, self).parse_trade(trade)
        feeRate = None
        isMaker = False
        if 'maker_fee_rate' in trade:
            isMaker = True
            parsed['takerOrMaker'] = 'maker'
            feeRate = self.safe_number(trade, 'maker_fee_rate')
        else:
            parsed['takerOrMaker'] = 'taker'
            feeRate = self.safe_number(trade, 'taker_fee_rate')
            # side always represents the maker side of the trade
            # so if we're taker, we invert it
            currentSide = parsed['side']
            parsed['side'] = self.safe_string({
                'buy': 'sell',
                'sell': 'buy',
            }, currentSide, currentSide)
        idKey = 'maker_order_id' if isMaker else 'taker_order_id'
        parsed['order'] = self.safe_string(trade, idKey)
        market = self.market(parsed['symbol'])
        feeCurrency = market['quote']
        feeCost = None
        if (parsed['cost'] is not None) and (feeRate is not None):
            cost = self.safe_number(parsed, 'cost')
            feeCost = cost * feeRate
        parsed['fee'] = {
            'rate': feeRate,
            'cost': feeCost,
            'currency': feeCurrency,
        }
        return parsed

    def parse_ws_order_status(self, status):
        statuses: dict = {
            'filled': 'closed',
            'canceled': 'canceled',
        }
        return self.safe_string(statuses, status, 'open')

    def handle_order(self, client: Client, message):
        #
        # Order is created
        #
        #     {
        #         "type": "received",
        #         "side": "sell",
        #         "product_id": "BTC-USDC",
        #         "time": "2021-03-05T16:42:21.878177Z",
        #         "sequence": 5641953814,
        #         "profile_id": "774ee0ce-fdda-405f-aa8d-47189a14ba0a",
        #         "user_id": "54fc141576dcf32596000133",
        #         "order_id": "11838707-bf9c-4d65-8cec-b57c9a7cab42",
        #         "order_type": "limit",
        #         "size": "0.0001",
        #         "price": "50000",
        #         "client_oid": "a317abb9-2b30-4370-ebfe-0deecb300180"
        #     }
        #
        #     {
        #         "type": "received",
        #         "time": "2014-11-09T08:19:27.028459Z",
        #         "product_id": "BTC-USD",
        #         "sequence": 12,
        #         "order_id": "dddec984-77a8-460a-b958-66f114b0de9b",
        #         "funds": "3000.234",
        #         "side": "buy",
        #         "order_type": "market"
        #     }
        #
        # Order is on the order book
        #
        #     {
        #         "type": "open",
        #         "side": "sell",
        #         "product_id": "BTC-USDC",
        #         "time": "2021-03-05T16:42:21.878177Z",
        #         "sequence": 5641953815,
        #         "profile_id": "774ee0ce-fdda-405f-aa8d-47189a14ba0a",
        #         "user_id": "54fc141576dcf32596000133",
        #         "price": "50000",
        #         "order_id": "11838707-bf9c-4d65-8cec-b57c9a7cab42",
        #         "remaining_size": "0.0001"
        #     }
        #
        # Order is partially or completely filled
        #
        #     {
        #         "type": "match",
        #         "side": "sell",
        #         "product_id": "BTC-USDC",
        #         "time": "2021-03-05T16:37:13.396107Z",
        #         "sequence": 5641897876,
        #         "profile_id": "774ee0ce-fdda-405f-aa8d-47189a14ba0a",
        #         "user_id": "54fc141576dcf32596000133",
        #         "trade_id": 5455505,
        #         "maker_order_id": "e5f5754d-70a3-4346-95a6-209bcb503629",
        #         "taker_order_id": "88bf7086-7b15-40ff-8b19-ab4e08516d69",
        #         "size": "0.00021019",
        #         "price": "47338.46",
        #         "taker_profile_id": "774ee0ce-fdda-405f-aa8d-47189a14ba0a",
        #         "taker_user_id": "54fc141576dcf32596000133",
        #         "taker_fee_rate": "0.005"
        #     }
        #
        # Order is canceled / closed
        #
        #     {
        #         "type": "done",
        #         "side": "buy",
        #         "product_id": "BTC-USDC",
        #         "time": "2021-03-05T16:37:13.396107Z",
        #         "sequence": 5641897877,
        #         "profile_id": "774ee0ce-fdda-405f-aa8d-47189a14ba0a",
        #         "user_id": "54fc141576dcf32596000133",
        #         "order_id": "88bf7086-7b15-40ff-8b19-ab4e08516d69",
        #         "reason": "filled"
        #     }
        #
        currentOrders = self.orders
        if currentOrders is None:
            limit = self.safe_integer(self.options, 'ordersLimit', 1000)
            currentOrders = ArrayCacheBySymbolById(limit)
            self.orders = currentOrders
        type = self.safe_string(message, 'type')
        marketId = self.safe_string(message, 'product_id')
        if marketId is not None:
            messageHash = 'orders:' + marketId
            symbol = self.safe_symbol(marketId)
            orderId = self.safe_string(message, 'order_id')
            makerOrderId = self.safe_string(message, 'maker_order_id')
            takerOrderId = self.safe_string(message, 'taker_order_id')
            orders = self.orders
            previousOrders = self.safe_value(orders.hashmap, symbol, {})
            previousOrder = self.safe_value(previousOrders, orderId)
            if previousOrder is None:
                previousOrder = self.safe_value_2(previousOrders, makerOrderId, takerOrderId)
            if previousOrder is None:
                parsed = self.parse_ws_order(message)
                orders.append(parsed)
                client.resolve(orders, messageHash)
            else:
                sequence = self.safe_integer(message, 'sequence')
                previousInfo = self.safe_value(previousOrder, 'info', {})
                previousSequence = self.safe_integer(previousInfo, 'sequence')
                if (previousSequence is None) or (sequence > previousSequence):
                    if type == 'match':
                        trade = self.parse_ws_trade(message)
                        if previousOrder['trades'] is None:
                            previousOrder['trades'] = []
                        previousOrder['trades'].append(trade)
                        previousOrder['lastTradeTimestamp'] = trade['timestamp']
                        totalCost = 0
                        totalAmount = 0
                        trades = previousOrder['trades']
                        for i in range(0, len(trades)):
                            tradeEntry = trades[i]
                            totalCost = self.sum(totalCost, tradeEntry['cost'])
                            totalAmount = self.sum(totalAmount, tradeEntry['amount'])
                        if totalAmount > 0:
                            previousOrder['average'] = totalCost / totalAmount
                        previousOrder['cost'] = totalCost
                        if previousOrder['filled'] is not None:
                            previousOrder['filled'] += trade['amount']
                            if previousOrder['amount'] is not None:
                                previousOrder['remaining'] = previousOrder['amount'] - previousOrder['filled']
                        if previousOrder['fee'] is None:
                            previousOrder['fee'] = {
                                'cost': 0,
                                'currency': trade['fee']['currency'],
                            }
                        if (previousOrder['fee']['cost'] is not None) and (trade['fee']['cost'] is not None):
                            previousOrder['fee']['cost'] = self.sum(previousOrder['fee']['cost'], trade['fee']['cost'])
                        # update the newUpdates count
                        orders.append(previousOrder)
                        client.resolve(orders, messageHash)
                    elif (type == 'received') or (type == 'done'):
                        info = self.extend(previousOrder['info'], message)
                        order = self.parse_ws_order(info)
                        keys = list(order.keys())
                        # update the reference
                        for i in range(0, len(keys)):
                            key = keys[i]
                            if order[key] is not None:
                                previousOrder[key] = order[key]
                        # update the newUpdates count
                        orders.append(previousOrder)
                        client.resolve(orders, messageHash)

    def parse_ws_order(self, order, market=None):
        id = self.safe_string(order, 'order_id')
        clientOrderId = self.safe_string(order, 'client_oid')
        marketId = self.safe_string(order, 'product_id')
        symbol = self.safe_symbol(marketId)
        side = self.safe_string(order, 'side')
        price = self.safe_number(order, 'price')
        amount = self.safe_number_2(order, 'size', 'funds')
        time = self.safe_string(order, 'time')
        timestamp = self.parse8601(time)
        reason = self.safe_string(order, 'reason')
        status = self.parse_ws_order_status(reason)
        orderType = self.safe_string(order, 'order_type')
        remaining = self.safe_number(order, 'remaining_size')
        type = self.safe_string(order, 'type')
        filled = None
        if (amount is not None) and (remaining is not None):
            filled = amount - remaining
        elif type == 'received':
            filled = 0
            if amount is not None:
                remaining = amount - filled
        return self.safe_order({
            'info': order,
            'symbol': symbol,
            'id': id,
            'clientOrderId': clientOrderId,
            'timestamp': timestamp,
            'datetime': self.iso8601(timestamp),
            'lastTradeTimestamp': None,
            'type': orderType,
            'timeInForce': None,
            'postOnly': None,
            'side': side,
            'price': price,
            'stopPrice': None,
            'triggerPrice': None,
            'amount': amount,
            'cost': None,
            'average': None,
            'filled': filled,
            'remaining': remaining,
            'status': status,
            'fee': None,
            'trades': None,
        })

    def handle_ticker(self, client: Client, message):
        #
        #     {
        #         "type": "ticker",
        #         "sequence": 12042642428,
        #         "product_id": "BTC-USD",
        #         "price": "9380.55",
        #         "open_24h": "9450.81000000",
        #         "volume_24h": "9611.79166047",
        #         "low_24h": "9195.49000000",
        #         "high_24h": "9475.19000000",
        #         "volume_30d": "327812.00311873",
        #         "best_bid": "9380.54",
        #         "best_ask": "9380.55",
        #         "side": "buy",
        #         "time": "2020-02-01T01:40:16.253563Z",
        #         "trade_id": 82062566,
        #         "last_size": "0.41969131"
        #     }
        #
        marketId = self.safe_string(message, 'product_id')
        if marketId is not None:
            ticker = self.parse_ticker(message)
            symbol = ticker['symbol']
            self.tickers[symbol] = ticker
            messageHash = 'ticker:' + symbol
            idMessageHash = 'ticker:' + marketId
            client.resolve(ticker, messageHash)
            client.resolve(ticker, idMessageHash)
        return message

    def parse_ticker(self, ticker, market=None) -> Ticker:
        #
        #     {
        #         "type": "ticker",
        #         "sequence": 7388547310,
        #         "product_id": "BTC-USDT",
        #         "price": "22345.67",
        #         "open_24h": "22308.13",
        #         "volume_24h": "470.21123644",
        #         "low_24h": "22150",
        #         "high_24h": "22495.15",
        #         "volume_30d": "25713.98401605",
        #         "best_bid": "22345.67",
        #         "best_bid_size": "0.10647825",
        #         "best_ask": "22349.68",
        #         "best_ask_size": "0.03131702",
        #         "side": "sell",
        #         "time": "2023-03-04T03:37:20.799258Z",
        #         "trade_id": 11586478,
        #         "last_size": "0.00352175"
        #     }
        #
        type = self.safe_string(ticker, 'type')
        if type is None:
            return super(coinbaseexchange, self).parse_ticker(ticker, market)
        marketId = self.safe_string(ticker, 'product_id')
        symbol = self.safe_symbol(marketId, market, '-')
        timestamp = self.parse8601(self.safe_string(ticker, 'time'))
        last = self.safe_string(ticker, 'price')
        return self.safe_ticker({
            'symbol': symbol,
            'timestamp': timestamp,
            'datetime': self.iso8601(timestamp),
            'high': self.safe_string(ticker, 'high_24h'),
            'low': self.safe_string(ticker, 'low_24h'),
            'bid': self.safe_string(ticker, 'best_bid'),
            'bidVolume': self.safe_string(ticker, 'best_bid_size'),
            'ask': self.safe_string(ticker, 'best_ask'),
            'askVolume': self.safe_string(ticker, 'best_ask_size'),
            'vwap': None,
            'open': self.safe_string(ticker, 'open_24h'),
            'close': last,
            'last': last,
            'previousClose': None,
            'change': None,
            'percentage': None,
            'average': None,
            'baseVolume': self.safe_string(ticker, 'volume_24h'),
            'quoteVolume': None,
            'info': ticker,
        })

    def handle_delta(self, bookside, delta):
        price = self.safe_number(delta, 0)
        amount = self.safe_number(delta, 1)
        bookside.store(price, amount)

    def handle_deltas(self, bookside, deltas):
        for i in range(0, len(deltas)):
            self.handle_delta(bookside, deltas[i])

    def handle_order_book(self, client: Client, message):
        #
        # first message(snapshot)
        #
        #     {
        #         "type": "snapshot",
        #         "product_id": "BTC-USD",
        #         "bids": [
        #             ["10101.10", "0.45054140"]
        #         ],
        #         "asks": [
        #             ["10102.55", "0.57753524"]
        #         ]
        #     }
        #
        # subsequent updates
        #
        #     {
        #         "type": "l2update",
        #         "product_id": "BTC-USD",
        #         "time": "2019-08-14T20:42:27.265Z",
        #         "changes": [
        #             ["buy", "10101.80000000", "0.162567"]
        #         ]
        #     }
        #
        type = self.safe_string(message, 'type')
        marketId = self.safe_string(message, 'product_id')
        market = self.safe_market(marketId, None, '-')
        symbol = market['symbol']
        name = 'level2'
        messageHash = name + ':' + marketId
        subscription = self.safe_value(client.subscriptions, messageHash, {})
        limit = self.safe_integer(subscription, 'limit')
        if type == 'snapshot':
            self.orderbooks[symbol] = self.order_book({}, limit)
            orderbook = self.orderbooks[symbol]
            self.handle_deltas(orderbook['asks'], self.safe_value(message, 'asks', []))
            self.handle_deltas(orderbook['bids'], self.safe_value(message, 'bids', []))
            orderbook['timestamp'] = None
            orderbook['datetime'] = None
            orderbook['symbol'] = symbol
            client.resolve(orderbook, messageHash)
        elif type == 'l2update':
            orderbook = self.orderbooks[symbol]
            timestamp = self.parse8601(self.safe_string(message, 'time'))
            changes = self.safe_value(message, 'changes', [])
            sides: dict = {
                'sell': 'asks',
                'buy': 'bids',
            }
            for i in range(0, len(changes)):
                change = changes[i]
                key = self.safe_string(change, 0)
                side = self.safe_string(sides, key)
                price = self.safe_number(change, 1)
                amount = self.safe_number(change, 2)
                bookside = orderbook[side]
                bookside.store(price, amount)
            orderbook['timestamp'] = timestamp
            orderbook['datetime'] = self.iso8601(timestamp)
            client.resolve(orderbook, messageHash)

    def handle_subscription_status(self, client: Client, message):
        #
        #     {
        #         "type": "subscriptions",
        #         "channels": [
        #             {
        #                 "name": "level2",
        #                 "product_ids": ["ETH-BTC"]
        #             }
        #         ]
        #     }
        #
        return message

    def handle_error_message(self, client: Client, message) -> Bool:
        #
        #     {
        #         "type": "error",
        #         "message": "error message",
        #         /* ..."""
        #     }
        #
        # auth error
        #
        #     {
        #         "type": "error",
        #         "message": "Authentication Failed",
        #         "reason": "{"message":"Invalid API Key"}"
        #     }
        #
        errMsg = self.safe_string(message, 'message')
        reason = self.safe_string(message, 'reason')
        try:
            if errMsg == 'Authentication Failed':
                raise AuthenticationError('Authentication failed: ' + reason)
            else:
                raise ExchangeError(self.id + ' ' + reason)
        except Exception as error:
            client.reject(error)
            return True

    def handle_message(self, client: Client, message):
        type = self.safe_string(message, 'type')
        methods: dict = {
            'snapshot': self.handle_order_book,
            'l2update': self.handle_order_book,
            'subscribe': self.handle_subscription_status,
            'ticker': self.handle_ticker,
            'received': self.handle_order,
            'open': self.handle_order,
            'change': self.handle_order,
            'done': self.handle_order,
            'error': self.handle_error_message,
        }
        length = len(client.url) - 0
        authenticated = client.url[length - 1] == '?'
        method = self.safe_value(methods, type)
        if method is None:
            if type == 'match':
                if authenticated:
                    self.handle_my_trade(client, message)
                    self.handle_order(client, message)
                else:
                    self.handle_trade(client, message)
        else:
            method(client, message)
