# -*- 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
from ccxt.base.types import Any, Bool, Int, Market, OrderBook, Ticker, Trade
from ccxt.async_support.base.ws.client import Client
from typing import List
from ccxt.base.errors import AuthenticationError


class coinone(ccxt.async_support.coinone):

    def describe(self) -> Any:
        return self.deep_extend(super(coinone, self).describe(), {
            'has': {
                'ws': True,
                'watchOrderBook': True,
                'watchOrders': False,
                'watchTrades': True,
                'watchTradesForSymbols': False,
                'watchOHLCV': False,
                'watchTicker': True,
                'watchTickers': False,
            },
            'urls': {
                'api': {
                    'ws': 'wss://stream.coinone.co.kr',
                },
            },
            'options': {
                'expiresIn': '',
                'userId': '',
                'wsSessionToken': '',
                'watchOrderBook': {
                    'snapshotDelay': 6,
                    'snapshotMaxRetries': 3,
                },
                'tradesLimit': 1000,
                'OHLCVLimit': 1000,
            },
            'exceptions': {
                'exact': {
                    '4009': AuthenticationError,
                },
            },
            'streaming': {
                'ping': self.ping,
                'keepAlive': 20000,
            },
        })

    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.coinone.co.kr/reference/public-websocket-orderbook

        :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 = 'orderbook:' + market['symbol']
        url = self.urls['api']['ws']
        request: dict = {
            'request_type': 'SUBSCRIBE',
            'channel': 'ORDERBOOK',
            'topic': {
                'quote_currency': market['quote'],
                'target_currency': market['base'],
            },
        }
        message = self.extend(request, params)
        orderbook = await self.watch(url, messageHash, message, messageHash)
        return orderbook.limit()

    def handle_order_book(self, client, message):
        #
        #     {
        #         "response_type": "DATA",
        #         "channel": "ORDERBOOK",
        #         "data": {
        #             "quote_currency": "KRW",
        #             "target_currency": "BTC",
        #             "timestamp": 1705288918649,
        #             "id": "1705288918649001",
        #             "asks": [
        #                 {
        #                     "price": "58412000",
        #                     "qty": "0.59919807"
        #                 }
        #             ],
        #             "bids": [
        #                 {
        #                     "price": "58292000",
        #                     "qty": "0.1045"
        #                 }
        #             ]
        #         }
        #     }
        #
        data = self.safe_value(message, 'data', {})
        baseId = self.safe_string_upper(data, 'target_currency')
        quoteId = self.safe_string_upper(data, 'quote_currency')
        base = self.safe_currency_code(baseId)
        quote = self.safe_currency_code(quoteId)
        symbol = self.symbol(base + '/' + quote)
        timestamp = self.safe_integer(data, 'timestamp')
        orderbook = self.safe_value(self.orderbooks, symbol)
        if orderbook is None:
            orderbook = self.order_book()
        else:
            orderbook.reset()
        orderbook['symbol'] = symbol
        asks = self.safe_value(data, 'asks', [])
        bids = self.safe_value(data, 'bids', [])
        self.handle_deltas(orderbook['asks'], asks)
        self.handle_deltas(orderbook['bids'], bids)
        orderbook['timestamp'] = timestamp
        orderbook['datetime'] = self.iso8601(timestamp)
        messageHash = 'orderbook:' + symbol
        self.orderbooks[symbol] = orderbook
        client.resolve(orderbook, messageHash)

    def handle_delta(self, bookside, delta):
        bidAsk = self.parse_bid_ask(delta, 'price', 'qty')
        bookside.storeArray(bidAsk)

    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.coinone.co.kr/reference/public-websocket-ticker

        :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()
        market = self.market(symbol)
        messageHash = 'ticker:' + market['symbol']
        url = self.urls['api']['ws']
        request: dict = {
            'request_type': 'SUBSCRIBE',
            'channel': 'TICKER',
            'topic': {
                'quote_currency': market['quote'],
                'target_currency': market['base'],
            },
        }
        message = self.extend(request, params)
        return await self.watch(url, messageHash, message, messageHash)

    def handle_ticker(self, client: Client, message):
        #
        #     {
        #         "response_type": "DATA",
        #         "channel": "TICKER",
        #         "data": {
        #             "quote_currency": "KRW",
        #             "target_currency": "BTC",
        #             "timestamp": 1705301117198,
        #             "quote_volume": "19521465345.504",
        #             "target_volume": "334.81445168",
        #             "high": "58710000",
        #             "low": "57276000",
        #             "first": "57293000",
        #             "last": "58532000",
        #             "volume_power": "100",
        #             "ask_best_price": "58537000",
        #             "ask_best_qty": "0.1961",
        #             "bid_best_price": "58532000",
        #             "bid_best_qty": "0.00009258",
        #             "id": "1705301117198001",
        #             "yesterday_high": "59140000",
        #             "yesterday_low": "57273000",
        #             "yesterday_first": "58897000",
        #             "yesterday_last": "57301000",
        #             "yesterday_quote_volume": "12967227517.4262",
        #             "yesterday_target_volume": "220.09232233"
        #         }
        #     }
        #
        data = self.safe_value(message, 'data', {})
        ticker = self.parse_ws_ticker(data)
        symbol = ticker['symbol']
        self.tickers[symbol] = ticker
        messageHash = 'ticker:' + symbol
        client.resolve(self.tickers[symbol], messageHash)

    def parse_ws_ticker(self, ticker, market: Market = None) -> Ticker:
        #
        #     {
        #         "quote_currency": "KRW",
        #         "target_currency": "BTC",
        #         "timestamp": 1705301117198,
        #         "quote_volume": "19521465345.504",
        #         "target_volume": "334.81445168",
        #         "high": "58710000",
        #         "low": "57276000",
        #         "first": "57293000",
        #         "last": "58532000",
        #         "volume_power": "100",
        #         "ask_best_price": "58537000",
        #         "ask_best_qty": "0.1961",
        #         "bid_best_price": "58532000",
        #         "bid_best_qty": "0.00009258",
        #         "id": "1705301117198001",
        #         "yesterday_high": "59140000",
        #         "yesterday_low": "57273000",
        #         "yesterday_first": "58897000",
        #         "yesterday_last": "57301000",
        #         "yesterday_quote_volume": "12967227517.4262",
        #         "yesterday_target_volume": "220.09232233"
        #     }
        #
        timestamp = self.safe_integer(ticker, 'timestamp')
        last = self.safe_string(ticker, 'last')
        baseId = self.safe_string(ticker, 'target_currency')
        quoteId = self.safe_string(ticker, 'quote_currency')
        base = self.safe_currency_code(baseId)
        quote = self.safe_currency_code(quoteId)
        symbol = self.symbol(base + '/' + quote)
        return self.safe_ticker({
            'symbol': symbol,
            'timestamp': timestamp,
            'datetime': self.iso8601(timestamp),
            'high': self.safe_string(ticker, 'high'),
            'low': self.safe_string(ticker, 'low'),
            'bid': self.safe_number(ticker, 'bid_best_price'),
            'bidVolume': self.safe_number(ticker, 'bid_best_qty'),
            'ask': self.safe_number(ticker, 'ask_best_price'),
            'askVolume': self.safe_number(ticker, 'ask_best_qty'),
            'vwap': None,
            'open': self.safe_string(ticker, 'first'),
            'close': last,
            'last': last,
            'previousClose': None,
            'change': None,
            'percentage': None,
            'average': None,
            'baseVolume': self.safe_string(ticker, 'target_volume'),
            'quoteVolume': self.safe_string(ticker, 'quote_volume'),
            'info': ticker,
        }, market)

    async def watch_trades(self, symbol: str, since: Int = None, limit: Int = None, params={}) -> List[Trade]:
        """
        watches information on multiple trades made in a market

        https://docs.coinone.co.kr/reference/public-websocket-trade

        :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>`
        """
        await self.load_markets()
        market = self.market(symbol)
        messageHash = 'trade:' + market['symbol']
        url = self.urls['api']['ws']
        request: dict = {
            'request_type': 'SUBSCRIBE',
            'channel': 'TRADE',
            'topic': {
                'quote_currency': market['quote'],
                'target_currency': market['base'],
            },
        }
        message = self.extend(request, params)
        trades = await self.watch(url, messageHash, message, messageHash)
        if self.newUpdates:
            limit = trades.getLimit(market['symbol'], limit)
        return self.filter_by_since_limit(trades, since, limit, 'timestamp', True)

    def handle_trades(self, client: Client, message):
        #
        #     {
        #         "response_type": "DATA",
        #         "channel": "TRADE",
        #         "data": {
        #             "quote_currency": "KRW",
        #             "target_currency": "BTC",
        #             "id": "1705303667916001",
        #             "timestamp": 1705303667916,
        #             "price": "58490000",
        #             "qty": "0.0008",
        #             "is_seller_maker": False
        #         }
        #     }
        #
        data = self.safe_value(message, 'data', {})
        trade = self.parse_ws_trade(data)
        symbol = trade['symbol']
        stored = self.safe_value(self.trades, symbol)
        if stored is None:
            limit = self.safe_integer(self.options, 'tradesLimit', 1000)
            stored = ArrayCache(limit)
            self.trades[symbol] = stored
        stored.append(trade)
        messageHash = 'trade:' + symbol
        client.resolve(stored, messageHash)

    def parse_ws_trade(self, trade: dict, market: Market = None) -> Trade:
        #
        #     {
        #         "quote_currency": "KRW",
        #         "target_currency": "BTC",
        #         "id": "1705303667916001",
        #         "timestamp": 1705303667916,
        #         "price": "58490000",
        #         "qty": "0.0008",
        #         "is_seller_maker": False
        #     }
        #
        baseId = self.safe_string_upper(trade, 'target_currency')
        quoteId = self.safe_string_upper(trade, 'quote_currency')
        base = self.safe_currency_code(baseId)
        quote = self.safe_currency_code(quoteId)
        symbol = base + '/' + quote
        timestamp = self.safe_integer(trade, 'timestamp')
        market = self.safe_market(symbol, market)
        isSellerMaker = self.safe_value(trade, 'is_seller_maker')
        side = None
        if isSellerMaker is not None:
            side = 'sell' if isSellerMaker else 'buy'
        priceString = self.safe_string(trade, 'price')
        amountString = self.safe_string(trade, 'qty')
        return self.safe_trade({
            'id': self.safe_string(trade, 'id'),
            'info': trade,
            'timestamp': timestamp,
            'datetime': self.iso8601(timestamp),
            'order': None,
            'symbol': market['symbol'],
            'type': None,
            'side': side,
            'takerOrMaker': None,
            'price': priceString,
            'amount': amountString,
            'cost': None,
            'fee': None,
        }, market)

    def handle_error_message(self, client: Client, message) -> Bool:
        #
        #     {
        #         "response_type": "ERROR",
        #         "error_code": 160012,
        #         "message": "Invalid Topic"
        #     }
        #
        type = self.safe_string(message, 'response_type', '')
        if type == 'ERROR':
            return True
        return False

    def handle_message(self, client: Client, message):
        if self.handle_error_message(client, message):
            return
        type = self.safe_string(message, 'response_type')
        if type == 'PONG':
            self.handle_pong(client, message)
            return
        if type == 'DATA':
            topic = self.safe_string(message, 'channel', '')
            methods: dict = {
                'ORDERBOOK': self.handle_order_book,
                'TICKER': self.handle_ticker,
                'TRADE': self.handle_trades,
            }
            exacMethod = self.safe_value(methods, topic)
            if exacMethod is not None:
                exacMethod(client, message)
                return
            keys = list(methods.keys())
            for i in range(0, len(keys)):
                key = keys[i]
                if topic.find(keys[i]) >= 0:
                    method = methods[key]
                    method(client, message)
                    return

    def ping(self, client: Client):
        return {
            'request_type': 'PING',
        }

    def handle_pong(self, client: Client, message):
        #
        #     {
        #         "response_type":"PONG"
        #     }
        #
        client.lastPong = self.milliseconds()
        return message
