import re
import time
import unittest
import hmac
import hashlib
import json
from pydantic import BaseModel
import requests

from base64 import b64decode, b64encode
from collections import OrderedDict
from Crypto.Hash import SHA256
from Crypto.PublicKey import RSA, ECC
from Crypto.Signature import pkcs1_15, eddsa
from enum import Enum
from types import SimpleNamespace
from typing import ClassVar, List, Optional, Union
from unittest.mock import Mock, patch
from urllib.parse import urlencode

from binance_common.constants import TimeUnit
from binance_common.configuration import ConfigurationRestAPI
from binance_common.errors import (
    BadRequestError,
    UnauthorizedError,
    ForbiddenError,
    NotFoundError,
    RateLimitBanError,
    TooManyRequestsError,
    ServerError,
    NetworkError,
)
from binance_common.models import RateLimit, WebsocketApiOptions
from binance_common.utils import (
    clean_none_value,
    encoded_string,
    get_signature,
    get_timestamp,
    get_uuid,
    get_validator_field_map,
    hmac_hashing,
    is_one_of_model,
    make_serializable,
    normalize_query_values,
    parse_proxies,
    parse_user_event,
    parse_rate_limit_headers,
    parse_ws_rate_limit_headers,
    resolve_model_from_event,
    send_request,
    should_retry_request,
    transform_query,
    validate_time_unit,
    websocket_api_signature,
    ws_api_payload,
    ws_streams_placeholder,
)
from binance_common.headers import sanitize_header_value, parse_custom_headers
from binance_common.signature import Signers


class TestHmacHashing(unittest.TestCase):
    def test_valid_hmac_hashing(self):
        api_secret = "mysecretkey"
        payload = "testpayload"
        expected_hash = hmac.new(
            api_secret.encode("utf-8"), payload.encode("utf-8"), hashlib.sha256
        ).hexdigest()
        self.assertEqual(hmac_hashing(api_secret, payload), expected_hash)

    def test_empty_payload(self):
        api_secret = "mysecretkey"
        payload = ""
        expected_hash = hmac.new(
            api_secret.encode("utf-8"), payload.encode("utf-8"), hashlib.sha256
        ).hexdigest()
        self.assertEqual(hmac_hashing(api_secret, payload), expected_hash)

    def test_empty_api_secret(self):
        api_secret = ""
        payload = "testpayload"
        expected_hash = hmac.new(
            api_secret.encode("utf-8"), payload.encode("utf-8"), hashlib.sha256
        ).hexdigest()
        self.assertEqual(hmac_hashing(api_secret, payload), expected_hash)

    def test_both_empty(self):
        api_secret = ""
        payload = ""
        expected_hash = hmac.new(
            api_secret.encode("utf-8"), payload.encode("utf-8"), hashlib.sha256
        ).hexdigest()
        self.assertEqual(hmac_hashing(api_secret, payload), expected_hash)

    def test_different_inputs(self):
        api_secret_1 = "key1"
        api_secret_2 = "key2"
        payload_1 = "data1"
        payload_2 = "data2"
        self.assertNotEqual(
            hmac_hashing(api_secret_1, payload_1), hmac_hashing(api_secret_2, payload_2)
        )
        self.assertNotEqual(
            hmac_hashing(api_secret_1, payload_1), hmac_hashing(api_secret_1, payload_2)
        )
        self.assertNotEqual(
            hmac_hashing(api_secret_1, payload_1), hmac_hashing(api_secret_2, payload_1)
        )


class TestRsaSignature(unittest.TestCase):
    def test_valid_rsa_signature(self):
        private_key = RSA.generate(2048)
        private_pem = private_key.export_key().decode("utf-8")
        payload = "testpayload"

        config = Mock(private_key=private_pem, api_secret=None)
        signer = Signers.get_signer(private_pem)

        signature = get_signature(config, payload, signer=signer)

        public_key = private_key.publickey()
        digest = SHA256.new(payload.encode("utf-8"))
        decoded_signature = b64decode(signature)

        try:
            pkcs1_15.new(public_key).verify(digest, decoded_signature)
            verified = True
        except (ValueError, TypeError):
            verified = False

        self.assertTrue(verified)

    def test_invalid_rsa_signature(self):
        private_key = RSA.generate(2048)
        another_private_key = RSA.generate(2048)
        private_pem = private_key.export_key().decode("utf-8")
        payload = "testpayload"

        config = Mock(private_key=private_pem, api_secret=None)
        signer = Signers.get_signer(private_pem)

        signature = get_signature(config, payload, signer=signer)

        wrong_public_key = another_private_key.publickey()
        digest = SHA256.new(payload.encode("utf-8"))
        decoded_signature = b64decode(signature)

        with self.assertRaises(ValueError):
            pkcs1_15.new(wrong_public_key).verify(digest, decoded_signature)


class TestEd25519Signature(unittest.TestCase):
    def test_valid_ed25519_signature(self):
        private_key = ECC.generate(curve="ed25519")
        private_pem = private_key.export_key(format="PEM")
        payload = "testpayload"

        config = Mock(private_key=private_pem, api_secret=None)
        signer = Signers.get_signer(private_pem)

        signature = get_signature(config, payload, signer)

        public_key = private_key.public_key()
        decoded_signature = b64decode(signature)
        verifier = eddsa.new(public_key, "rfc8032")

        try:
            verifier.verify(payload.encode("utf-8"), decoded_signature)
            verified = True
        except ValueError:
            verified = False

        self.assertTrue(verified)

    def test_invalid_ed25519_signature(self):
        private_key = ECC.generate(curve="ed25519")
        another_private_key = ECC.generate(curve="ed25519")
        private_pem = private_key.export_key(format="PEM")
        payload = "testpayload"

        config = Mock(private_key=private_pem, api_secret=None)
        signer = Signers.get_signer(private_pem)

        signature = get_signature(config, payload, signer)

        public_key = another_private_key.public_key()
        decoded_signature = b64decode(signature)
        verifier = eddsa.new(public_key, "rfc8032")

        with self.assertRaises(ValueError):
            verifier.verify(payload.encode("utf-8"), decoded_signature)


class TestCleanNoneValue(unittest.TestCase):
    def test_remove_none_values(self):
        input_dict = {"a": 1, "b": None, "c": 3, "d": None}
        expected_output = {"a": 1, "c": 3}
        self.assertEqual(clean_none_value(input_dict), expected_output)

    def test_all_none_values(self):
        input_dict = {"a": None, "b": None, "c": None}
        expected_output = {}
        self.assertEqual(clean_none_value(input_dict), expected_output)

    def test_no_none_values(self):
        input_dict = {"a": 1, "b": 2, "c": 3}
        expected_output = {"a": 1, "b": 2, "c": 3}
        self.assertEqual(clean_none_value(input_dict), expected_output)

    def test_empty_dictionary(self):
        input_dict = {}
        expected_output = {}
        self.assertEqual(clean_none_value(input_dict), expected_output)

    def test_mixed_types(self):
        input_dict = {"a": 0, "b": False, "c": None, "d": "", "e": [], "f": {}}
        expected_output = {"a": 0, "b": False, "d": ""}
        self.assertEqual(clean_none_value(input_dict), expected_output)

    def test_clean_none_value_with_class(self):
        class Dummy:
            def __init__(self):
                self.a = 0
                self.b = False
                self.c = ""
                self.d = None
                self.e = []
                self.f = {}

        obj = Dummy()
        cleaned = clean_none_value(obj)

        expected = {
            "a": 0,
            "b": False,
            "c": ""
        }

        assert cleaned == expected

    def test_clean_none_value_with_array_of_dicts(self):
        input_list = [
            {"a": 0, "b": False, "c": None, "d": "", "e": [], "f": {}},
        ]
        expected_output = [
            {"a": 0, "b": False, "d": ""}
        ]

        assert clean_none_value(input_list) == expected_output

    def test_clean_none_value_with_enum(self):
        class BasisPeriodEnum(Enum):
            TEST_A = "TEST_A"
            TEST_B = "TEST_B"

        input_list = [
            {"a": 0, "b": False, "c": BasisPeriodEnum.TEST_A, "d": "", "e": [], "f": {}},
        ]
        expected_output = [
            {"a": 0, "b": False, "c": "TEST_A", "d": ""}
        ]

        assert clean_none_value(input_list) == expected_output


class TestGetTimestamp(unittest.TestCase):
    def test_timestamp_is_integer(self):
        timestamp = get_timestamp()
        self.assertIsInstance(timestamp, int)

    def test_timestamp_is_current(self):
        lower_bound = int(time.time() * 1000)
        timestamp = get_timestamp()
        upper_bound = int(time.time() * 1000)

        self.assertGreaterEqual(timestamp, lower_bound)
        self.assertLessEqual(timestamp, upper_bound)

    def test_timestamps_are_increasing(self):
        timestamp1 = get_timestamp()
        time.sleep(0.01)
        timestamp2 = get_timestamp()

        self.assertGreater(timestamp2, timestamp1)


class Dummy:
    def __init__(self, a, b):
        self.a = a
        self.b = b


class TestMakeSerializable(unittest.TestCase):

    def test_list_of_objects(self):
        obj1 = Dummy(1, 2)
        obj2 = Dummy(3, 4)
        result = make_serializable([obj1, obj2])
        self.assertEqual(result, [obj1.__dict__, obj2.__dict__])

    def test_boolean(self):
        self.assertEqual(make_serializable(True), "true")
        self.assertEqual(make_serializable(False), "false")

    def test_float(self):
        self.assertEqual(make_serializable(3.14), "3.14")
        self.assertEqual(make_serializable(-2.5), "-2.5")

    def test_enum(self):
        class Color(Enum):
            RED = "red"
            BLUE = "blue"
        self.assertEqual(make_serializable(Color.RED), "red")
        self.assertEqual(make_serializable(Color.BLUE), "blue")

    def test_object_with_dict(self):
        obj = Dummy(5, 6)
        result = make_serializable(obj)
        self.assertEqual(result, obj.__dict__)

    def test_int_and_string(self):
        self.assertEqual(make_serializable(42), 42)
        self.assertEqual(make_serializable("hello"), "hello")

    def test_list_of_mixed(self):
        obj = Dummy(1, 2)
        lst = [obj, 10, "test", True]
        result = make_serializable(lst)
        expected = [obj.__dict__, 10, "test", True]
        self.assertEqual(result, expected)


class TestTransformQuery(unittest.TestCase):

    def test_transform_dict(self):
        data = {
            "first_name": "John",
            "last_name": "Doe",
            "age": 30
        }
        expected = {
            "firstName": "John",
            "lastName": "Doe",
            "age": 30
        }
        self.assertEqual(transform_query(data), expected)

    def test_transform_nested_dict(self):
        data = {
            "user_info": {
                "first_name": "Alice",
                "last_name": "Smith"
            },
            "is_active": True
        }
        expected = {
            "userInfo": {
                "firstName": "Alice",
                "lastName": "Smith"
            },
            "isActive": "true"
        }
        self.assertEqual(transform_query(data), expected)

    def test_transform_list_of_dicts(self):
        data = [
            {"first_name": "John"},
            {"first_name": "Jane"}
        ]
        expected = [
            {"firstName": "John"},
            {"firstName": "Jane"}
        ]
        self.assertEqual(transform_query(data), expected)

    def test_transform_mixed_list(self):
        data = [1, "two", {"three_four": 4}]
        expected = [1, "two", {"threeFour": 4}]
        self.assertEqual(transform_query(data), expected)

    def test_transform_primitive_types(self):
        self.assertEqual(transform_query(42), 42)
        self.assertEqual(transform_query(3.14), "3.14")
        self.assertEqual(transform_query(True), "true")
        self.assertEqual(transform_query("hello"), "hello")

    def test_empty_structures(self):
        self.assertEqual(transform_query({}), {})
        self.assertEqual(transform_query([]), [])


class TestEncodedString(unittest.TestCase):
    def test_encode_regular_string(self):
        query = {"message": "hello world"}
        expected_output = urlencode(query, doseq=True)
        self.assertEqual(encoded_string(query), expected_output)

    def test_encode_string_with_special_characters(self):
        query = {"email": "test@example.com"}
        expected_output = urlencode(query, doseq=True)
        self.assertEqual(encoded_string(query), expected_output)

    def test_encode_empty_string(self):
        query = {}
        expected_output = urlencode(query, doseq=True)
        self.assertEqual(encoded_string(query), expected_output)

    def test_encode_string_with_multiple_at_symbols(self):
        query = {"user": "test@example.com", "admin": "admin@domain.com"}
        expected_output = urlencode(query, doseq=True)
        self.assertEqual(encoded_string(query), expected_output)

    def test_encode_string_with_array(self):
        query = {"ids": [1, 2, 3]}
        expected_output = "ids=%5B1%2C2%2C3%5D"
        self.assertEqual(encoded_string(query), expected_output)

    def test_encode_with_class_instances_in_list(self):
        class Dummy:
            def __init__(self, a, b):
                self.a = a
                self.b = b

        dummy_instance = Dummy("value1", 123)
        query = {"items": [dummy_instance]}

        expected_value = urlencode({
            "items": json.dumps([{"a": "value1", "b": 123}], separators=(",", ":"))
        }, doseq=True)

        self.assertEqual(encoded_string(query), expected_value)

    def test_encode_with_snake_case(self):
        query = {"user_email": "test@example.com", "admin_email": "admin@domain.com"}

        expected_value = urlencode({
            "userEmail": "test@example.com",
            "adminEmail": "admin@domain.com"
        }, doseq=True)

        self.assertEqual(encoded_string(query), expected_value)


class TestIsOneOfModel(unittest.TestCase):

    class DummyOneOf(BaseModel):
        a: int
        b: str
        actual_instance_must_validate_oneof: ClassVar[bool] = True
        def is_oneof_model() -> bool:
            return True

    class DummyNotOneOf(BaseModel):
        x: float

    def test_is_one_of_model_valid(self):
        self.assertTrue(is_one_of_model(self.DummyOneOf))

    def test_is_one_of_model_invalid(self):
        self.assertFalse(is_one_of_model(self.DummyNotOneOf))

class TestGetUUID(unittest.TestCase):
    def test_uuid_is_string(self):
        generated_uuid = get_uuid()
        self.assertIsInstance(generated_uuid, str)

    def test_uuid_is_valid_format(self):
        generated_uuid = get_uuid()
        uuid_regex = re.compile(
            r"^[a-f0-9]{8}-[a-f0-9]{4}-[1-5][a-f0-9]{3}-[89ab][a-f0-9]{3}-[a-f0-9]{12}$",
            re.IGNORECASE,
        )
        self.assertRegex(generated_uuid, uuid_regex)

    def test_uuid_is_unique(self):
        uuid1 = get_uuid()
        uuid2 = get_uuid()
        self.assertNotEqual(uuid1, uuid2)


class TestValidateTimeUnit(unittest.TestCase):
    def test_valid_time_units(self):
        valid_units = [
            TimeUnit.MICROSECOND.value,
            TimeUnit.microsecond.value,
            TimeUnit.MILLISECOND.value,
            TimeUnit.millisecond.value,
        ]
        for unit in valid_units:
            with self.subTest(unit=unit):
                self.assertEqual(validate_time_unit(unit), unit)

    def test_invalid_time_unit(self):
        with self.assertRaises(ValueError):
            validate_time_unit("weeks")

        with self.assertRaises(ValueError):
            validate_time_unit("random_unit")

    def test_none_time_unit(self):
        self.assertIsNone(validate_time_unit(None))


class TestGetSignature(unittest.TestCase):
    def setUp(self):
        self.hmac_secret = "mysecret"

        self.hmac_config = Mock(
            api_secret=self.hmac_secret, private_key=None, private_key_algo=None
        )

        self.rsa_key = RSA.generate(2048)
        self.rsa_config = Mock(
            api_secret=None,
            private_key=self.rsa_key.export_key().decode("utf-8"),
            private_key_passphrase=None,
        )

        self.ed25519_key = ECC.generate(curve="ed25519")
        self.ed25519_config = Mock(
            api_secret=None,
            private_key=self.ed25519_key.export_key(format="PEM"),
            private_key_passphrase=None,
        )

        self.invalid_algo_config = Mock(
            api_secret=None,
            private_key="some_private_key",
            private_key_passphrase=None,
        )

        self.missing_keys_config = Mock(
            api_secret=None, private_key=None
        )

    def test_hmac_signature(self):
        payload = {"key": "value"}
        payload_str = json.dumps(payload, separators=(",", ":"))
        expected_signature = hmac_hashing(self.hmac_secret, payload_str)
        self.assertEqual(
            get_signature(self.hmac_config, payload_str), expected_signature
        )

    def test_rsa_signature(self):
        payload = {"key": "value"}
        payload_str = json.dumps(payload, separators=(",", ":"))
        signer = Signers.get_signer(self.rsa_key.export_key().decode("utf-8"))
        expected_signature = b64encode(
            signer.sign(SHA256.new(payload_str.encode("utf-8")))
        ).decode("utf-8")
        self.assertEqual(
            get_signature(self.rsa_config, payload_str, signer), expected_signature
        )

    def test_ed25519_signature(self):
        payload = {"key": "value"}
        payload_str = json.dumps(payload, separators=(",", ":"))
        signer = Signers.get_signer(self.ed25519_key.export_key(format="PEM"))
        expected_signature = b64encode(
            signer.sign(payload_str.encode("utf-8"))
        ).decode("utf-8")
        self.assertEqual(
            get_signature(self.ed25519_config, payload_str, signer), expected_signature
        )

    def test_invalid_private_key_algo(self):
        with self.assertRaises(ValueError) as context:
            Signers.get_signer(self.invalid_algo_config.private_key)
        self.assertEqual(
            str(context.exception),
            "Unsupported or invalid private key format. Private key must be either 'RSA' or 'ED25519'",
        )

    def test_missing_keys(self):
        payload = {"key": "value"}
        payload_str = json.dumps(payload, separators=(",", ":"))
        with self.assertRaises(ValueError) as context:
            get_signature(self.missing_keys_config, payload_str)
        self.assertEqual(
            str(context.exception),
            "Either 'api_secret' or 'private_key' must be provided for signed requests.",
        )


class TestShouldRetryRequest(unittest.TestCase):
    def test_retry_on_retriable_status_code(self):
        error = Mock(response=Mock(status_code=500))
        self.assertTrue(should_retry_request(error, method="GET", retries_left=3))

        error.response.status_code = 502
        self.assertTrue(should_retry_request(error, method="DELETE", retries_left=2))

        error.response.status_code = 503
        self.assertTrue(should_retry_request(error, method="GET", retries_left=1))

        error.response.status_code = 504
        self.assertTrue(should_retry_request(error, method="DELETE", retries_left=1))

    def test_no_retry_on_non_retriable_status_code(self):
        error = Mock(response=Mock(status_code=400))
        self.assertFalse(should_retry_request(error, method="GET", retries_left=3))

        error.response.status_code = 401
        self.assertFalse(should_retry_request(error, method="DELETE", retries_left=2))

        error.response.status_code = 404
        self.assertFalse(should_retry_request(error, method="GET", retries_left=1))

        error.response.status_code = 200
        self.assertFalse(should_retry_request(error, method="DELETE", retries_left=1))

    def test_no_retry_on_non_retriable_method(self):
        error = Mock(response=Mock(status_code=500))
        self.assertFalse(should_retry_request(error, method="POST", retries_left=3))
        self.assertFalse(should_retry_request(error, method="PUT", retries_left=3))

    def test_no_retry_when_no_retries_left(self):
        error = Mock(response=Mock(status_code=500))
        self.assertFalse(should_retry_request(error, method="GET", retries_left=0))

        error.response.status_code = 503
        self.assertFalse(should_retry_request(error, method="DELETE", retries_left=-1))

    def test_retry_on_no_response_attribute(self):
        error = Mock(response=None)
        self.assertTrue(should_retry_request(error, method="GET", retries_left=3))

    def test_no_retry_when_method_is_none(self):
        error = Mock(response=Mock(status_code=500))
        self.assertFalse(should_retry_request(error, method=None, retries_left=3))

    def test_no_retry_when_retries_left_is_none(self):
        error = Mock(response=Mock(status_code=500))
        self.assertFalse(should_retry_request(error, method="GET", retries_left=None))


class TestSendRequest(unittest.TestCase):
    def setUp(self):
        self.session = Mock(spec=requests.Session)
        self.configuration = ConfigurationRestAPI(
            api_key="test_key",
            api_secret="test_secret",
            base_path="https://api.test.com",
            retries=3,
            backoff=1,
            timeout=5000,
            proxy=None,
            compression=False,
            https_agent=None,
            keep_alive=False,
        )
        self.method = "GET"
        self.path = "/test"
        self.url = f"{self.configuration.base_path}{self.path}"

    @patch("binance_common.utils.encoded_string", side_effect=lambda x: x)
    @patch("binance_common.utils.clean_none_value", side_effect=lambda x: x)
    @patch("binance_common.utils.parse_rate_limit_headers", return_value=[])
    @patch("binance_common.utils.get_timestamp", return_value=1234567890)
    @patch("binance_common.utils.get_signature", return_value="signed_signature")
    def test_signed_request(
        self,
        mock_get_signature,
        mock_get_timestamp,
        mock_parse_rate_limits,
        mock_clean_none,
        mock_encoded_string,
    ):
        """Test signed request includes signature and timestamp."""
        mock_response = Mock(status_code=200)
        mock_response.json.return_value = {"success": True}
        mock_response.text = json.dumps({"success": True})

        self.session.request.return_value = mock_response

        response = send_request(
            self.session,
            self.configuration,
            self.method,
            self.path,
            payload={"param": "value"},
            is_signed=True,
        )

        self.assertEqual(response.data(), {"success": True})
        self.assertEqual(response.status, 200)
        self.assertEqual(response.headers, mock_response.headers)
        self.assertEqual(response.status, 200)

        self.session.request.assert_called_once()
        _, kwargs = self.session.request.call_args

        self.assertIn("timestamp", kwargs["params"])
        self.assertIn("signature", kwargs["params"])
        self.assertEqual(kwargs["params"]["timestamp"], 1234567890)
        self.assertEqual(kwargs["params"]["signature"], "signed_signature")

    @patch("binance_common.utils.encoded_string", side_effect=lambda x: x)
    @patch("binance_common.utils.clean_none_value", side_effect=lambda x: x)
    @patch("binance_common.utils.parse_rate_limit_headers", return_value=[])
    def test_successful_request(
        self, mock_parse_rate_limits, mock_clean_none, mock_encoded_string
    ):
        """Test successful request (200 response)."""
        mock_response = Mock(status_code=200)
        mock_response.json.return_value = {"success": True}
        mock_response.text = json.dumps({"success": True})

        self.session.request.return_value = mock_response

        response = send_request(
            self.session, self.configuration, self.method, self.path, {"param": "value"}
        )

        self.assertEqual(response.data(), {"success": True})
        self.assertEqual(response.status, 200)
        self.assertEqual(response.headers, mock_response.headers)
        self.assertEqual(response.rate_limits, [])

        headers = self.configuration.base_headers
        headers["Connection"] = "close"

        self.session.request.assert_called_once_with(
            method=self.method,
            url=self.url,
            params={"param": "value"},
            headers=headers,
            timeout=5,
            proxies=None,
            data=None,
        )

    @patch("binance_common.utils.encoded_string", side_effect=lambda x: x)
    @patch("binance_common.utils.clean_none_value", side_effect=lambda x: x)
    @patch("binance_common.utils.parse_rate_limit_headers", return_value=[])
    def test_successful_request_empty_array_response(
        self, mock_parse_rate_limits, mock_clean_none, mock_encoded_string
    ):
        """Test successful request (200 response)."""
        mock_response = Mock(status_code=200)
        mock_response.json.return_value = []
        mock_response.text = json.dumps([])

        self.session.request.return_value = mock_response

        response = send_request(
            self.session, self.configuration, self.method, self.path, {"param": "value"}
        )

        self.assertEqual(response.data(), [])
        self.assertEqual(response.status, 200)
        self.assertEqual(response.headers, mock_response.headers)
        self.assertEqual(response.rate_limits, [])

        headers = self.configuration.base_headers
        headers["Connection"] = "close"

        self.session.request.assert_called_once_with(
            method=self.method,
            url=self.url,
            params={"param": "value"},
            headers=headers,
            timeout=5,
            proxies=None,
            data=None,
        )

    @patch("binance_common.utils.encoded_string", side_effect=lambda x: x)
    @patch("binance_common.utils.clean_none_value", side_effect=lambda x: x)
    def test_client_errors(self, mock_clean_none, mock_encoded_string):
        """Test 400-499 errors raising appropriate exceptions."""
        error_cases = {
            400: BadRequestError,
            401: UnauthorizedError,
            403: ForbiddenError,
            404: NotFoundError,
            418: RateLimitBanError,
            429: TooManyRequestsError,
        }

        for status, exception in error_cases.items():
            with self.subTest(status=status):
                mock_response = Mock(
                    status_code=status,
                    headers={"Content-Type": "application/json"}
                )

                mock_response.json.return_value = {"msg": f"Error {status}", "code": 10001}
                self.session.request.return_value = mock_response

                with self.assertRaises(exception) as context:
                    send_request(
                        self.session, self.configuration, self.method, self.path, {}
                    )

                self.assertEqual(context.exception.error_message, f"Error {status}")
                self.assertEqual(context.exception.status_code, 10001)

                mock_response.json.return_value = {}
                self.session.request.return_value = mock_response

                with self.assertRaises(exception) as context:
                    send_request(
                        self.session, self.configuration, self.method, self.path, {}
                    )

                self.assertEqual(
                    context.exception.error_message, exception().error_message
                )
                self.assertIsNone(getattr(context.exception, "status_code", None))

    @patch("binance_common.utils.encoded_string", side_effect=lambda x: x)
    @patch("binance_common.utils.clean_none_value", side_effect=lambda x: x)
    def test_server_errors(self, mock_clean_none, mock_encoded_string):
        """Test 500-599 server errors raising ServerError."""
        for status in [500, 502, 503, 504]:
            with self.subTest(status=status):
                mock_response = Mock(status_code=status)
                mock_response.json.return_value = {"msg": "Server Error"}
                self.session.request.return_value = mock_response

                with self.assertRaises(ServerError) as context:
                    send_request(
                        self.session, self.configuration, self.method, self.path, {}
                    )

                self.assertEqual(str(context.exception), f"Server error: {status}")

    @patch("binance_common.utils.encoded_string", side_effect=lambda x: x)
    @patch("binance_common.utils.clean_none_value", side_effect=lambda x: x)
    @patch(
        "binance_common.utils.should_retry_request",
        side_effect=lambda e, m, r: r > 0
        and (getattr(e, "response", None) is None or e.response.status_code != 500),
    )
    @patch("time.sleep", return_value=None)
    def test_network_error_with_retry(
        self, mock_sleep, mock_should_retry, mock_clean_none, mock_encoded_string
    ):
        """Test request retrying on network errors."""

        mock_response = Mock(status_code=500)
        self.session.request.side_effect = [
            requests.RequestException("Network Error"),
            mock_response,
        ]

        with self.assertRaises(ServerError):
            send_request(self.session, self.configuration, self.method, self.path, {})

    @patch("binance_common.utils.encoded_string", side_effect=lambda x: x)
    @patch("binance_common.utils.clean_none_value", side_effect=lambda x: x)
    @patch("binance_common.utils.should_retry_request", return_value=False)
    def test_network_error_no_retry(
        self, mock_should_retry, mock_clean_none, mock_encoded_string
    ):
        """Test request failure when retry is not allowed."""
        self.session.request.side_effect = requests.RequestException("Network Error")

        with self.assertRaises(NetworkError) as context:
            send_request(self.session, self.configuration, self.method, self.path, {})

        self.assertEqual(str(context.exception), "Network error: Network Error")
        self.assertEqual(mock_should_retry.call_count, 1)

    @patch("binance_common.utils.encoded_string", side_effect=lambda x: x)
    @patch("binance_common.utils.clean_none_value", side_effect=lambda x: x)
    def test_correct_headers_and_proxies(self, mock_clean_none, mock_encoded_string):
        """Ensure correct headers and proxies are used in request."""
        self.configuration.proxy = {
            "protocol": "https",
            "host": "127.0.0.1",
            "port": 8080,
        }
        self.configuration.compression = True

        mock_response = Mock(status_code=200)
        mock_response.json.return_value = {"success": True}
        mock_response.headers = {"x-mbx-used-weight-1m": "10"}
        mock_response.text = json.dumps({"success": True})
        self.session.request.return_value = mock_response

        send_request(self.session, self.configuration, self.method, self.path, {})

        self.session.request.assert_called_once()
        _, kwargs = self.session.request.call_args

        self.assertEqual(kwargs["headers"]["Accept-Encoding"], "gzip, deflate, br")
        self.assertEqual(
            kwargs["proxies"],
            {"http": "https://127.0.0.1:8080", "https": "https://127.0.0.1:8080"},
        )

    @patch("binance_common.utils.encoded_string", side_effect=lambda x: x)
    @patch("binance_common.utils.clean_none_value", side_effect=lambda x: x)
    @patch("binance_common.utils.should_retry_request", return_value=False)
    def test_request_fails_after_retries(
        self, mock_should_retry, mock_clean_none, mock_encoded_string
    ):
        """Ensure request fails after max retries are exhausted."""
        self.configuration.retries = 2
        self.session.request.side_effect = requests.RequestException("Final Failure")

        with self.assertRaises(NetworkError) as context:
            send_request(self.session, self.configuration, self.method, self.path, {})

        self.assertEqual(str(context.exception), "Network error: Final Failure")

    @patch("binance_common.utils.encoded_string", side_effect=lambda x: x)
    @patch("binance_common.utils.clean_none_value", side_effect=lambda x: x)
