# -*- 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
from ccxt.base.precise import Precise


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_string(trade, 'maker_fee_rate')
        else:
            parsed['takerOrMaker'] = 'taker'
            feeRate = self.safe_string(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_string(parsed, 'cost')
            feeCost = Precise.string_mul(cost, feeRate)
        parsed['fee'] = {
            'rate': self.parse_number(feeRate),
            'cost': self.parse_number(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.safe_string(tradeEntry, 'cost', '0')
                            totalAmount = self.safe_string(tradeEntry, 'amount', '0')
                        if not Precise.string_eq(totalAmount, '0'):
                            previousOrder['average'] = self.parse_number(Precise.string_div(totalCost, totalAmount))
                        previousOrder['cost'] = self.parse_number(totalCost)
                        previousOrderFilled = self.safe_string(previousOrder, 'filled')
                        if previousOrderFilled is not None:
                            previousOrder['filled'] = self.parse_number(Precise.string_add(previousOrderFilled, self.safe_string(trade, 'amount')))
                            if previousOrder['amount'] is not None:
                                previousOrder['remaining'] = self.parse_number(Precise.string_sub(self.safe_string(previousOrder, 'amount'), self.safe_string(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'])
                            previousOrderFee = self.safe_dict(previousOrder, 'fee')
                            tradeFee = self.safe_dict(trade, 'fee')
                            previousOrder['fee']['cost'] = self.parse_number(Precise.string_add(self.safe_string(previousOrderFee, 'cost'), self.safe_string(tradeFee, '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_string_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_string(order, 'remaining_size')
        type = self.safe_string(order, 'type')
        filled = None
        if (amount is not None) and (remaining is not None):
            filled = Precise.string_sub(amount, remaining)
        elif type == 'received':
            filled = '0'
            if amount is not None:
                remaining = Precise.string_sub(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': self.parse_number(amount),
            'cost': None,
            'average': None,
            'filled': self.parse_number(filled),
            'remaining': self.parse_number(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)
