"""
Binance Spot WebSocket API

OpenAPI Specifications for the Binance Spot WebSocket API

API documents:
  - [Github web-socket-api documentation file](https://github.com/binance/binance-spot-api-docs/blob/master/web-socket-api.md)
  - [General API information for web-socket-api on website](https://developers.binance.com/docs/binance-spot-api-docs/web-socket-api/general-api-information)

The version of the OpenAPI document: 1.0.0
Generated by OpenAPI Generator (https://openapi-generator.tech)

Do not edit the class manually.
"""

import pytest

from unittest.mock import AsyncMock, MagicMock

from binance_common.models import WebsocketApiResponse
from binance_common.utils import parse_ws_rate_limit_headers
from binance_sdk_spot.websocket_api.api import GeneralApi


from binance_sdk_spot.websocket_api.models import ExchangeInfoSymbolStatusEnum
from binance_sdk_spot.websocket_api.models import ExchangeInfoResponse
from binance_sdk_spot.websocket_api.models import PingResponse
from binance_sdk_spot.websocket_api.models import TimeResponse


class TestWebSocketGeneralApi:
    @pytest.fixture(autouse=True)
    def setup_method(self):
        self.mock_websocket_api = MagicMock()
        self.websocket_api = GeneralApi(websocket_api=self.mock_websocket_api)

    @pytest.mark.asyncio
    async def test_exchange_info_success(self):
        """Test exchange_info() successfully with required parameters only."""

        expected_response = {
            "id": "5494febb-d167-46a2-996d-70533eb4d976",
            "status": 200,
            "result": {
                "timezone": "UTC",
                "serverTime": 1655969291181,
                "rateLimits": [
                    {
                        "rateLimitType": "REQUEST_WEIGHT",
                        "interval": "MINUTE",
                        "intervalNum": 1,
                        "limit": 6000,
                    },
                    {
                        "rateLimitType": "ORDERS",
                        "interval": "SECOND",
                        "intervalNum": 10,
                        "limit": 50,
                    },
                    {
                        "rateLimitType": "ORDERS",
                        "interval": "DAY",
                        "intervalNum": 1,
                        "limit": 160000,
                    },
                    {
                        "rateLimitType": "CONNECTIONS",
                        "interval": "MINUTE",
                        "intervalNum": 5,
                        "limit": 300,
                    },
                ],
                "exchangeFilters": [],
                "symbols": [
                    {
                        "symbol": "BNBBTC",
                        "status": "TRADING",
                        "baseAsset": "BNB",
                        "baseAssetPrecision": 8,
                        "quoteAsset": "BTC",
                        "quotePrecision": 8,
                        "quoteAssetPrecision": 8,
                        "baseCommissionPrecision": 8,
                        "quoteCommissionPrecision": 8,
                        "orderTypes": [
                            "LIMIT LIMIT_MAKER MARKET STOP_LOSS_LIMIT TAKE_PROFIT_LIMIT"
                        ],
                        "icebergAllowed": True,
                        "ocoAllowed": True,
                        "otoAllowed": True,
                        "opoAllowed": True,
                        "quoteOrderQtyMarketAllowed": True,
                        "allowTrailingStop": True,
                        "cancelReplaceAllowed": True,
                        "amendAllowed": False,
                        "pegInstructionsAllowed": True,
                        "isSpotTradingAllowed": True,
                        "isMarginTradingAllowed": True,
                        "filters": [
                            {
                                "filterType": "PRICE_FILTER",
                                "minPrice": "0.00000100",
                                "maxPrice": "100000.00000000",
                                "tickSize": "0.00000100",
                            },
                            {
                                "filterType": "LOT_SIZE",
                                "minQty": "0.00100000",
                                "maxQty": "100000.00000000",
                                "stepSize": "0.00100000",
                            },
                        ],
                        "permissions": [],
                        "permissionSets": [["SPOT", "MARGIN", "TRD_GRP_004"]],
                        "defaultSelfTradePreventionMode": "NONE",
                        "allowedSelfTradePreventionModes": ["NONE"],
                    }
                ],
                "sors": [{"baseAsset": "BTC", "symbols": ["BTCUSDT BTCUSDC"]}],
            },
            "rateLimits": [
                {
                    "rateLimitType": "REQUEST_WEIGHT",
                    "interval": "MINUTE",
                    "intervalNum": 1,
                    "limit": 6000,
                },
                {
                    "rateLimitType": "ORDERS",
                    "interval": "DAY",
                    "intervalNum": 1,
                    "limit": 160000,
                },
                {
                    "rateLimitType": "RAW_REQUESTS",
                    "interval": "MINUTE",
                    "intervalNum": 5,
                    "limit": 61000,
                },
            ],
        }

        self.mock_websocket_api.send_message = AsyncMock(
            return_value=WebsocketApiResponse(
                data_function=lambda: expected_response,
                rate_limits=(
                    parse_ws_rate_limit_headers(expected_response["rateLimits"])
                    if "rateLimits" in expected_response
                    else None
                ),
            )
        )
        result = await self.websocket_api.exchange_info()

        actual_call_args = self.mock_websocket_api.send_message.call_args
        request_kwargs = actual_call_args.kwargs

        assert "payload" in request_kwargs
        assert "method" in request_kwargs["payload"]
        assert request_kwargs["payload"]["method"] == "/exchangeInfo".replace(
            "/", "", 1
        )

        assert result is not None
        assert result.data() == expected_response
        self.mock_websocket_api.send_message.assert_called_once_with(
            payload={"method": "/exchangeInfo".replace("/", "", 1), "params": {}},
            response_model=ExchangeInfoResponse,
        )

    @pytest.mark.asyncio
    async def test_exchange_info_success_with_optional_params(self):
        """Test exchange_info() successfully with optional parameters."""

        params = {
            "id": "e9d6b4349871b40611412680b3445fac",
            "symbol": "BNBUSDT",
            "symbols": ["symbols_example"],
            "permissions": ["permissions_example"],
            "show_permission_sets": False,
            "symbol_status": ExchangeInfoSymbolStatusEnum["TRADING"].value,
        }

        expected_response = {
            "id": "5494febb-d167-46a2-996d-70533eb4d976",
            "status": 200,
            "result": {
                "timezone": "UTC",
                "serverTime": 1655969291181,
                "rateLimits": [
                    {
                        "rateLimitType": "REQUEST_WEIGHT",
                        "interval": "MINUTE",
                        "intervalNum": 1,
                        "limit": 6000,
                    },
                    {
                        "rateLimitType": "ORDERS",
                        "interval": "SECOND",
                        "intervalNum": 10,
                        "limit": 50,
                    },
                    {
                        "rateLimitType": "ORDERS",
                        "interval": "DAY",
                        "intervalNum": 1,
                        "limit": 160000,
                    },
                    {
                        "rateLimitType": "CONNECTIONS",
                        "interval": "MINUTE",
                        "intervalNum": 5,
                        "limit": 300,
                    },
                ],
                "exchangeFilters": [],
                "symbols": [
                    {
                        "symbol": "BNBBTC",
                        "status": "TRADING",
                        "baseAsset": "BNB",
                        "baseAssetPrecision": 8,
                        "quoteAsset": "BTC",
                        "quotePrecision": 8,
                        "quoteAssetPrecision": 8,
                        "baseCommissionPrecision": 8,
                        "quoteCommissionPrecision": 8,
                        "orderTypes": [
                            "LIMIT LIMIT_MAKER MARKET STOP_LOSS_LIMIT TAKE_PROFIT_LIMIT"
                        ],
                        "icebergAllowed": True,
                        "ocoAllowed": True,
                        "otoAllowed": True,
                        "opoAllowed": True,
                        "quoteOrderQtyMarketAllowed": True,
                        "allowTrailingStop": True,
                        "cancelReplaceAllowed": True,
                        "amendAllowed": False,
                        "pegInstructionsAllowed": True,
                        "isSpotTradingAllowed": True,
                        "isMarginTradingAllowed": True,
                        "filters": [
                            {
                                "filterType": "PRICE_FILTER",
                                "minPrice": "0.00000100",
                                "maxPrice": "100000.00000000",
                                "tickSize": "0.00000100",
                            },
                            {
                                "filterType": "LOT_SIZE",
                                "minQty": "0.00100000",
                                "maxQty": "100000.00000000",
                                "stepSize": "0.00100000",
                            },
                        ],
                        "permissions": [],
                        "permissionSets": [["SPOT", "MARGIN", "TRD_GRP_004"]],
                        "defaultSelfTradePreventionMode": "NONE",
                        "allowedSelfTradePreventionModes": ["NONE"],
                    }
                ],
                "sors": [{"baseAsset": "BTC", "symbols": ["BTCUSDT BTCUSDC"]}],
            },
            "rateLimits": [
                {
                    "rateLimitType": "REQUEST_WEIGHT",
                    "interval": "MINUTE",
                    "intervalNum": 1,
                    "limit": 6000,
                },
                {
                    "rateLimitType": "ORDERS",
                    "interval": "DAY",
                    "intervalNum": 1,
                    "limit": 160000,
                },
                {
                    "rateLimitType": "RAW_REQUESTS",
                    "interval": "MINUTE",
                    "intervalNum": 5,
                    "limit": 61000,
                },
            ],
        }

        self.mock_websocket_api.send_message = AsyncMock(
            return_value=WebsocketApiResponse(
                data_function=lambda: expected_response,
                rate_limits=(
                    parse_ws_rate_limit_headers(expected_response["rateLimits"])
                    if "rateLimits" in expected_response
                    else None
                ),
            )
        )

        result = await self.websocket_api.exchange_info(**params)

        actual_call_args = self.mock_websocket_api.send_message.call_args
        request_kwargs = actual_call_args.kwargs

        assert "payload" in request_kwargs
        assert "method" in request_kwargs["payload"]
        assert request_kwargs["payload"]["method"] == "/exchangeInfo".replace(
            "/", "", 1
        )
        assert "params" in request_kwargs["payload"]
        params = request_kwargs["payload"]["params"]
        assert params["id"] == "e9d6b4349871b40611412680b3445fac"
        assert params["symbol"] == "BNBUSDT"
        assert params["symbols"] == ["symbols_example"]
        assert params["permissions"] == ["permissions_example"]
        assert params["show_permission_sets"] is False
        assert params["symbol_status"] == ExchangeInfoSymbolStatusEnum["TRADING"].value

        assert result is not None
        assert result.data() == expected_response
        self.mock_websocket_api.send_message.assert_called_once_with(
            payload={"method": "/exchangeInfo".replace("/", "", 1), "params": params},
            response_model=ExchangeInfoResponse,
        )

    @pytest.mark.asyncio
    async def test_exchange_info_server_error(self):
        """Test that exchange_info() raises an error when the server returns an error."""

        mock_error = Exception("ResponseError")
        self.mock_websocket_api.send_message.side_effect = mock_error

        with pytest.raises(Exception, match="ResponseError"):
            await self.websocket_api.exchange_info()

    @pytest.mark.asyncio
    async def test_ping_success(self):
        """Test ping() successfully with required parameters only."""

        expected_response = {
            "id": "922bcc6e-9de8-440d-9e84-7c80933a8d0d",
            "status": 200,
            "result": {},
            "rateLimits": [
                {
                    "rateLimitType": "REQUEST_WEIGHT",
                    "interval": "MINUTE",
                    "intervalNum": 1,
                    "limit": 6000,
                    "count": 1,
                }
            ],
        }

        self.mock_websocket_api.send_message = AsyncMock(
            return_value=WebsocketApiResponse(
                data_function=lambda: expected_response,
                rate_limits=(
                    parse_ws_rate_limit_headers(expected_response["rateLimits"])
                    if "rateLimits" in expected_response
                    else None
                ),
            )
        )
        result = await self.websocket_api.ping()

        actual_call_args = self.mock_websocket_api.send_message.call_args
        request_kwargs = actual_call_args.kwargs

        assert "payload" in request_kwargs
        assert "method" in request_kwargs["payload"]
        assert request_kwargs["payload"]["method"] == "/ping".replace("/", "", 1)

        assert result is not None
        assert result.data() == expected_response
        self.mock_websocket_api.send_message.assert_called_once_with(
            payload={"method": "/ping".replace("/", "", 1), "params": {}},
            response_model=PingResponse,
        )

    @pytest.mark.asyncio
    async def test_ping_success_with_optional_params(self):
        """Test ping() successfully with optional parameters."""

        params = {"id": "e9d6b4349871b40611412680b3445fac"}

        expected_response = {
            "id": "922bcc6e-9de8-440d-9e84-7c80933a8d0d",
            "status": 200,
            "result": {},
            "rateLimits": [
                {
                    "rateLimitType": "REQUEST_WEIGHT",
                    "interval": "MINUTE",
                    "intervalNum": 1,
                    "limit": 6000,
                    "count": 1,
                }
            ],
        }

        self.mock_websocket_api.send_message = AsyncMock(
            return_value=WebsocketApiResponse(
                data_function=lambda: expected_response,
                rate_limits=(
                    parse_ws_rate_limit_headers(expected_response["rateLimits"])
                    if "rateLimits" in expected_response
                    else None
                ),
            )
        )

        result = await self.websocket_api.ping(**params)

        actual_call_args = self.mock_websocket_api.send_message.call_args
        request_kwargs = actual_call_args.kwargs

        assert "payload" in request_kwargs
        assert "method" in request_kwargs["payload"]
        assert request_kwargs["payload"]["method"] == "/ping".replace("/", "", 1)
        assert params["id"] == "e9d6b4349871b40611412680b3445fac"

        assert result is not None
        assert result.data() == expected_response
        self.mock_websocket_api.send_message.assert_called_once_with(
            payload={"method": "/ping".replace("/", "", 1), "params": params},
            response_model=PingResponse,
        )

    @pytest.mark.asyncio
    async def test_ping_server_error(self):
        """Test that ping() raises an error when the server returns an error."""

        mock_error = Exception("ResponseError")
        self.mock_websocket_api.send_message.side_effect = mock_error

        with pytest.raises(Exception, match="ResponseError"):
            await self.websocket_api.ping()

    @pytest.mark.asyncio
    async def test_time_success(self):
        """Test time() successfully with required parameters only."""

        expected_response = {
            "id": "187d3cb2-942d-484c-8271-4e2141bbadb1",
            "status": 200,
            "result": {"serverTime": 1656400526260},
            "rateLimits": [
                {
                    "rateLimitType": "REQUEST_WEIGHT",
                    "interval": "MINUTE",
                    "intervalNum": 1,
                    "limit": 6000,
                    "count": 1,
                }
            ],
        }

        self.mock_websocket_api.send_message = AsyncMock(
            return_value=WebsocketApiResponse(
                data_function=lambda: expected_response,
                rate_limits=(
                    parse_ws_rate_limit_headers(expected_response["rateLimits"])
                    if "rateLimits" in expected_response
                    else None
                ),
            )
        )
        result = await self.websocket_api.time()

        actual_call_args = self.mock_websocket_api.send_message.call_args
        request_kwargs = actual_call_args.kwargs

        assert "payload" in request_kwargs
        assert "method" in request_kwargs["payload"]
        assert request_kwargs["payload"]["method"] == "/time".replace("/", "", 1)

        assert result is not None
        assert result.data() == expected_response
        self.mock_websocket_api.send_message.assert_called_once_with(
            payload={"method": "/time".replace("/", "", 1), "params": {}},
            response_model=TimeResponse,
        )

    @pytest.mark.asyncio
    async def test_time_success_with_optional_params(self):
        """Test time() successfully with optional parameters."""

        params = {"id": "e9d6b4349871b40611412680b3445fac"}

        expected_response = {
            "id": "187d3cb2-942d-484c-8271-4e2141bbadb1",
            "status": 200,
            "result": {"serverTime": 1656400526260},
            "rateLimits": [
                {
                    "rateLimitType": "REQUEST_WEIGHT",
                    "interval": "MINUTE",
                    "intervalNum": 1,
                    "limit": 6000,
                    "count": 1,
                }
            ],
        }

        self.mock_websocket_api.send_message = AsyncMock(
            return_value=WebsocketApiResponse(
                data_function=lambda: expected_response,
                rate_limits=(
                    parse_ws_rate_limit_headers(expected_response["rateLimits"])
                    if "rateLimits" in expected_response
                    else None
                ),
            )
        )

        result = await self.websocket_api.time(**params)

        actual_call_args = self.mock_websocket_api.send_message.call_args
        request_kwargs = actual_call_args.kwargs

        assert "payload" in request_kwargs
        assert "method" in request_kwargs["payload"]
        assert request_kwargs["payload"]["method"] == "/time".replace("/", "", 1)
        assert params["id"] == "e9d6b4349871b40611412680b3445fac"

        assert result is not None
        assert result.data() == expected_response
        self.mock_websocket_api.send_message.assert_called_once_with(
            payload={"method": "/time".replace("/", "", 1), "params": params},
            response_model=TimeResponse,
        )

    @pytest.mark.asyncio
    async def test_time_server_error(self):
        """Test that time() raises an error when the server returns an error."""

        mock_error = Exception("ResponseError")
        self.mock_websocket_api.send_message.side_effect = mock_error

        with pytest.raises(Exception, match="ResponseError"):
            await self.websocket_api.time()
