"""
Binance Dual Investment REST API

OpenAPI Specification for the Binance Dual Investment 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.errors import RequiredError
from binance_common.utils import normalize_query_values, is_one_of_model, snake_to_camel

from binance_sdk_dual_investment.rest_api.api import TradeApi
from binance_sdk_dual_investment.rest_api.models import ChangeAutoCompoundStatusResponse
from binance_sdk_dual_investment.rest_api.models import (
    CheckDualInvestmentAccountsResponse,
)
from binance_sdk_dual_investment.rest_api.models import (
    GetDualInvestmentPositionsResponse,
)
from binance_sdk_dual_investment.rest_api.models import (
    SubscribeDualInvestmentProductsResponse,
)


class TestTradeApi:
    @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 = TradeApi(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_change_auto_compound_status_success(self, mock_get_signature):
        """Test change_auto_compound_status() successfully with required parameters only."""

        params = {
            "position_id": "1",
        }

        expected_response = {"positionId": "123456789", "autoCompoundPlan": "ADVANCED"}
        mock_get_signature.return_value = "mocked_signature"
        self.set_mock_response(expected_response)

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

        actual_call_args = self.mock_session.request.call_args
        request_kwargs = actual_call_args.kwargs
        parsed_params = parse_qs(request_kwargs["params"])
        camel_case_params = {snake_to_camel(k): v for k, v in params.items()}
        normalized = normalize_query_values(parsed_params, camel_case_params)

        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/dci/product/auto_compound/edit-status" in request_kwargs["url"]
        assert request_kwargs["method"] == "POST"
        assert normalized["positionId"] == "1"

        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(ChangeAutoCompoundStatusResponse)

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

        assert response.data() == expected

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

        params = {
            "position_id": "1",
            "auto_compound_plan": "auto_compound_plan_example",
            "recv_window": 5000,
        }

        expected_response = {"positionId": "123456789", "autoCompoundPlan": "ADVANCED"}
        mock_get_signature.return_value = "mocked_signature"
        self.set_mock_response(expected_response)

        response = self.client.change_auto_compound_status(**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/v1/dci/product/auto_compound/edit-status" in request_kwargs["url"]
        assert request_kwargs["method"] == "POST"

        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(ChangeAutoCompoundStatusResponse)

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

        assert response.data() == expected

    def test_change_auto_compound_status_missing_required_param_position_id(self):
        """Test that change_auto_compound_status() raises RequiredError when 'position_id' is missing."""
        params = {
            "position_id": "1",
        }
        params["position_id"] = None

        with pytest.raises(
            RequiredError, match="Missing required parameter 'position_id'"
        ):
            self.client.change_auto_compound_status(**params)

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

        params = {
            "position_id": "1",
        }

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

        with pytest.raises(Exception, match="ResponseError"):
            self.client.change_auto_compound_status(**params)

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

        expected_response = {
            "totalAmountInBTC": "0.01067982",
            "totalAmountInUSDT": "77.13289230",
        }
        mock_get_signature.return_value = "mocked_signature"
        self.set_mock_response(expected_response)

        response = self.client.check_dual_investment_accounts()

        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/dci/product/accounts" 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(CheckDualInvestmentAccountsResponse)

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

        assert response.data() == expected

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

        params = {"recv_window": 5000}

        expected_response = {
            "totalAmountInBTC": "0.01067982",
            "totalAmountInUSDT": "77.13289230",
        }
        mock_get_signature.return_value = "mocked_signature"
        self.set_mock_response(expected_response)

        response = self.client.check_dual_investment_accounts(**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/v1/dci/product/accounts" 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(CheckDualInvestmentAccountsResponse)

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

        assert response.data() == expected

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

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

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

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

        expected_response = {
            "total": 1,
            "list": [
                {
                    "id": "10160533",
                    "investCoin": "USDT",
                    "exercisedCoin": "BNB",
                    "subscriptionAmount": "0.5",
                    "strikePrice": "330",
                    "duration": 4,
                    "settleDate": 1708416000000,
                    "purchaseStatus": "PURCHASE_SUCCESS",
                    "apr": "0.0365",
                    "orderId": 7973677530,
                    "purchaseEndTime": 1708329600000,
                    "optionType": "PUT",
                    "autoCompoundPlan": "STANDARD",
                }
            ],
        }
        mock_get_signature.return_value = "mocked_signature"
        self.set_mock_response(expected_response)

        response = self.client.get_dual_investment_positions()

        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/dci/product/positions" 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(GetDualInvestmentPositionsResponse)

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

        assert response.data() == expected

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

        params = {
            "status": "status_example",
            "page_size": 10,
            "page_index": 1,
            "recv_window": 5000,
        }

        expected_response = {
            "total": 1,
            "list": [
                {
                    "id": "10160533",
                    "investCoin": "USDT",
                    "exercisedCoin": "BNB",
                    "subscriptionAmount": "0.5",
                    "strikePrice": "330",
                    "duration": 4,
                    "settleDate": 1708416000000,
                    "purchaseStatus": "PURCHASE_SUCCESS",
                    "apr": "0.0365",
                    "orderId": 7973677530,
                    "purchaseEndTime": 1708329600000,
                    "optionType": "PUT",
                    "autoCompoundPlan": "STANDARD",
                }
            ],
        }
        mock_get_signature.return_value = "mocked_signature"
        self.set_mock_response(expected_response)

        response = self.client.get_dual_investment_positions(**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/v1/dci/product/positions" 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(GetDualInvestmentPositionsResponse)

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

        assert response.data() == expected

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

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

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

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

        params = {
            "id": "id_example",
            "order_id": "1",
            "deposit_amount": 1.0,
            "auto_compound_plan": "NONE",
        }

        expected_response = {
            "positionId": 10208824,
            "investCoin": "BNB",
            "exercisedCoin": "USDT",
            "subscriptionAmount": "0.002",
            "duration": 4,
            "autoCompoundPlan": "STANDARD",
            "strikePrice": "380",
            "settleDate": 1709020800000,
            "purchaseStatus": "PURCHASE_SUCCESS",
            "apr": "0.7397",
            "orderId": 8259117597,
            "purchaseTime": 1708677583874,
            "optionType": "CALL",
        }
        mock_get_signature.return_value = "mocked_signature"
        self.set_mock_response(expected_response)

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

        actual_call_args = self.mock_session.request.call_args
        request_kwargs = actual_call_args.kwargs
        parsed_params = parse_qs(request_kwargs["params"])
        camel_case_params = {snake_to_camel(k): v for k, v in params.items()}
        normalized = normalize_query_values(parsed_params, camel_case_params)

        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/dci/product/subscribe" in request_kwargs["url"]
        assert request_kwargs["method"] == "POST"
        assert normalized["id"] == "id_example"
        assert normalized["orderId"] == "1"
        assert normalized["depositAmount"] == 1.0
        assert normalized["autoCompoundPlan"] == "NONE"

        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(SubscribeDualInvestmentProductsResponse)

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

        assert response.data() == expected

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

        params = {
            "id": "id_example",
            "order_id": "1",
            "deposit_amount": 1.0,
            "auto_compound_plan": "NONE",
            "recv_window": 5000,
        }

        expected_response = {
            "positionId": 10208824,
            "investCoin": "BNB",
            "exercisedCoin": "USDT",
            "subscriptionAmount": "0.002",
            "duration": 4,
            "autoCompoundPlan": "STANDARD",
            "strikePrice": "380",
            "settleDate": 1709020800000,
            "purchaseStatus": "PURCHASE_SUCCESS",
            "apr": "0.7397",
            "orderId": 8259117597,
            "purchaseTime": 1708677583874,
            "optionType": "CALL",
        }
        mock_get_signature.return_value = "mocked_signature"
        self.set_mock_response(expected_response)

        response = self.client.subscribe_dual_investment_products(**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/v1/dci/product/subscribe" in request_kwargs["url"]
        assert request_kwargs["method"] == "POST"

        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(SubscribeDualInvestmentProductsResponse)

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

        assert response.data() == expected

    def test_subscribe_dual_investment_products_missing_required_param_id(self):
        """Test that subscribe_dual_investment_products() raises RequiredError when 'id' is missing."""
        params = {
            "id": "id_example",
            "order_id": "1",
            "deposit_amount": 1.0,
            "auto_compound_plan": "NONE",
        }
        params["id"] = None

        with pytest.raises(RequiredError, match="Missing required parameter 'id'"):
            self.client.subscribe_dual_investment_products(**params)

    def test_subscribe_dual_investment_products_missing_required_param_order_id(self):
        """Test that subscribe_dual_investment_products() raises RequiredError when 'order_id' is missing."""
        params = {
            "id": "id_example",
            "order_id": "1",
            "deposit_amount": 1.0,
            "auto_compound_plan": "NONE",
        }
        params["order_id"] = None

        with pytest.raises(
            RequiredError, match="Missing required parameter 'order_id'"
        ):
            self.client.subscribe_dual_investment_products(**params)

    def test_subscribe_dual_investment_products_missing_required_param_deposit_amount(
        self,
    ):
        """Test that subscribe_dual_investment_products() raises RequiredError when 'deposit_amount' is missing."""
        params = {
            "id": "id_example",
            "order_id": "1",
            "deposit_amount": 1.0,
            "auto_compound_plan": "NONE",
        }
        params["deposit_amount"] = None

        with pytest.raises(
            RequiredError, match="Missing required parameter 'deposit_amount'"
        ):
            self.client.subscribe_dual_investment_products(**params)

    def test_subscribe_dual_investment_products_missing_required_param_auto_compound_plan(
        self,
    ):
        """Test that subscribe_dual_investment_products() raises RequiredError when 'auto_compound_plan' is missing."""
        params = {
            "id": "id_example",
            "order_id": "1",
            "deposit_amount": 1.0,
            "auto_compound_plan": "NONE",
        }
        params["auto_compound_plan"] = None

        with pytest.raises(
            RequiredError, match="Missing required parameter 'auto_compound_plan'"
        ):
            self.client.subscribe_dual_investment_products(**params)

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

        params = {
            "id": "id_example",
            "order_id": "1",
            "deposit_amount": 1.0,
            "auto_compound_plan": "NONE",
        }

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

        with pytest.raises(Exception, match="ResponseError"):
            self.client.subscribe_dual_investment_products(**params)
