"""
Binance Derivatives Trading USDS Futures WebSocket API

OpenAPI Specification for the Binance Derivatives Trading USDS Futures WebSocket API
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_common.errors import RequiredError
from binance_sdk_derivatives_trading_usds_futures.websocket_api.api import MarketDataApi


from binance_sdk_derivatives_trading_usds_futures.websocket_api.models import (
    OrderBookResponse,
)
from binance_sdk_derivatives_trading_usds_futures.websocket_api.models import (
    SymbolOrderBookTickerResponse,
)
from binance_sdk_derivatives_trading_usds_futures.websocket_api.models import (
    SymbolPriceTickerResponse,
)


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

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

        params = {
            "symbol": "symbol_example",
        }

        expected_response = {
            "id": "51e2affb-0aba-4821-ba75-f2625006eb43",
            "status": 200,
            "result": {
                "lastUpdateId": 1027024,
                "E": 1589436922972,
                "T": 1589436922959,
                "bids": [["4.00000000", "431.00000000"]],
                "asks": [["4.00000200", "12.00000000"]],
            },
            "rateLimits": [
                {
                    "rateLimitType": "REQUEST_WEIGHT",
                    "interval": "MINUTE",
                    "intervalNum": 1,
                    "limit": 2400,
                    "count": 5,
                }
            ],
        }

        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.order_book(**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"] == "/depth".replace("/", "", 1)

        assert "params" in request_kwargs["payload"]
        params = request_kwargs["payload"]["params"]
        assert params["symbol"] == "symbol_example"

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

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

        params = {
            "symbol": "symbol_example",
            "id": "e9d6b4349871b40611412680b3445fac",
            "limit": 10,
        }

        expected_response = {
            "id": "51e2affb-0aba-4821-ba75-f2625006eb43",
            "status": 200,
            "result": {
                "lastUpdateId": 1027024,
                "E": 1589436922972,
                "T": 1589436922959,
                "bids": [["4.00000000", "431.00000000"]],
                "asks": [["4.00000200", "12.00000000"]],
            },
            "rateLimits": [
                {
                    "rateLimitType": "REQUEST_WEIGHT",
                    "interval": "MINUTE",
                    "intervalNum": 1,
                    "limit": 2400,
                    "count": 5,
                }
            ],
        }

        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.order_book(**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"] == "/depth".replace("/", "", 1)
        assert params["symbol"] == "symbol_example"
        assert params["id"] == "e9d6b4349871b40611412680b3445fac"
        assert params["limit"] == 10

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

    @pytest.mark.asyncio
    async def test_order_book_missing_required_param_symbol(self):
        """Test that order_book() raises RequiredError when 'symbol' is missing."""

        params = {
            "symbol": "symbol_example",
            "id": "e9d6b4349871b40611412680b3445fac",
            "limit": 10,
        }
        params["symbol"] = None

        with pytest.raises(RequiredError, match="Missing required parameter 'symbol'"):
            await self.websocket_api.order_book(**params)

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

        params = {
            "symbol": "symbol_example",
        }

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

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

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

        expected_response = {
            "id": "9d32157c-a556-4d27-9866-66760a174b57",
            "status": 200,
            "result": {
                "lastUpdateId": 1027024,
                "symbol": "BTCUSDT",
                "bidPrice": "4.00000000",
                "bidQty": "431.00000000",
                "askPrice": "4.00000200",
                "askQty": "9.00000000",
                "time": 1589437530011,
            },
            "rateLimits": [
                {
                    "rateLimitType": "REQUEST_WEIGHT",
                    "interval": "MINUTE",
                    "intervalNum": 1,
                    "limit": 2400,
                    "count": 2,
                }
            ],
        }

        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.symbol_order_book_ticker()

        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"] == "/ticker.book".replace("/", "", 1)

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

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

        params = {"id": "e9d6b4349871b40611412680b3445fac", "symbol": "symbol_example"}

        expected_response = {
            "id": "9d32157c-a556-4d27-9866-66760a174b57",
            "status": 200,
            "result": {
                "lastUpdateId": 1027024,
                "symbol": "BTCUSDT",
                "bidPrice": "4.00000000",
                "bidQty": "431.00000000",
                "askPrice": "4.00000200",
                "askQty": "9.00000000",
                "time": 1589437530011,
            },
            "rateLimits": [
                {
                    "rateLimitType": "REQUEST_WEIGHT",
                    "interval": "MINUTE",
                    "intervalNum": 1,
                    "limit": 2400,
                    "count": 2,
                }
            ],
        }

        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.symbol_order_book_ticker(**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"] == "/ticker.book".replace("/", "", 1)
        assert params["id"] == "e9d6b4349871b40611412680b3445fac"
        assert params["symbol"] == "symbol_example"

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

    @pytest.mark.asyncio
    async def test_symbol_order_book_ticker_server_error(self):
        """Test that symbol_order_book_ticker() 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.symbol_order_book_ticker()

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

        expected_response = {
            "id": "9d32157c-a556-4d27-9866-66760a174b57",
            "status": 200,
            "result": {"symbol": "BTCUSDT", "price": "6000.01", "time": 1589437530011},
            "rateLimits": [
                {
                    "rateLimitType": "REQUEST_WEIGHT",
                    "interval": "MINUTE",
                    "intervalNum": 1,
                    "limit": 2400,
                    "count": 2,
                }
            ],
        }

        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.symbol_price_ticker()

        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"] == "/ticker.price".replace(
            "/", "", 1
        )

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

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

        params = {"id": "e9d6b4349871b40611412680b3445fac", "symbol": "symbol_example"}

        expected_response = {
            "id": "9d32157c-a556-4d27-9866-66760a174b57",
            "status": 200,
            "result": {"symbol": "BTCUSDT", "price": "6000.01", "time": 1589437530011},
            "rateLimits": [
                {
                    "rateLimitType": "REQUEST_WEIGHT",
                    "interval": "MINUTE",
                    "intervalNum": 1,
                    "limit": 2400,
                    "count": 2,
                }
            ],
        }

        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.symbol_price_ticker(**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"] == "/ticker.price".replace(
            "/", "", 1
        )
        assert params["id"] == "e9d6b4349871b40611412680b3445fac"
        assert params["symbol"] == "symbol_example"

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

    @pytest.mark.asyncio
    async def test_symbol_price_ticker_server_error(self):
        """Test that symbol_price_ticker() 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.symbol_price_ticker()
