"""
Binance Spot REST API

OpenAPI Specifications for the Binance Spot REST API

API documents:
  - [Github rest-api documentation file](https://github.com/binance/binance-spot-api-docs/blob/master/rest-api.md)
  - [General API information for rest-api on website](https://developers.binance.com/docs/binance-spot-api-docs/rest-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 json
import pytest
import requests

from unittest.mock import MagicMock

from binance_common.configuration import ConfigurationRestAPI
from binance_common.utils import is_one_of_model

from binance_sdk_spot.rest_api.api import GeneralApi
from binance_sdk_spot.rest_api.models import ExchangeInfoResponse

from binance_sdk_spot.rest_api.models import TimeResponse


from binance_sdk_spot.rest_api.models import ExchangeInfoSymbolStatusEnum


class TestGeneralApi:
    @pytest.fixture(autouse=True)
    def setup_client(self):
        """Setup a client instance with mocked session before each test method."""
        self.mock_session = MagicMock(spec=requests.Session)
        config = ConfigurationRestAPI(
            api_key="test-api-key",
            api_secret="test-api-secret",
        )
        self.client = GeneralApi(configuration=config, session=self.mock_session)

    def set_mock_response(self, data: dict = {}, status_code=200, headers=None):
        """Helper method to setup mock response for the client's session request."""
        if headers is None:
            headers = {}

        mock_response = MagicMock()
        mock_response.status_code = status_code
        mock_response.json.return_value = data
        mock_response.text = json.dumps(data)
        mock_response.headers = headers

        self.mock_session.request.return_value = mock_response

    def test_exchange_info_success(self):
        """Test exchange_info() successfully with required parameters only."""

        expected_response = {
            "timezone": "UTC",
            "serverTime": 1565246363776,
            "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,
                },
            ],
            "exchangeFilters": [],
            "symbols": [
                {
                    "symbol": "ETHBTC",
                    "status": "TRADING",
                    "baseAsset": "ETH",
                    "baseAssetPrecision": 8,
                    "quoteAsset": "BTC",
                    "quotePrecision": 8,
                    "quoteAssetPrecision": 8,
                    "baseCommissionPrecision": 8,
                    "quoteCommissionPrecision": 8,
                    "orderTypes": [
                        "LIMIT LIMIT_MAKER MARKET STOP_LOSS STOP_LOSS_LIMIT TAKE_PROFIT TAKE_PROFIT_LIMIT"
                    ],
                    "icebergAllowed": True,
                    "ocoAllowed": True,
                    "otoAllowed": True,
                    "opoAllowed": True,
                    "quoteOrderQtyMarketAllowed": True,
                    "allowTrailingStop": False,
                    "cancelReplaceAllowed": False,
                    "amendAllowed": False,
                    "pegInstructionsAllowed": True,
                    "isSpotTradingAllowed": True,
                    "isMarginTradingAllowed": True,
                    "filters": [],
                    "permissions": [],
                    "permissionSets": [["SPOT", "MARGIN"]],
                    "defaultSelfTradePreventionMode": "NONE",
                    "allowedSelfTradePreventionModes": ["NONE"],
                }
            ],
        }

        self.set_mock_response(expected_response)

        response = self.client.exchange_info()

        actual_call_args = self.mock_session.request.call_args
        request_kwargs = actual_call_args.kwargs

        self.mock_session.request.assert_called_once()

        assert "url" in request_kwargs
        assert "/api/v3/exchangeInfo" in request_kwargs["url"]
        assert request_kwargs["method"] == "GET"

        assert response is not None
        is_list = isinstance(expected_response, list)
        is_flat_list = (
            is_list and not isinstance(expected_response[0], list) if is_list else False
        )
        is_oneof = is_one_of_model(ExchangeInfoResponse)

        if is_list and not is_flat_list:
            expected = expected_response
        elif is_oneof or is_list or hasattr(ExchangeInfoResponse, "from_dict"):
            expected = ExchangeInfoResponse.from_dict(expected_response)
        else:
            expected = ExchangeInfoResponse.model_validate_json(
                json.dumps(expected_response)
            )

        assert response.data() == expected

    def test_exchange_info_success_with_optional_params(self):
        """Test exchange_info() successfully with optional parameters."""

        params = {
            "symbol": "BNBUSDT",
            "symbols": [""],
            "permissions": [""],
            "show_permission_sets": True,
            "symbol_status": ExchangeInfoSymbolStatusEnum["TRADING"].value,
        }

        expected_response = {
            "timezone": "UTC",
            "serverTime": 1565246363776,
            "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,
                },
            ],
            "exchangeFilters": [],
            "symbols": [
                {
                    "symbol": "ETHBTC",
                    "status": "TRADING",
                    "baseAsset": "ETH",
                    "baseAssetPrecision": 8,
                    "quoteAsset": "BTC",
                    "quotePrecision": 8,
                    "quoteAssetPrecision": 8,
                    "baseCommissionPrecision": 8,
                    "quoteCommissionPrecision": 8,
                    "orderTypes": [
                        "LIMIT LIMIT_MAKER MARKET STOP_LOSS STOP_LOSS_LIMIT TAKE_PROFIT TAKE_PROFIT_LIMIT"
                    ],
                    "icebergAllowed": True,
                    "ocoAllowed": True,
                    "otoAllowed": True,
                    "opoAllowed": True,
                    "quoteOrderQtyMarketAllowed": True,
                    "allowTrailingStop": False,
                    "cancelReplaceAllowed": False,
                    "amendAllowed": False,
                    "pegInstructionsAllowed": True,
                    "isSpotTradingAllowed": True,
                    "isMarginTradingAllowed": True,
                    "filters": [],
                    "permissions": [],
                    "permissionSets": [["SPOT", "MARGIN"]],
                    "defaultSelfTradePreventionMode": "NONE",
                    "allowedSelfTradePreventionModes": ["NONE"],
                }
            ],
        }

        self.set_mock_response(expected_response)

        response = self.client.exchange_info(**params)

        actual_call_args = self.mock_session.request.call_args
        request_kwargs = actual_call_args.kwargs

        assert "url" in request_kwargs
        assert "/api/v3/exchangeInfo" in request_kwargs["url"]
        assert request_kwargs["method"] == "GET"

        self.mock_session.request.assert_called_once()
        assert response is not None
        is_list = isinstance(expected_response, list)
        is_flat_list = (
            is_list and not isinstance(expected_response[0], list) if is_list else False
        )
        is_oneof = is_one_of_model(ExchangeInfoResponse)

        if is_list and not is_flat_list:
            expected = expected_response
        elif is_oneof or is_list or hasattr(ExchangeInfoResponse, "from_dict"):
            expected = ExchangeInfoResponse.from_dict(expected_response)
        else:
            expected = ExchangeInfoResponse.model_validate_json(
                json.dumps(expected_response)
            )

        assert response.data() == expected

    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.client.exchange_info = MagicMock(side_effect=mock_error)

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

    def test_ping_success(self):
        """Test ping() successfully with required parameters only."""

        self.set_mock_response({})

        response = self.client.ping()

        actual_call_args = self.mock_session.request.call_args
        request_kwargs = actual_call_args.kwargs

        self.mock_session.request.assert_called_once()

        assert "url" in request_kwargs
        assert "/api/v3/ping" in request_kwargs["url"]
        assert request_kwargs["method"] == "GET"

        assert response is not None

        assert response.data() == {}

    def test_ping_server_error(self):
        """Test that ping() raises an error when the server returns an error."""

        mock_error = Exception("ResponseError")
        self.client.ping = MagicMock(side_effect=mock_error)

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

    def test_time_success(self):
        """Test time() successfully with required parameters only."""

        expected_response = {"serverTime": 1499827319559}

        self.set_mock_response(expected_response)

        response = self.client.time()

        actual_call_args = self.mock_session.request.call_args
        request_kwargs = actual_call_args.kwargs

        self.mock_session.request.assert_called_once()

        assert "url" in request_kwargs
        assert "/api/v3/time" in request_kwargs["url"]
        assert request_kwargs["method"] == "GET"

        assert response is not None
        is_list = isinstance(expected_response, list)
        is_flat_list = (
            is_list and not isinstance(expected_response[0], list) if is_list else False
        )
        is_oneof = is_one_of_model(TimeResponse)

        if is_list and not is_flat_list:
            expected = expected_response
        elif is_oneof or is_list or hasattr(TimeResponse, "from_dict"):
            expected = TimeResponse.from_dict(expected_response)
        else:
            expected = TimeResponse.model_validate_json(json.dumps(expected_response))

        assert response.data() == expected

    def test_time_server_error(self):
        """Test that time() raises an error when the server returns an error."""

        mock_error = Exception("ResponseError")
        self.client.time = MagicMock(side_effect=mock_error)

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