# app/tests/unit/market_data/acquisition/validation/test_values.py

from __future__ import annotations

from dataclasses import replace

import pytest

from src.market_data.acquisition.adapters.dzengi.models import (
    DzengiExchangeInfoPayload,
    DzengiExchangeInfoResponse,
    DzengiExchangeInfoSymbol,
    DzengiLotSizeFilter,
    DzengiMinNotionalFilter,
    DzengiRateLimit,
    DzengiUnknownFilter,
)
from src.market_data.acquisition.exceptions import (
    InstrumentReferenceValueError,
)
from src.market_data.acquisition.validation.values import (
    validate_exchange_info_values,
)


def _valid_symbol() -> DzengiExchangeInfoSymbol:
    return DzengiExchangeInfoSymbol(
        symbol="ETH/EUR_LEVERAGE",
        name="ETH/EUR",
        status="TRADING",
        asset_type="CRYPTOCURRENCY",
        base_asset="ETH",
        base_asset_precision=3,
        quote_asset="EUR",
        quote_asset_id="EUR_LEVERAGE",
        quote_precision=3,
        order_types=("LIMIT", "MARKET", "STOP"),
        filters=(
            DzengiLotSizeFilter(
                filter_type="LOT_SIZE",
                min_qty="0.001",
                max_qty="1000",
                step_size="0.001",
            ),
            DzengiMinNotionalFilter(
                filter_type="MIN_NOTIONAL",
                min_notional="2",
            ),
        ),
        market_modes=("REGULAR",),
        market_type="LEVERAGE",
        country="",
        sector="",
        industry="",
        trading_hours="UTC; Mon - 21:00, 21:05 -",
        tick_size=0.01,
        tick_value=18.3415,
        trading_fee=0.06,
        exchange_fee=None,
        long_rate=-0.01,
        short_rate=0.01,
        swap_charge_interval=480,
        min_sl_gap=0,
        max_sl_gap=50.0,
        min_tp_gap=0,
        max_tp_gap=50.0,
    )


def _valid_response(
    *,
    symbol: DzengiExchangeInfoSymbol | None = None,
    rate_limits: tuple[DzengiRateLimit, ...] = (),
    exchange_filters: tuple[DzengiUnknownFilter, ...] = (),
) -> DzengiExchangeInfoResponse:
    return DzengiExchangeInfoResponse(
        status="OK",
        correlation_id="2",
        payload=DzengiExchangeInfoPayload(
            timezone="UTC",
            server_time=1783537921471,
            rate_limits=rate_limits,
            exchange_filters=exchange_filters,
            symbols=(symbol or _valid_symbol(),),
        ),
    )


def test_validate_complete_exchange_info_values() -> None:
    response = _valid_response(
        rate_limits=(
            DzengiRateLimit(
                interval="MINUTE",
                interval_num=1,
                limit=1200,
                rate_limit_type="REQUEST_WEIGHT",
            ),
        ),
    )

    assert validate_exchange_info_values(response) is None


@pytest.mark.parametrize(
    "field",
    [
        "symbol",
        "name",
        "status",
        "base_asset",
        "quote_asset",
        "market_type",
    ],
)
def test_reject_empty_required_symbol_string(field: str) -> None:
    symbol = replace(
        _valid_symbol(),
        **{field: "   "},
    )

    with pytest.raises(
        InstrumentReferenceValueError,
        match="не должен быть пустым",
    ):
        validate_exchange_info_values(
            _valid_response(symbol=symbol)
        )


def test_reject_empty_order_type() -> None:
    symbol = replace(
        _valid_symbol(),
        order_types=("LIMIT", " "),
    )

    with pytest.raises(
        InstrumentReferenceValueError,
        match=r"orderTypes\[1\] не должен быть пустым",
    ):
        validate_exchange_info_values(
            _valid_response(symbol=symbol)
        )


def test_reject_empty_market_mode() -> None:
    symbol = replace(
        _valid_symbol(),
        market_modes=("REGULAR", ""),
    )

    with pytest.raises(
        InstrumentReferenceValueError,
        match=r"marketModes\[1\] не должен быть пустым",
    ):
        validate_exchange_info_values(
            _valid_response(symbol=symbol)
        )


@pytest.mark.parametrize(
    "field",
    [
        "base_asset_precision",
        "quote_precision",
        "swap_charge_interval",
    ],
)
def test_reject_negative_non_negative_integer_field(field: str) -> None:
    symbol = replace(
        _valid_symbol(),
        **{field: -1},
    )

    with pytest.raises(
        InstrumentReferenceValueError,
        match="должно быть больше или равно нулю",
    ):
        validate_exchange_info_values(
            _valid_response(symbol=symbol)
        )


@pytest.mark.parametrize(
    "tick_size",
    [
        0,
        -0.01,
    ],
)
def test_reject_non_positive_tick_size(tick_size: float) -> None:
    symbol = replace(
        _valid_symbol(),
        tick_size=tick_size,
    )

    with pytest.raises(
        InstrumentReferenceValueError,
        match=r"tickSize должно быть больше нуля",
    ):
        validate_exchange_info_values(
            _valid_response(symbol=symbol)
        )


@pytest.mark.parametrize(
    "tick_size",
    [
        float("nan"),
        float("inf"),
        float("-inf"),
    ],
)
def test_reject_non_finite_tick_size(tick_size: float) -> None:
    symbol = replace(
        _valid_symbol(),
        tick_size=tick_size,
    )

    with pytest.raises(
        InstrumentReferenceValueError,
        match=r"tickSize должно быть конечным числом",
    ):
        validate_exchange_info_values(
            _valid_response(symbol=symbol)
        )


def test_reject_non_numeric_lot_size_value() -> None:
    symbol = replace(
        _valid_symbol(),
        filters=(
            DzengiLotSizeFilter(
                filter_type="LOT_SIZE",
                min_qty="not-a-number",
                max_qty="1000",
                step_size="0.001",
            ),
        ),
    )

    with pytest.raises(
        InstrumentReferenceValueError,
        match=r"minQty должно быть корректным числом",
    ):
        validate_exchange_info_values(
            _valid_response(symbol=symbol)
        )


@pytest.mark.parametrize(
    ("field", "value"),
    [
        ("min_qty", "0"),
        ("max_qty", 0),
        ("step_size", -1),
    ],
)
def test_reject_non_positive_lot_size_values(
    field: str,
    value: str | int,
) -> None:
    lot_size = DzengiLotSizeFilter(
        filter_type="LOT_SIZE",
        min_qty="0.001",
        max_qty="1000",
        step_size="0.001",
    )

    lot_size = replace(
        lot_size,
        **{field: value},
    )

    symbol = replace(
        _valid_symbol(),
        filters=(lot_size,),
    )

    with pytest.raises(
        InstrumentReferenceValueError,
        match="должно быть больше нуля",
    ):
        validate_exchange_info_values(
            _valid_response(symbol=symbol)
        )


def test_reject_min_qty_greater_than_max_qty() -> None:
    symbol = replace(
        _valid_symbol(),
        filters=(
            DzengiLotSizeFilter(
                filter_type="LOT_SIZE",
                min_qty="10",
                max_qty="1",
                step_size="0.1",
            ),
        ),
    )

    with pytest.raises(
        InstrumentReferenceValueError,
        match="minQty не должно превышать",
    ):
        validate_exchange_info_values(
            _valid_response(symbol=symbol)
        )


def test_reject_negative_min_notional() -> None:
    symbol = replace(
        _valid_symbol(),
        filters=(
            DzengiMinNotionalFilter(
                filter_type="MIN_NOTIONAL",
                min_notional="-1",
            ),
        ),
    )

    with pytest.raises(
        InstrumentReferenceValueError,
        match=r"minNotional должно быть больше или равно нулю",
    ):
        validate_exchange_info_values(
            _valid_response(symbol=symbol)
        )


def test_accept_zero_min_notional() -> None:
    symbol = replace(
        _valid_symbol(),
        filters=(
            DzengiMinNotionalFilter(
                filter_type="MIN_NOTIONAL",
                min_notional="0",
            ),
        ),
    )

    assert (
        validate_exchange_info_values(
            _valid_response(symbol=symbol)
        )
        is None
    )


def test_accept_negative_long_and_short_rates() -> None:
    symbol = replace(
        _valid_symbol(),
        long_rate=-0.15,
        short_rate=-0.25,
    )

    assert (
        validate_exchange_info_values(
            _valid_response(symbol=symbol)
        )
        is None
    )


def test_accept_zero_optional_numeric_values() -> None:
    symbol = replace(
        _valid_symbol(),
        tick_value=0,
        trading_fee=0,
        exchange_fee=0,
        min_sl_gap=0,
        max_sl_gap=0,
        min_tp_gap=0,
        max_tp_gap=0,
    )

    assert (
        validate_exchange_info_values(
            _valid_response(symbol=symbol)
        )
        is None
    )


@pytest.mark.parametrize(
    ("interval_num", "limit"),
    [
        (0, 1200),
        (1, 0),
        (-1, 1200),
        (1, -100),
    ],
)
def test_reject_invalid_rate_limit_values(
    interval_num: int,
    limit: int,
) -> None:
    response = _valid_response(
        rate_limits=(
            DzengiRateLimit(
                interval="MINUTE",
                interval_num=interval_num,
                limit=limit,
                rate_limit_type="REQUEST_WEIGHT",
            ),
        ),
    )

    with pytest.raises(
        InstrumentReferenceValueError,
        match="должно быть больше нуля",
    ):
        validate_exchange_info_values(response)


def test_reject_empty_rate_limit_string() -> None:
    response = _valid_response(
        rate_limits=(
            DzengiRateLimit(
                interval=" ",
                interval_num=1,
                limit=1200,
                rate_limit_type="REQUEST_WEIGHT",
            ),
        ),
    )

    with pytest.raises(
        InstrumentReferenceValueError,
        match=r"interval не должен быть пустым",
    ):
        validate_exchange_info_values(response)


def test_reject_empty_unknown_instrument_filter_type() -> None:
    symbol = replace(
        _valid_symbol(),
        filters=(
            DzengiUnknownFilter(
                filter_type=" ",
                fields=(("enabled", True),),
            ),
        ),
    )

    with pytest.raises(
        InstrumentReferenceValueError,
        match=r"filterType не должен быть пустым",
    ):
        validate_exchange_info_values(
            _valid_response(symbol=symbol)
        )


def test_accept_empty_global_exchange_filter_type() -> None:
    response = _valid_response(
        exchange_filters=(
            DzengiUnknownFilter(
                filter_type="",
                fields=(("enabled", True),),
            ),
        ),
    )

    assert validate_exchange_info_values(response) is None


def test_reject_whitespace_global_exchange_filter_type() -> None:
    response = _valid_response(
        exchange_filters=(
            DzengiUnknownFilter(
                filter_type="   ",
                fields=(("enabled", True),),
            ),
        ),
    )

    with pytest.raises(
        InstrumentReferenceValueError,
        match=r"filterType не должен состоять только из пробелов",
    ):
        validate_exchange_info_values(response)