# -*- 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, Bool, Int, Market, Order, OrderBook, Str, Strings, Ticker, Tickers, FundingRate, FundingRates, Trade
from ccxt.async_support.base.ws.client import Client
from typing import List


class paradex(ccxt.async_support.paradex):

    def describe(self) -> Any:
        return self.deep_extend(super(paradex, self).describe(), {
            'has': {
                'ws': True,
                'watchFundingRate': True,
                'watchFundingRates': True,
                'watchTicker': True,
                'watchTickers': True,
                'watchOrderBook': True,
                'watchOrders': True,
                'watchTrades': True,
                'watchTradesForSymbols': False,
                'watchBalance': False,
                'watchOHLCV': False,
            },
            'urls': {
                'logo': 'https://x.com/tradeparadex/photo',
                'api': {
                    'ws': 'wss://ws.api.prod.paradex.trade/v1',
                },
                'test': {
                    'ws': 'wss://ws.api.testnet.paradex.trade/v1',
                },
                'www': 'https://www.paradex.trade/',
                'doc': 'https://docs.api.testnet.paradex.trade/',
                'fees': 'https://docs.paradex.trade/getting-started/trading-fees',
                'referral': '',
            },
            'options': {},
            'streaming': {},
        })

    def request_id(self):
        requestId = self.sum(self.safe_integer(self.options, 'requestId', 0), 1)
        self.options['requestId'] = requestId
        return requestId

    async def authenticate(self, params={}):
        url = self.urls['api']['ws']
        client = self.client(url)
        messageHash = 'authenticated'
        future = client.reusableFuture('authenticated')
        authenticated = self.safe_value(client.subscriptions, messageHash)
        if authenticated is None:
            token = await self.authenticateRest()
            request: dict = {
                'jsonrpc': '2.0',
                'id': self.request_id(),
                'method': 'auth',
                'params': {
                    'bearer': token,
                },
            }
            self.watch(url, messageHash, self.deep_extend(request, params), messageHash)
        return await future

    def handle_authentication_message(self, client: Client, message):
        #
        #     {
        #         "jsonrpc": "2.0",
        #         "id": 1,
        #         "result": {"node_id": "73cf456f7cb78d59"}
        #     }
        #
        result = self.safe_dict(message, 'result')
        if result is not None:
            # client.resolve(True, messageHash)
            future = self.safe_value(client.futures, 'authenticated')
            if future is not None:
                future.resolve(True)

    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.paradex.trade/ws/web-socket-channels/trades/trades

        :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()
        messageHash = 'trades.'
        if symbol is not None:
            market = self.market(symbol)
            messageHash += market['id']
        else:
            messageHash += 'ALL'
        url = self.urls['api']['ws']
        request: dict = {
            'jsonrpc': '2.0',
            'method': 'subscribe',
            'params': {
                'channel': messageHash,
            },
        }
        trades = await self.watch(url, messageHash, self.deep_extend(request, params), messageHash)
        if self.newUpdates:
            limit = trades.getLimit(symbol, limit)
        return self.filter_by_since_limit(trades, since, limit, 'timestamp', True)

    def handle_trade(self, client: Client, message):
        #
        #     {
        #         "jsonrpc": "2.0",
        #         "method": "subscription",
        #         "params": {
        #             "channel": "trades.ALL",
        #             "data": {
        #                 "id": "1718179273230201709233240002",
        #                 "market": "kBONK-USD-PERP",
        #                 "side": "BUY",
        #                 "size": "34028",
        #                 "price": "0.028776",
        #                 "created_at": 1718179273230,
        #                 "trade_type": "FILL"
        #             }
        #         }
        #     }
        #
        params = self.safe_dict(message, 'params', {})
        data = self.safe_dict(params, 'data', {})
        parsedTrade = self.parse_trade(data)
        symbol = parsedTrade['symbol']
        messageHash = self.safe_string(params, 'channel')
        stored = self.safe_value(self.trades, symbol)
        if stored is None:
            stored = ArrayCache(self.safe_integer(self.options, 'tradesLimit', 1000))
            self.trades[symbol] = stored
        stored.append(parsedTrade)
        client.resolve(stored, messageHash)
        return message

    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.paradex.trade/ws/web-socket-channels/order-book/order-book

        :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
        """
        await self.load_markets()
        market = self.market(symbol)
        messageHash = 'order_book.' + market['id'] + '.snapshot@15@100ms'
        url = self.urls['api']['ws']
        request: dict = {
            'jsonrpc': '2.0',
            'method': 'subscribe',
            'params': {
                'channel': messageHash,
            },
        }
        orderbook = await self.watch(url, messageHash, self.deep_extend(request, params), messageHash)
        return orderbook.limit()

    def handle_order_book(self, client: Client, message):
        #
        #     {
        #         "jsonrpc": "2.0",
        #         "method": "subscription",
        #         "params": {
        #             "channel": "order_book.BTC-USD-PERP.snapshot@15@50ms",
        #             "data": {
        #                 "seq_no": 14127815,
        #                 "market": "BTC-USD-PERP",
        #                 "last_updated_at": 1718267837265,
        #                 "update_type": "s",
        #                 "inserts": [
        #                     {
        #                         "side": "BUY",
        #                         "price": "67629.7",
        #                         "size": "0.992"
        #                     },
        #                     {
        #                         "side": "SELL",
        #                         "price": "69378.6",
        #                         "size": "3.137"
        #                     }
        #                 ],
        #                 "updates": [],
        #                 "deletes": []
        #             }
        #         }
        #     }
        #
        params = self.safe_dict(message, 'params', {})
        data = self.safe_dict(params, 'data', {})
        marketId = self.safe_string(data, 'market')
        market = self.safe_market(marketId)
        timestamp = self.safe_integer(data, 'last_updated_at')
        symbol = market['symbol']
        if not (symbol in self.orderbooks):
            self.orderbooks[symbol] = self.order_book()
        orderbookData = {
            'bids': [],
            'asks': [],
        }
        inserts = self.safe_list(data, 'inserts')
        for i in range(0, len(inserts)):
            insert = self.safe_dict(inserts, i)
            side = self.safe_string(insert, 'side')
            price = self.safe_string(insert, 'price')
            size = self.safe_string(insert, 'size')
            if side == 'BUY':
                orderbookData['bids'].append([price, size])
            else:
                orderbookData['asks'].append([price, size])
        orderbook = self.orderbooks[symbol]
        snapshot = self.parse_order_book(orderbookData, symbol, timestamp, 'bids', 'asks')
        snapshot['nonce'] = self.safe_integer(data, 'seq_no')
        orderbook.reset(snapshot)
        messageHash = self.safe_string(params, 'channel')
        client.resolve(orderbook, messageHash)

    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.paradex.trade/ws/web-socket-channels/markets-summary/markets-summary

        :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>`
        """
        await self.load_markets()
        symbol = self.symbol(symbol)
        channel = 'markets_summary'
        url = self.urls['api']['ws']
        request: dict = {
            'jsonrpc': '2.0',
            'method': 'subscribe',
            'params': {
                'channel': channel,
            },
        }
        messageHash = channel + '.' + symbol
        return await self.watch(url, messageHash, self.deep_extend(request, params), messageHash)

    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

        https://docs.paradex.trade/ws/web-socket-channels/markets-summary/markets-summary

        :param str[] symbols: 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>`
        """
        await self.load_markets()
        symbols = self.market_symbols(symbols)
        channel = 'markets_summary'
        url = self.urls['api']['ws']
        request: dict = {
            'jsonrpc': '2.0',
            'method': 'subscribe',
            'params': {
                'channel': channel,
            },
        }
        messageHashes = []
        if symbols is not None and isinstance(symbols, list):
            for i in range(0, len(symbols)):
                messageHash = channel + '.' + symbols[i]
                messageHashes.append(messageHash)
        else:
            messageHashes.append(channel)
        newTicker = await self.watch_multiple(url, messageHashes, self.deep_extend(request, params), messageHashes)
        if self.newUpdates:
            result: dict = {}
            result[newTicker['symbol']] = newTicker
            return result
        return self.filter_by_array(self.tickers, 'symbol', symbols)

    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

        https://docs.paradex.trade/ws/web-socket-channels/orders/orders

        :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>`
        """
        await self.load_markets()
        await self.authenticate()
        messageHash = 'orders'
        channel = 'orders.'
        if symbol is not None:
            market = self.market(symbol)
            symbol = market['symbol']
            channel += market['id']
            messageHash += ':' + symbol
        else:
            channel += 'ALL'
        url = self.urls['api']['ws']
        request: dict = {
            'jsonrpc': '2.0',
            'method': 'subscribe',
            'params': {
                'channel': channel,
            },
        }
        orders = await self.watch(url, messageHash, self.deep_extend(request, params), channel)
        if self.newUpdates:
            limit = orders.getLimit(symbol, limit)
        return self.filter_by_symbol_since_limit(orders, symbol, since, limit, True)

    def handle_order(self, client: Client, message):
        #
        #     {
        #         "jsonrpc": "2.0",
        #         "method": "subscription",
        #         "params": {
        #             "channel": "orders.ALL",
        #             "data": {
        #                 "account": "0x4638e3041366aa71720be63e32e53e1223316c7f0d56f7aa617542ed1e7512x",
        #                 "avg_fill_price": "26000",
        #                 "client_id": "x1234",
        #                 "cancel_reason": "",
        #                 "created_at": 1681493746016,
        #                 "flags": ["REDUCE_ONLY"],
        #                 "id": "123456",
        #                 "instruction": "GTC",
        #                 "last_updated_at": 1681493746016,
        #                 "market": "BTC-USD-PERP",
        #                 "price": "26000",
        #                 "remaining_size": "0",
        #                 "side": "BUY",
        #                 "size": "0.05",
        #                 "status": "NEW",
        #                 "type": "LIMIT"
        #             }
        #         }
        #     }
        #
        params = self.safe_dict(message, 'params', {})
        data = self.safe_dict(params, 'data', {})
        parsed = self.parse_order(data)
        symbol = self.safe_string(parsed, 'symbol')
        if self.orders is None:
            limit = self.safe_integer(self.options, 'ordersLimit', 1000)
            self.orders = ArrayCacheBySymbolById(limit)
        self.orders.append(parsed)
        messageHash = 'orders'
        client.resolve(self.orders, messageHash)
        if symbol is not None:
            symbolMessageHash = messageHash + ':' + symbol
            client.resolve(self.orders, symbolMessageHash)

    def handle_ticker(self, client: Client, message):
        #
        #     {
        #         "jsonrpc": "2.0",
        #         "method": "subscription",
        #         "params": {
        #             "channel": "markets_summary",
        #             "data": {
        #                 "symbol": "ORDI-USD-PERP",
        #                 "oracle_price": "49.80885481",
        #                 "mark_price": "49.80885481",
        #                 "last_traded_price": "62.038",
        #                 "bid": "49.822",
        #                 "ask": "58.167",
        #                 "volume_24h": "0",
        #                 "total_volume": "54542628.66054200416",
        #                 "created_at": 1718334307698,
        #                 "underlying_price": "47.93",
        #                 "open_interest": "6999.5",
        #                 "funding_rate": "0.03919997509811",
        #                 "price_change_rate_24h": ""
        #             }
        #         }
        #     }
        #
        params = self.safe_dict(message, 'params', {})
        data = self.safe_dict(params, 'data', {})
        marketId = self.safe_string(data, 'symbol')
        market = self.safe_market(marketId)
        symbol = market['symbol']
        channel = self.safe_string(params, 'channel')
        messageHash = channel + '.' + symbol
        ticker = self.parse_ticker(data, market)
        self.tickers[symbol] = ticker
        client.resolve(ticker, channel)
        client.resolve(ticker, messageHash)
        return message

    async def watch_funding_rate(self, symbol: str, params={}) -> FundingRate:
        """
        watch the current funding rate for a symbol

        https://docs.paradex.trade/ws/web-socket-channels/funding-data-market-symbol/funding-data-market-symbol

        :param str symbol: unified market symbol
        :param dict [params]: extra parameters specific to the exchange API endpoint
        :returns dict: a `funding rate structure <https://docs.ccxt.com/?id=funding-rate-structure>`
        """
        await self.load_markets()
        symbol = self.symbol(symbol)
        channel = 'funding_data'
        url = self.urls['api']['ws']
        request: dict = {
            'jsonrpc': '2.0',
            'method': 'subscribe',
            'params': {
                'channel': channel,
            },
        }
        messageHash = channel + '.' + symbol
        return await self.watch(url, messageHash, self.deep_extend(request, params), messageHash)

    async def watch_funding_rates(self, symbols: Strings = None, params={}) -> FundingRates:
        """
        watch the funding rate for multiple markets

        https://docs.paradex.trade/ws/web-socket-channels/markets-summary/markets-summary

        :param str[] [symbols]: a list of unified market symbols
        :param dict [params]: extra parameters specific to the exchange API endpoint
        :returns dict: a `funding rate structure <https://docs.ccxt.com/?id=funding-rate-structure>`
        """
        await self.load_markets()
        symbols = self.market_symbols(symbols)
        channel = 'funding_data'
        url = self.urls['api']['ws']
        request: dict = {
            'jsonrpc': '2.0',
            'method': 'subscribe',
            'params': {
                'channel': channel,
            },
        }
        messageHashes = []
        if symbols is not None:
            symbolsLength = len(symbols)
            if symbolsLength > 0:
                for i in range(0, len(symbols)):
                    messageHash = channel + '.' + symbols[i]
                    messageHashes.append(messageHash)
            else:
                messageHashes.append(channel)  # if an empty array is passed, subscribe to all funding rates
        else:
            messageHashes.append(channel)
        newFundingRates = await self.watch_multiple(url, messageHashes, self.deep_extend(request, params), messageHashes)
        if self.newUpdates:
            result: dict = {}
            result[newFundingRates['symbol']] = newFundingRates
            return result
        return self.filter_by_array(self.fundingRates, 'symbol', symbols)

    def handle_funding_rate(self, client: Client, message):
        #
        #     {
        #         "jsonrpc": "2.0",
        #         "method": "subscription",
        #         "params": {
        #             "channel": "funding_data",
        #             "data": {
        #                 "market": "TRUMP-USD-PERP",
        #                 "funding_index": "-0.551694014226244835",
        #                 "funding_premium": "-0.000509914923994872836",
        #                 "funding_rate": "-0.00014969570582",
        #                 "funding_rate_8h": "-0.00014969",
        #                 "funding_period_hours": 8,
        #                 "created_at": 1771506636154
        #             }
        #         }
        #     }
        #
        params = self.safe_dict(message, 'params', {})
        data = self.safe_dict(params, 'data', {})
        fundingRate = self.parse_funding_rate_ws(data)
        symbol = fundingRate['symbol']
        self.fundingRates[symbol] = fundingRate
        channel = self.safe_string(params, 'channel')
        messageHash = channel + '.' + symbol
        client.resolve(fundingRate, messageHash)

    def parse_funding_rate_ws(self, contract, market: Market = None) -> FundingRate:
        #
        #     {
        #         "market": "TRUMP-USD-PERP",
        #         "funding_index": "-0.551694014226244835",
        #         "funding_premium": "-0.000509914923994872836",
        #         "funding_rate": "-0.00014969570582",
        #         "funding_rate_8h": "-0.00014969",
        #         "funding_period_hours": 8,
        #         "created_at": 1771506636154
        #     }
        #
        marketId = self.safe_string(contract, 'market')
        symbol = self.safe_symbol(marketId, market)
        timestamp = self.safe_integer(contract, 'created_at')
        fundingPeriod = self.safe_string(contract, 'funding_period_hours')
        return {
            'info': contract,
            'symbol': symbol,
            'markPrice': None,
            'indexPrice': None,
            'interestRate': self.parse_number('0'),
            'estimatedSettlePrice': None,
            'timestamp': timestamp,
            'datetime': self.iso8601(timestamp),
            'fundingRate': self.safe_number(contract, 'funding_rate'),
            'fundingTimestamp': None,
            'fundingDatetime': None,
            'nextFundingRate': None,
            'nextFundingTimestamp': None,
            'nextFundingDatetime': None,
            'previousFundingRate': None,
            'previousFundingTimestamp': None,
            'previousFundingDatetime': None,
            'interval': fundingPeriod + 'h',
        }

    def handle_error_message(self, client: Client, message) -> Bool:
        #
        #     {
        #         "jsonrpc": "2.0",
        #         "id": 0,
        #         "error": {
        #             "code": -32600,
        #             "message": "invalid subscribe request",
        #             "data": "invalid channel"
        #         },
        #         "usIn": 1718179125962419,
        #         "usDiff": 76,
        #         "usOut": 1718179125962495
        #     }
        #
        error = self.safe_dict(message, 'error')
        if error is None:
            return True
        else:
            errorCode = self.safe_string(error, 'code')
            if errorCode is not None:
                feedback = self.id + ' ' + self.json(error)
                self.throw_exactly_matched_exception(self.exceptions['exact'], '-32600', feedback)
                messageString = self.safe_value(error, 'message')
                if messageString is not None:
                    self.throw_broadly_matched_exception(self.exceptions['broad'], messageString, feedback)
            return False

    def handle_message(self, client: Client, message):
        if not self.handle_error_message(client, message):
            return
        #
        # auth response
        #
        #     {
        #         "jsonrpc": "2.0",
        #         "id": 1,
        #         "result": {"node_id": "73cf456f7cb78d59"}
        #     }
        #
        # subscription message
        #
        #     {
        #         "jsonrpc": "2.0",
        #         "method": "subscription",
        #         "params": {
        #             "channel": "trades.ALL",
        #             "data": {
        #                 "id": "1718179273230201709233240002",
        #                 "market": "kBONK-USD-PERP",
        #                 "side": "BUY",
        #                 "size": "34028",
        #                 "price": "0.028776",
        #                 "created_at": 1718179273230,
        #                 "trade_type": "FILL"
        #             }
        #         }
        #     }
        #
        result = self.safe_value(message, 'result')
        if result is not None:
            self.handle_authentication_message(client, message)
            return
        data = self.safe_dict(message, 'params')
        if data is not None:
            channel = self.safe_string(data, 'channel')
            parts = channel.split('.')
            name = self.safe_string(parts, 0)
            methods: dict = {
                'trades': self.handle_trade,
                'order_book': self.handle_order_book,
                'markets_summary': self.handle_ticker,
                'orders': self.handle_order,
                'funding_data': self.handle_funding_rate,
            }
            method = self.safe_value(methods, name)
            if method is not None:
                method(client, message)
