"""
Binance Derivatives Trading Portfolio Margin Pro REST API

OpenAPI Specification for the Binance Derivatives Trading Portfolio Margin Pro REST 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 json
import pytest
import requests

from unittest.mock import MagicMock, patch
from urllib.parse import parse_qs

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

from binance_sdk_derivatives_trading_portfolio_margin_pro.rest_api.api import (
    MarketDataApi,
)
from binance_sdk_derivatives_trading_portfolio_margin_pro.rest_api.models import (
    GetPortfolioMarginAssetLeverageResponse,
)
from binance_sdk_derivatives_trading_portfolio_margin_pro.rest_api.models import (
    PortfolioMarginCollateralRateResponse,
)
from binance_sdk_derivatives_trading_portfolio_margin_pro.rest_api.models import (
    PortfolioMarginProTieredCollateralRateResponse,
)
from binance_sdk_derivatives_trading_portfolio_margin_pro.rest_api.models import (
    QueryPortfolioMarginAssetIndexPriceResponse,
)


class TestMarketDataApi:
    @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 = MarketDataApi(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

    @patch("binance_common.utils.get_signature")
    def test_get_portfolio_margin_asset_leverage_success(self, mock_get_signature):
        """Test get_portfolio_margin_asset_leverage() successfully with required parameters only."""

        expected_response = [
            {"asset": "USDC", "leverage": 10},
            {"asset": "USDT", "leverage": 10},
        ]
        mock_get_signature.return_value = "mocked_signature"
        self.set_mock_response(expected_response)

        response = self.client.get_portfolio_margin_asset_leverage()

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

        self.mock_session.request.assert_called_once()
        mock_get_signature.assert_called_once()

        assert "url" in request_kwargs
        assert "signature" in parse_qs(request_kwargs["params"])
        assert "/sapi/v1/portfolio/margin-asset-leverage" 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(GetPortfolioMarginAssetLeverageResponse)

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

        assert response.data() == expected

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

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

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

    def test_portfolio_margin_collateral_rate_success(self):
        """Test portfolio_margin_collateral_rate() successfully with required parameters only."""

        expected_response = [
            {"asset": "USDC", "collateralRate": "1.0000"},
            {"asset": "BUSD", "collateralRate": "1.0000"},
        ]

        self.set_mock_response(expected_response)

        response = self.client.portfolio_margin_collateral_rate()

        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 "/sapi/v1/portfolio/collateralRate" 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(PortfolioMarginCollateralRateResponse)

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

        assert response.data() == expected

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

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

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

    @patch("binance_common.utils.get_signature")
    def test_portfolio_margin_pro_tiered_collateral_rate_success(
        self, mock_get_signature
    ):
        """Test portfolio_margin_pro_tiered_collateral_rate() successfully with required parameters only."""

        expected_response = [
            {
                "asset": "BNB",
                "collateralInfo": [
                    {
                        "tierFloor": "0.0000",
                        "tierCap": "1000.0000",
                        "collateralRate": "1.0000",
                        "cum": "0.0000",
                    },
                    {
                        "tierFloor": "1000.0000",
                        "tierCap": "2000.0000",
                        "collateralRate": "0.9000",
                        "cum": "0.0000",
                    },
                ],
            },
            {
                "asset": "USDT",
                "collateralInfo": [
                    {
                        "tierFloor": "0.0000",
                        "tierCap": "1000.0000",
                        "collateralRate": "1.0000",
                        "cum": "0.0000",
                    },
                    {
                        "tierFloor": "1000.0000",
                        "tierCap": "2000.0000",
                        "collateralRate": "0.9999",
                        "cum": "0.0000",
                    },
                ],
            },
        ]
        mock_get_signature.return_value = "mocked_signature"
        self.set_mock_response(expected_response)

        response = self.client.portfolio_margin_pro_tiered_collateral_rate()

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

        self.mock_session.request.assert_called_once()
        mock_get_signature.assert_called_once()

        assert "url" in request_kwargs
        assert "signature" in parse_qs(request_kwargs["params"])
        assert "/sapi/v2/portfolio/collateralRate" 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(PortfolioMarginProTieredCollateralRateResponse)

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

        assert response.data() == expected

    @patch("binance_common.utils.get_signature")
    def test_portfolio_margin_pro_tiered_collateral_rate_success_with_optional_params(
        self, mock_get_signature
    ):
        """Test portfolio_margin_pro_tiered_collateral_rate() successfully with optional parameters."""

        params = {"recv_window": 5000}

        expected_response = [
            {
                "asset": "BNB",
                "collateralInfo": [
                    {
                        "tierFloor": "0.0000",
                        "tierCap": "1000.0000",
                        "collateralRate": "1.0000",
                        "cum": "0.0000",
                    },
                    {
                        "tierFloor": "1000.0000",
                        "tierCap": "2000.0000",
                        "collateralRate": "0.9000",
                        "cum": "0.0000",
                    },
                ],
            },
            {
                "asset": "USDT",
                "collateralInfo": [
                    {
                        "tierFloor": "0.0000",
                        "tierCap": "1000.0000",
                        "collateralRate": "1.0000",
                        "cum": "0.0000",
                    },
                    {
                        "tierFloor": "1000.0000",
                        "tierCap": "2000.0000",
                        "collateralRate": "0.9999",
                        "cum": "0.0000",
                    },
                ],
            },
        ]
        mock_get_signature.return_value = "mocked_signature"
        self.set_mock_response(expected_response)

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

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

        assert "url" in request_kwargs
        assert "signature" in parse_qs(request_kwargs["params"])
        assert "/sapi/v2/portfolio/collateralRate" 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(PortfolioMarginProTieredCollateralRateResponse)

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

        assert response.data() == expected

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

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

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

    def test_query_portfolio_margin_asset_index_price_success(self):
        """Test query_portfolio_margin_asset_index_price() successfully with required parameters only."""

        expected_response = [
            {"asset": "BTC", "assetIndexPrice": "28251.9136906", "time": 1683518338121}
        ]

        self.set_mock_response(expected_response)

        response = self.client.query_portfolio_margin_asset_index_price()

        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 "/sapi/v1/portfolio/asset-index-price" 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(QueryPortfolioMarginAssetIndexPriceResponse)

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

        assert response.data() == expected

    def test_query_portfolio_margin_asset_index_price_success_with_optional_params(
        self,
    ):
        """Test query_portfolio_margin_asset_index_price() successfully with optional parameters."""

        params = {"asset": "asset_example"}

        expected_response = [
            {"asset": "BTC", "assetIndexPrice": "28251.9136906", "time": 1683518338121}
        ]

        self.set_mock_response(expected_response)

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

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

        assert "url" in request_kwargs
        assert "/sapi/v1/portfolio/asset-index-price" 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(QueryPortfolioMarginAssetIndexPriceResponse)

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

        assert response.data() == expected

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

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

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