<?php

namespace ccxt\pro;

// 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

use Exception; // a common import
use ccxt\ExchangeError;
use \React\Async;
use \React\Promise\PromiseInterface;

class lbank extends \ccxt\async\lbank {

    public function describe(): mixed {
        return $this->deep_extend(parent::describe(), array(
            'has' => array(
                'ws' => true,
                'fetchOHLCVWs' => true,
                'fetchOrderBookWs' => true,
                'fetchTickerWs' => true,
                'fetchTradesWs' => true,
                'watchBalance' => true,
                'watchTicker' => true,
                'watchTickers' => false,
                'watchTrades' => true,
                'watchTradesForSymbols' => false,
                'watchMyTrades' => false,
                'watchOrders' => true,
                'watchOrderBook' => true,
                'watchOHLCV' => true,
            ),
            'urls' => array(
                'api' => array(
                    'ws' => 'wss://www.lbkex.net/ws/V2/',
                ),
            ),
            'options' => array(
                'watchOHLCV' => array(
                    'timeframes' => array(
                        '1m' => '1min',
                        '5m' => '5min',
                        '15m' => '15min',
                        '30m' => '30min',
                        '1h' => '1hr',
                        '4h' => '4hr',
                        '1d' => 'day',
                        '1w' => 'week',
                        '1M' => 'month',
                        '1y' => 'year',
                    ),
                ),
            ),
            'streaming' => array(
            ),
            'exceptions' => array(
            ),
        ));
    }

    public function request_id() {
        $previousValue = $this->safe_integer($this->options, 'requestId', 0);
        $newValue = $this->sum($previousValue, 1);
        $this->options['requestId'] = $newValue;
        return $newValue;
    }

    public function fetch_ohlcv_ws(string $symbol, $timeframe = '1m', ?int $since = null, ?int $limit = null, $params = array ()): PromiseInterface {
        return Async\async(function () use ($symbol, $timeframe, $since, $limit, $params) {
            /**
             *
             * @see https://www.lbank.com/en-US/docs/index.html#$request-amp-subscription-instruction
             *
             * watches historical candlestick data containing the open, high, low, and close price, and the volume of a $market
             * @param {string} $symbol unified $symbol of the $market to fetch OHLCV data for
             * @param {string} $timeframe the length of time each candle represents
             * @param {int} [$since] timestamp in ms of the earliest candle to fetch
             * @param {int} [$limit] the maximum amount of candles to fetch
             * @param {array} [$params] extra parameters specific to the exchange API endpoint
             * @return {int[][]} A list of candles ordered, open, high, low, close, volume
             */
            Async\await($this->load_markets());
            $market = $this->market($symbol);
            $url = $this->urls['api']['ws'];
            $watchOHLCVOptions = $this->safe_value($this->options, 'watchOHLCV', array());
            $timeframes = $this->safe_value($watchOHLCVOptions, 'timeframes', array());
            $timeframeId = $this->safe_string($timeframes, $timeframe, $timeframe);
            $messageHash = 'fetchOHLCV:' . $market['symbol'] . ':' . $timeframeId;
            $message = array(
                'action' => 'request',
                'request' => 'kbar',
                'kbar' => $timeframeId,
                'pair' => $market['id'],
            );
            if ($since !== null) {
                $message['start'] = $this->parse_to_int((int) floor($since / 1000));
            }
            if ($limit !== null) {
                $message['size'] = $limit;
            }
            $request = $this->deep_extend($message, $params);
            $requestId = $this->request_id();
            return Async\await($this->watch($url, $messageHash, $request, $requestId, $request));
        }) ();
    }

    public function watch_ohlcv(string $symbol, $timeframe = '1m', ?int $since = null, ?int $limit = null, $params = array ()): PromiseInterface {
        return Async\async(function () use ($symbol, $timeframe, $since, $limit, $params) {
            /**
             *
             * @see https://www.lbank.com/en-US/docs/index.html#subscription-of-k-line-data
             *
             * watches historical candlestick data containing the open, high, low, and close price, and the volume of a $market
             * @param {string} $symbol unified $symbol of the $market to fetch OHLCV data for
             * @param {string} $timeframe the length of time each candle represents
             * @param {int} [$since] timestamp in ms of the earliest candle to fetch
             * @param {int} [$limit] the maximum amount of candles to fetch
             * @param {array} [$params] extra parameters specific to the exchange API endpoint
             * @return {int[][]} A list of candles ordered, open, high, low, close, volume
             */
            Async\await($this->load_markets());
            $market = $this->market($symbol);
            $watchOHLCVOptions = $this->safe_value($this->options, 'watchOHLCV', array());
            $timeframes = $this->safe_value($watchOHLCVOptions, 'timeframes', array());
            $timeframeId = $this->safe_string($timeframes, $timeframe, $timeframe);
            $messageHash = 'ohlcv:' . $market['symbol'] . ':' . $timeframeId;
            $url = $this->urls['api']['ws'];
            $subscribe = array(
                'action' => 'subscribe',
                'subscribe' => 'kbar',
                'kbar' => $timeframeId,
                'pair' => $market['id'],
            );
            $request = $this->deep_extend($subscribe, $params);
            $ohlcv = Async\await($this->watch($url, $messageHash, $request, $messageHash));
            if ($this->newUpdates) {
                $limit = $ohlcv->getLimit ($symbol, $limit);
            }
            return $this->filter_by_since_limit($ohlcv, $since, $limit, 0, true);
        }) ();
    }

    public function handle_ohlcv($client, $message) {
        //
        // request
        //    {
        //        "records":array(
        //           array(
        //              1705364400,
        //              42614,
        //              42624.57,
        //              42532.15,
        //              42537.43,
        //              13.2615,
        //              564568.931565,
        //              433
        //           )
        //        ),
        //        "columns":array(
        //           "timestamp",
        //           "open",
        //           "high",
        //           "low",
        //           "close",
        //           "volume",
        //           "turnover",
        //           "count"
        //        ),
        //        "SERVER":"V2",
        //        "count":1,
        //        "kbar":"5min",
        //        "type":"kbar",
        //        "pair":"btc_usdt",
        //        "TS":"2024-01-16T08:29:41.718"
        //    }
        // subscribe
        //      {
        //          SERVER => 'V2',
        //          kbar => array(
        //              a => 26415.891476,
        //              c => 19315.51,
        //              t => '2022-10-02T12:44:00.000',
        //              v => 1.3676,
        //              h => 19316.66,
        //              slot => '1min',
        //              l => 19315.51,
        //              n => 1,
        //              o => 19316.66
        //          ),
        //          type => 'kbar',
        //          pair => 'btc_usdt',
        //          TS => '2022-10-02T12:44:15.865'
        //      }
        //
        $marketId = $this->safe_string($message, 'pair');
        $symbol = $this->safe_symbol($marketId, null, '_');
        $watchOHLCVOptions = $this->safe_value($this->options, 'watchOHLCV', array());
        $timeframes = $this->safe_value($watchOHLCVOptions, 'timeframes', array());
        $records = $this->safe_value($message, 'records');
        if ($records !== null) {  // from request
            $rawOHLCV = $this->safe_value($records, 0, array());
            $parsed = array(
                $this->safe_integer($rawOHLCV, 0),
                $this->safe_number($rawOHLCV, 1),
                $this->safe_number($rawOHLCV, 2),
                $this->safe_number($rawOHLCV, 3),
                $this->safe_number($rawOHLCV, 4),
                $this->safe_number($rawOHLCV, 5),
            );
            $timeframeId = $this->safe_string($message, 'kbar');
            $timeframe = $this->find_timeframe($timeframeId, $timeframes);
            $this->ohlcvs[$symbol] = $this->safe_value($this->ohlcvs, $symbol, array());
            $stored = $this->safe_value($this->ohlcvs[$symbol], $timeframe);
            if ($stored === null) {
                $limit = $this->safe_integer($this->options, 'OHLCVLimit', 1000);
                $stored = new ArrayCacheByTimestamp ($limit);
                $this->ohlcvs[$symbol][$timeframe] = $stored;
            }
            $stored->append ($parsed);
            $messageHash = 'fetchOHLCV:' . $symbol . ':' . $timeframeId;
            $client->resolve ($stored, $messageHash);
        } else {  // from subscription
            $rawOHLCV = $this->safe_value($message, 'kbar', array());
            $timeframeId = $this->safe_string($rawOHLCV, 'slot');
            $datetime = $this->safe_string($rawOHLCV, 't');
            $parsed = array(
                $this->parse8601($datetime),
                $this->safe_number($rawOHLCV, 'o'),
                $this->safe_number($rawOHLCV, 'h'),
                $this->safe_number($rawOHLCV, 'l'),
                $this->safe_number($rawOHLCV, 'c'),
                $this->safe_number($rawOHLCV, 'v'),
            );
            $timeframe = $this->find_timeframe($timeframeId, $timeframes);
            $this->ohlcvs[$symbol] = $this->safe_value($this->ohlcvs, $symbol, array());
            $stored = $this->safe_value($this->ohlcvs[$symbol], $timeframe);
            if ($stored === null) {
                $limit = $this->safe_integer($this->options, 'OHLCVLimit', 1000);
                $stored = new ArrayCacheByTimestamp ($limit);
                $this->ohlcvs[$symbol][$timeframe] = $stored;
            }
            $stored->append ($parsed);
            $messageHash = 'ohlcv:' . $symbol . ':' . $timeframeId;
            $client->resolve ($stored, $messageHash);
        }
    }

    public function fetch_ticker_ws(string $symbol, $params = array ()): PromiseInterface {
        return Async\async(function () use ($symbol, $params) {
            /**
             *
             * @see https://www.lbank.com/en-US/docs/index.html#$request-amp-subscription-instruction
             *
             * fetches a price ticker, a statistical calculation with the information calculated over the past 24 hours for a specific $market
             * @param {string} $symbol unified $symbol of the $market to fetch the ticker for
             * @param {array} [$params] extra parameters specific to the cex api endpoint
             * @return {array} a ~@link https://docs.ccxt.com/#/?id=ticker-structure ticker structure~
             */
            Async\await($this->load_markets());
            $market = $this->market($symbol);
            $url = $this->urls['api']['ws'];
            $messageHash = 'fetchTicker:' . $market['symbol'];
            $message = array(
                'action' => 'request',
                'request' => 'tick',
                'pair' => $market['id'],
            );
            $request = $this->deep_extend($message, $params);
            $requestId = $this->request_id();
            return Async\await($this->watch($url, $messageHash, $request, $requestId, $request));
        }) ();
    }

    public function watch_ticker(string $symbol, $params = array ()): PromiseInterface {
        return Async\async(function () use ($symbol, $params) {
            /**
             *
             * @see https://www.lbank.com/en-US/docs/index.html#$market
             *
             * watches a price ticker, a statistical calculation with the information calculated over the past 24 hours for a specific $market
             * @param {string} $symbol unified $symbol of the $market to fetch the ticker for
             * @param {array} $params extra parameters specific to the lbank api endpoint
             * @return {array} a {@link https://docs.ccxt.com/en/latest/manual.html#ticker-structure ticker structure}
             */
            Async\await($this->load_markets());
            $market = $this->market($symbol);
            $url = $this->urls['api']['ws'];
            $messageHash = 'ticker:' . $market['symbol'];
            $message = array(
                'action' => 'subscribe',
                'subscribe' => 'tick',
                'pair' => $market['id'],
            );
            $request = $this->deep_extend($message, $params);
            return Async\await($this->watch($url, $messageHash, $request, $messageHash, $request));
        }) ();
    }

    public function handle_ticker($client, $message) {
        //
        //     {
        //         "tick":array(
        //             "to_cny":76643.5,
        //             "high":0.02719761,
        //             "vol":497529.7686,
        //             "low":0.02603071,
        //             "change":2.54,
        //             "usd":299.12,
        //             "to_usd":11083.66,
        //             "dir":"sell",
        //             "turnover":13224.0186,
        //             "latest":0.02698749,
        //             "cny":2068.41
        //         ),
        //         "type":"tick",
        //         "pair":"eth_btc",
        //         "SERVER":"V2",
        //         "TS":"2019-07-01T11:33:55.188"
        //     }
        //
        $marketId = $this->safe_string($message, 'pair');
        $symbol = $this->safe_symbol($marketId);
        $market = $this->safe_market($marketId);
        $parsedTicker = $this->parse_ws_ticker($message, $market);
        $this->tickers[$symbol] = $parsedTicker;
        $messageHash = 'ticker:' . $symbol;
        $client->resolve ($parsedTicker, $messageHash);
        $messageHash = 'fetchTicker:' . $symbol;
        $client->resolve ($parsedTicker, $messageHash);
    }

    public function parse_ws_ticker($ticker, $market = null) {
        //
        //     {
        //         "tick":array(
        //             "to_cny":76643.5,
        //             "high":0.02719761,
        //             "vol":497529.7686,
        //             "low":0.02603071,
        //             "change":2.54,
        //             "usd":299.12,
        //             "to_usd":11083.66,
        //             "dir":"sell",
        //             "turnover":13224.0186,
        //             "latest":0.02698749,
        //             "cny":2068.41
        //         ),
        //         "type":"tick",
        //         "pair":"eth_btc",
        //         "SERVER":"V2",
        //         "TS":"2019-07-01T11:33:55.188"
        //     }
        //
        $marketId = $this->safe_string($ticker, 'pair');
        $symbol = $this->safe_symbol($marketId, $market);
        $datetime = $this->safe_string($ticker, 'TS');
        $tickerData = $this->safe_value($ticker, 'tick');
        return $this->safe_ticker(array(
            'symbol' => $symbol,
            'timestamp' => $this->parse8601($datetime),
            'datetime' => $datetime,
            'high' => $this->safe_string($tickerData, 'high'),
            'low' => $this->safe_string($tickerData, 'low'),
            'bid' => null,
            'bidVolume' => null,
            'ask' => null,
            'askVolume' => null,
            'vwap' => null,
            'open' => null,
            'close' => null,
            'last' => $this->safe_string($tickerData, 'latest'),
            'previousClose' => null,
            'change' => null,
            'percentage' => $this->safe_string($tickerData, 'change'),
            'average' => null,
            'baseVolume' => $this->safe_string($tickerData, 'vol'),
            'quoteVolume' => $this->safe_string($tickerData, 'turnover'),
            'info' => $ticker,
        ), $market);
    }

    public function fetch_trades_ws(string $symbol, ?int $since = null, ?int $limit = null, $params = array ()): PromiseInterface {
        return Async\async(function () use ($symbol, $since, $limit, $params) {
            /**
             * get the list of most recent trades for a particular $symbol
             *
             * @see https://www.lbank.com/en-US/docs/index.html#$request-amp-subscription-instruction
             *
             * @param {string} $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 {array} [$params] extra parameters specific to the exchange API endpoint
             * @return {Trade[]} a list of ~@link https://docs.ccxt.com/#/?id=public-trades trade structures~
             */
            Async\await($this->load_markets());
            $market = $this->market($symbol);
            $url = $this->urls['api']['ws'];
            $messageHash = 'fetchTrades:' . $market['symbol'];
            if ($limit === null) {
                $limit = 10;
            }
            $message = array(
                'action' => 'request',
                'request' => 'trade',
                'pair' => $market['id'],
                'size' => $limit,
            );
            $request = $this->deep_extend($message, $params);
            $requestId = $this->request_id();
            return Async\await($this->watch($url, $messageHash, $request, $requestId, $request));
        }) ();
    }

    public function watch_trades(string $symbol, ?int $since = null, ?int $limit = null, $params = array ()): PromiseInterface {
        return Async\async(function () use ($symbol, $since, $limit, $params) {
            /**
             *
             * @see https://www.lbank.com/en-US/docs/index.html#trade-record
             *
             * get the list of most recent $trades for a particular $symbol
             * @param {string} $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 {array} [$params] extra parameters specific to the exchange API endpoint
             * @return {array[]} a list of ~@link https://docs.ccxt.com/#/?id=public-$trades trade structures~
             */
            Async\await($this->load_markets());
            $market = $this->market($symbol);
            $url = $this->urls['api']['ws'];
            $messageHash = 'trades:' . $market['symbol'];
            $message = array(
                'action' => 'subscribe',
                'subscribe' => 'trade',
                'pair' => $market['id'],
            );
            $request = $this->deep_extend($message, $params);
            $trades = Async\await($this->watch($url, $messageHash, $request, $messageHash, $request));
            return $this->filter_by_since_limit($trades, $since, $limit, 'timestamp', true);
        }) ();
    }

    public function handle_trades($client, $message) {
        //
        // request
        //     {
        //         columns => array( 'timestamp', 'price', 'volume', 'direction' ),
        //         SERVER => 'V2',
        //         count => 100,
        //         trades => array(),
        //         type => 'trade',
        //         pair => 'btc_usdt',
        //         TS => '2024-01-16T08:48:24.470'
        //     }
        // subscribe
        //     {
        //         "trade":array(
        //             "volume":6.3607,
        //             "amount":77148.9303,
        //             "price":12129,
        //             "direction":"sell", // buy, sell, buy_market, sell_market, buy_maker, sell_maker, buy_ioc, sell_ioc, buy_fok, sell_fok
        //             "TS":"2019-06-28T19:55:49.460"
        //         ),
        //         "type":"trade",
        //         "pair":"btc_usdt",
        //         "SERVER":"V2",
        //         "TS":"2019-06-28T19:55:49.466"
        //     }
        //
        $marketId = $this->safe_string($message, 'pair');
        $symbol = $this->safe_symbol($marketId);
        $market = $this->safe_market($marketId);
        $stored = $this->safe_value($this->trades, $symbol);
        if ($stored === null) {
            $limit = $this->safe_integer($this->options, 'tradesLimit', 1000);
            $stored = new ArrayCache ($limit);
            $this->trades[$symbol] = $stored;
        }
        $rawTrade = $this->safe_value($message, 'trade');
        $rawTrades = $this->safe_value($message, 'trades', array( $rawTrade ));
        for ($i = 0; $i < count($rawTrades); $i++) {
            $trade = $this->parse_ws_trade($rawTrades[$i], $market);
            $trade['symbol'] = $symbol;
            $stored->append ($trade);
        }
        $this->trades[$symbol] = $stored;
        $messageHash = 'trades:' . $symbol;
        $client->resolve ($this->trades[$symbol], $messageHash);
        $messageHash = 'fetchTrades:' . $symbol;
        $client->resolve ($this->trades[$symbol], $messageHash);
    }

    public function parse_ws_trade($trade, $market = null) {
        //
        // request
        //    array( 'timestamp', 'price', 'volume', 'direction' )
        // subscribe
        //    {
        //        "volume":6.3607,
        //        "amount":77148.9303,
        //        "price":12129,
        //        "direction":"sell", // buy, sell, buy_market, sell_market, buy_maker, sell_maker, buy_ioc, sell_ioc, buy_fok, sell_fok
        //        "TS":"2019-06-28T19:55:49.460"
        //    }
        //
        $timestamp = $this->safe_integer($trade, 0);
        $datetime = ($timestamp !== null) ? ($this->iso8601($timestamp)) : ($this->safe_string($trade, 'TS'));
        if ($timestamp === null) {
            $timestamp = $this->parse8601($datetime);
        }
        $rawSide = $this->safe_string_2($trade, 'direction', 3);
        $parts = explode('_', $rawSide);
        $firstPart = $this->safe_string($parts, 0);
        $secondPart = $this->safe_string($parts, 1);
        $side = $firstPart;
        // reverse if it was 'maker'
        if ($secondPart !== null && $secondPart === 'maker') {
            $side = ($side === 'buy') ? 'sell' : 'buy';
        }
        return $this->safe_trade(array(
            'timestamp' => $timestamp,
            'datetime' => $datetime,
            'symbol' => null,
            'id' => null,
            'order' => null,
            'type' => null,
            'takerOrMaker' => null,
            'side' => $side,
            'price' => $this->safe_string_2($trade, 'price', 1),
            'amount' => $this->safe_string_2($trade, 'volume', 2),
            'cost' => $this->safe_string($trade, 'amount'),
            'fee' => null,
            'info' => $trade,
        ), $market);
    }

    public function watch_orders(?string $symbol = null, ?int $since = null, ?int $limit = null, $params = array ()): PromiseInterface {
        return Async\async(function () use ($symbol, $since, $limit, $params) {
            /**
             *
             * @see https://www.lbank.com/en-US/docs/index.html#update-subscribed-$orders
             *
             * get the list of trades associated with the user
             * @param {string} [$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 {array} $params extra parameters specific to the lbank api endpoint
             * @return {array[]} a list of ~@link https://docs.ccxt.com/#/?id=public-trades trade structures~
             */
            Async\await($this->load_markets());
            $key = Async\await($this->authenticate($params));
            $url = $this->urls['api']['ws'];
            $messageHash = null;
            $pair = 'all';
            if ($symbol === null) {
                $messageHash = 'orders:all';
            } else {
                $market = $this->market($symbol);
                $symbol = $this->symbol($symbol);
                $messageHash = 'orders:' . $market['symbol'];
                $pair = $market['id'];
            }
            $message = array(
                'action' => 'subscribe',
                'subscribe' => 'orderUpdate',
                'subscribeKey' => $key,
                'pair' => $pair,
            );
            $request = $this->deep_extend($message, $params);
            $orders = Async\await($this->watch($url, $messageHash, $request, $messageHash, $request));
            return $this->filter_by_symbol_since_limit($orders, $symbol, $since, $limit, true);
        }) ();
    }

    public function handle_orders($client, $message) {
        //
        //     {
        //         "orderUpdate":array(
        //             "amount":"0.003",
        //             "orderStatus":2,
        //             "price":"0.02455211",
        //             "role":"maker",
        //             "updateTime":1561704577786,
        //             "uuid":"d0db191d-xxxxx-4418-xxxxx-fbb1xxxx2ea9",
        //             "txUuid":"da88f354d5xxxxxxa12128aa5bdcb3",
        //             "volumePrice":"0.00007365633"
        //         ),
        //         "pair":"eth_btc",
        //         "type":"orderUpdate",
        //         "SERVER":"V2",
        //         "TS":"2019-06-28T14:49:37.816"
        //     }
        //
        $marketId = $this->safe_string($message, 'pair');
        $symbol = $this->safe_symbol($marketId, null, '_');
        $myOrders = null;
        if ($this->orders === null) {
            $limit = $this->safe_integer($this->options, 'ordersLimit', 1000);
            $myOrders = new ArrayCacheBySymbolById ($limit);
        } else {
            $myOrders = $this->orders;
        }
        $order = $this->parse_ws_order($message);
        $myOrders->append ($order);
        $this->orders = $myOrders;
        $client->resolve ($myOrders, 'orders');
        $messageHash = 'orders:' . $symbol;
        $client->resolve ($myOrders, $messageHash);
    }

    public function parse_ws_order($order, $market = null) {
        //
        //     {
        //         "orderUpdate":array(
        //             "amount":"0.003",
        //             "orderStatus":2,
        //             "price":"0.02455211",
        //             "role":"maker",
        //             "updateTime":1561704577786,
        //             "uuid":"d0db191d-xxxxx-4418-xxxxx-fbb1xxxx2ea9",
        //             "txUuid":"da88f354d5xxxxxxa12128aa5bdcb3",
        //             "volumePrice":"0.00007365633"
        //         ),
        //         "pair":"eth_btc",
        //         "type":"orderUpdate",
        //         "SERVER":"V2",
        //         "TS":"2019-06-28T14:49:37.816"
        //     }
        //     {
        //         "SERVER" => "V2",
        //         "orderUpdate" => array(
        //            "accAmt" => "0",
        //            "amount" => "0",
        //            "avgPrice" => "0",
        //            "customerID" => "",
        //            "orderAmt" => "5",
        //            "orderPrice" => "0.009834",
        //            "orderStatus" => 0,
        //            "price" => "0.009834",
        //            "remainAmt" => "5",
        //            "role" => "taker",
        //            "symbol" => "lbk_usdt",
        //            "type" => "buy_market",
        //            "updateTime" => 1705676718532,
        //            "uuid" => "9b94ab2d-a510-4abe-a784-44a9d9c38ec7",
        //            "volumePrice" => "0"
        //         ),
        //         "type" => "orderUpdate",
        //         "pair" => "lbk_usdt",
        //         "TS" => "2024-01-19T23:05:18.548"
        //     }
        //
        $orderUpdate = $this->safe_value($order, 'orderUpdate', array());
        $rawType = $this->safe_string($orderUpdate, 'type', '');
        $typeParts = explode('_', $rawType);
        $side = $this->safe_string($typeParts, 0);
        $exchangeType = $this->safe_string($typeParts, 1);
        $type = null;
        if ($rawType !== 'buy' && $rawType !== 'sell') {
            $type = ($exchangeType === 'market') ? 'market' : 'limit';
        }
        $marketId = $this->safe_string($order, 'pair');
        $symbol = $this->safe_symbol($marketId, $market, '_');
        $timestamp = $this->safe_integer($orderUpdate, 'updateTime');
        $status = $this->safe_string($orderUpdate, 'orderStatus');
        $orderAmount = $this->safe_string($orderUpdate, 'orderAmt');
        $cost = null;
        if (($type === 'market') && ($side === 'buy')) {
            $cost = $orderAmount;
        }
        return $this->safe_order(array(
            'info' => $order,
            'id' => $this->safe_string($orderUpdate, 'uuid'),
            'clientOrderId' => $this->safe_string($orderUpdate, 'customerID'),
            'timestamp' => $timestamp,
            'datetime' => $this->iso8601($timestamp),
            'lastTradeTimestamp' => null,
            'lastUpdateTimestamp' => $this->safe_integer($orderUpdate, 'updateTime'),
            'symbol' => $symbol,
            'type' => $type,
            'side' => $side,
            'price' => $this->safe_string_2($orderUpdate, 'price', 'orderPrice'),
            'stopPrice' => null,
            'average' => $this->safe_string($orderUpdate, 'avgPrice'),
            'amount' => $this->safe_string_2($orderUpdate, 'amount', 'orderAmt'),
            'remaining' => $this->safe_string($orderUpdate, 'remainAmt'),
            'filled' => $this->safe_string($orderUpdate, 'accAmt'),
            'status' => $this->parse_ws_order_status($status),
            'fee' => null,
            'cost' => $cost,
            'trades' => null,
        ), $market);
    }

    public function parse_ws_order_status($status) {
        $statuses = array(
            '-1' => 'canceled',  // Withdrawn
            '0' => 'open',   // Unsettled
            '1' => 'open',   // Partial sale
            '2' => 'closed', // Completed
            '4' => 'closed',  // Withrawing
        );
        return $this->safe_string($statuses, $status, $status);
    }

    public function watch_balance($params = array ()): PromiseInterface {
        return Async\async(function () use ($params) {
            /**
             * watch balance and get the amount of funds available for trading or funds locked in orders
             *
             * @see https://www.lbank.com/docs/index.html#update-subscribed-asset
             *
             * @param {array} [$params] extra parameters specific to the exchange API endpoint
             * @return {array} a ~@link https://docs.ccxt.com/#/?id=balance-structure balance structure~
             */
            Async\await($this->load_markets());
            $key = Async\await($this->authenticate($params));
            $url = $this->urls['api']['ws'];
            $messageHash = 'balance';
            $message = array(
                'action' => 'subscribe',
                'subscribe' => 'assetUpdate',
                'subscribeKey' => $key,
            );
            $request = $this->deep_extend($message, $params);
            return Async\await($this->watch($url, $messageHash, $request, $messageHash, $request));
        }) ();
    }

    public function handle_balance(Client $client, $message) {
        //
        //     {
        //         "data" => array(
        //             "asset" => "114548.31881315",
        //             "assetCode" => "usdt",
        //             "free" => "97430.6739041",
        //             "freeze" => "17117.64490905",
        //             "time" => 1627300043270,
        //             "type" => "ORDER_CREATE"
        //         ),
        //         "SERVER" => "V2",
        //         "type" => "assetUpdate",
        //         "TS" => "2021-07-26T19:48:03.548"
        //     }
        //
        $data = $this->safe_dict($message, 'data', array());
        $timestamp = $this->parse8601($this->safe_string($message, 'TS'));
        $datetime = $this->iso8601($timestamp);
        $this->balance['info'] = $data;
        $this->balance['timestamp'] = $timestamp;
        $this->balance['datetime'] = $datetime;
        $currencyId = $this->safe_string($data, 'assetCode');
        $code = $this->safe_currency_code($currencyId);
        $account = $this->account();
        $account['free'] = $this->safe_string($data, 'free');
        $account['used'] = $this->safe_string($data, 'freeze');
        $account['total'] = $this->safe_string($data, 'asset');
        $this->balance[$code] = $account;
        $this->balance = $this->safe_balance($this->balance);
        $client->resolve ($this->balance, 'balance');
    }

    public function fetch_order_book_ws(string $symbol, ?int $limit = null, $params = array ()): PromiseInterface {
        return Async\async(function () use ($symbol, $limit, $params) {
            /**
             *
             * @see https://www.lbank.com/en-US/docs/index.html#$request-amp-subscription-instruction
             *
             * watches information on open orders with bid (buy) and ask (sell) prices, volumes and other data
             * @param {string} $symbol unified $symbol of the $market to fetch the order book for
             * @param {int|null} $limit the maximum amount of order book entries to return
             * @param {array} $params extra parameters specific to the lbank api endpoint
             * @return {array} A dictionary of {@link https://docs.ccxt.com/en/latest/manual.html#order-book-structure order book structures} indexed by $market symbols
             */
            Async\await($this->load_markets());
            $market = $this->market($symbol);
            $url = $this->urls['api']['ws'];
            $messageHash = 'fetchOrderbook:' . $market['symbol'];
            if ($limit === null) {
                $limit = 100;
            }
            $subscribe = array(
                'action' => 'request',
                'request' => 'depth',
                'depth' => $limit,
                'pair' => $market['id'],
            );
            $request = $this->deep_extend($subscribe, $params);
            $orderbook = Async\await($this->watch($url, $messageHash, $request, $messageHash));
            return $orderbook->limit ();
        }) ();
    }

    public function watch_order_book(string $symbol, ?int $limit = null, $params = array ()): PromiseInterface {
        return Async\async(function () use ($symbol, $limit, $params) {
            /**
             *
             * @see https://www.lbank.com/en-US/docs/index.html#$market-depth
             *
             * watches information on open orders with bid (buy) and ask (sell) prices, volumes and other data
             * @param {string} $symbol unified $symbol of the $market to fetch the order book for
             * @param {int|null} $limit the maximum amount of order book entries to return
             * @param {array} $params extra parameters specific to the lbank api endpoint
             * @return {array} A dictionary of {@link https://docs.ccxt.com/en/latest/manual.html#order-book-structure order book structures} indexed by $market symbols
             */
            Async\await($this->load_markets());
            $market = $this->market($symbol);
            $url = $this->urls['api']['ws'];
            $messageHash = 'orderbook:' . $market['symbol'];
            $params = $this->omit($params, 'aggregation');
            if ($limit === null) {
                $limit = 100;
            }
            $subscribe = array(
                'action' => 'subscribe',
                'subscribe' => 'depth',
                'depth' => $limit,
                'pair' => $market['id'],
            );
            $request = $this->deep_extend($subscribe, $params);
            $orderbook = Async\await($this->watch($url, $messageHash, $request, $messageHash));
            return $orderbook->limit ();
        }) ();
    }

    public function handle_order_book($client, $message) {
        //
        // request
        //    {
        //        "SERVER":"V2",
        //        "asks":array(
        //           array(
        //              42585.84,
        //              1.4422
        //           ),
        //           ...
        //        ),
        //        "bids":array(
        //           array(
        //              42585.83,
        //              1.8054
        //           ),
        //          ,,,
        //        ),
        //        "count":100,
        //        "type":"depth",
        //        "pair":"btc_usdt",
        //        "TS":"2024-01-16T08:26:00.413"
        //    }
        // subscribe
        //     {
        //         "depth" => array(
        //             "asks" => array(
        //                 array(
        //                     0.0252,
        //                     0.5833
        //                 ),
        //                 array(
        //                     0.025215,
        //                     4.377
        //                 ),
        //                 ...
        //             ),
        //             "bids" => array(
        //                 array(
        //                     0.025135,
        //                     3.962
        //                 ),
        //                 array(
        //                     0.025134,
        //                     3.46
        //                 ),
        //                 ...
        //             )
        //         ),
        //         "count" => 100,
        //         "type" => "depth",
        //         "pair" => "eth_btc",
        //         "SERVER" => "V2",
        //         "TS" => "2019-06-28T17:49:22.722"
        //     }
        //
        $marketId = $this->safe_string($message, 'pair');
        $symbol = $this->safe_symbol($marketId);
        $orderBook = $this->safe_value($message, 'depth', $message);
        $datetime = $this->safe_string($message, 'TS');
        $timestamp = $this->parse8601($datetime);
        // $orderbook = $this->safe_value($this->orderbooks, $symbol);
        if (!(is_array($this->orderbooks) && array_key_exists($symbol, $this->orderbooks))) {
            $this->orderbooks[$symbol] = $this->order_book(array());
        }
        $orderbook = $this->orderbooks[$symbol];
        $snapshot = $this->parse_order_book($orderBook, $symbol, $timestamp, 'bids', 'asks');
        $orderbook->reset ($snapshot);
        $messageHash = 'orderbook:' . $symbol;
        $client->resolve ($orderbook, $messageHash);
        $messageHash = 'fetchOrderbook:' . $symbol;
        $client->resolve ($orderbook, $messageHash);
    }

    public function handle_error_message($client, $message) {
        //
        //    {
        //        SERVER => 'V2',
        //        $message => "Missing parameter ['kbar']",
        //        status => 'error',
        //        TS => '2024-01-16T08:09:43.314'
        //    }
        //
        $errMsg = $this->safe_string($message, 'message', '');
        $error = new ExchangeError ($this->id . ' ' . $errMsg);
        $client->reject ($error);
    }

    public function handle_ping(Client $client, $message) {
        return Async\async(function () use ($client, $message) {
            //
            //  array( ping => 'a13a939c-5f25-4e06-9981-93cb3b890707', action => 'ping' )
            //
            $pingId = $this->safe_string($message, 'ping');
            try {
                Async\await($client->send (array(
                    'action' => 'pong',
                    'pong' => $pingId,
                )));
            } catch (Exception $e) {
                $this->on_error($client, $e);
            }
        }) ();
    }

    public function handle_message($client, $message) {
        $status = $this->safe_string($message, 'status');
        if ($status === 'error') {
            $this->handle_error_message($client, $message);
            return;
        }
        $type = $this->safe_string_2($message, 'type', 'action');
        if ($type === 'ping') {
            $this->spawn(array($this, 'handle_ping'), $client, $message);
            return;
        }
        $handlers = array(
            'kbar' => array($this, 'handle_ohlcv'),
            'depth' => array($this, 'handle_order_book'),
            'trade' => array($this, 'handle_trades'),
            'tick' => array($this, 'handle_ticker'),
            'orderUpdate' => array($this, 'handle_orders'),
            'assetUpdate' => array($this, 'handle_balance'),
        );
        $handler = $this->safe_value($handlers, $type);
        if ($handler !== null) {
            $handler($client, $message);
        }
    }

    public function authenticate($params = array ()) {
        return Async\async(function () use ($params) {
            // when we implement more private streams, we need to refactor the authentication
            // to be concurent-safe and respect the same authentication token
            $url = $this->urls['api']['ws'];
            $client = $this->client($url);
            $now = $this->milliseconds();
            $messageHash = 'authenticated';
            $authenticated = $this->safe_value($client->subscriptions, $messageHash);
            if ($authenticated === null) {
                $this->check_required_credentials();
                $response = Async\await($this->spotPrivatePostSubscribeGetKey ($params));
                //
                // array("result":true,"data":"4e9958623e6006bd7b13ff9f36c03b36132f0f8da37f70b14ff2c4eab1fe0c97","error_code":0,"ts":1705602277198)
                //
                $result = $this->safe_value($response, 'result');
                if ($result !== true) {
                    throw new ExchangeError($this->id . ' failed to get subscribe key');
                }
                $client->subscriptions['authenticated'] = array(
                    'key' => $this->safe_string($response, 'data'),
                    'expires' => $this->sum($now, 3300000), // SubscribeKey lasts one hour, refresh it every 55 minutes
                );
            } else {
                $expires = $this->safe_integer($authenticated, 'expires', 0);
                if ($expires < $now) {
                    $request = array(
                        'subscribeKey' => $authenticated['key'],
                    );
                    $response = Async\await($this->spotPrivatePostSubscribeRefreshKey ($this->extend($request, $params)));
                    //
                    //    array("result" => "true")
                    //
                    $result = $this->safe_string($response, 'result');
                    if ($result !== 'true') {
                        throw new ExchangeError($this->id . ' failed to refresh the SubscribeKey');
                    }
                    $client['subscriptions']['authenticated']['expires'] = $this->sum($now, 3300000); // SubscribeKey lasts one hour, refresh it 5 minutes before it $expires
                }
            }
            return $client->subscriptions['authenticated']['key'];
        }) ();
    }
}
