# app/tests/unit/market_data/acquisition/adapters/dzengi/test_parser.py

from __future__ import annotations

from types import MappingProxyType

import pytest

from src.market_data.acquisition.adapters.dzengi.models import (
    DzengiLotSizeFilter,
    DzengiMinNotionalFilter,
    DzengiUnknownFilter,
)
from src.market_data.acquisition.adapters.dzengi.parser import (
    parse_exchange_info,
)
from src.market_data.acquisition.exceptions import (
    InstrumentReferenceParseError,
)
from src.market_data.acquisition.validation.schema import (
    ValidatedExchangeInfoDocument,
)


def _validated_document(
    payload: dict[str, object],
    *,
    is_wrapped: bool = False,
    status: object | None = None,
    correlation_id: object | None = None,
) -> ValidatedExchangeInfoDocument:
    return ValidatedExchangeInfoDocument(
        payload=MappingProxyType(payload),
        is_wrapped=is_wrapped,
        status=status,
        correlation_id=correlation_id,
    )


def _complete_symbol() -> dict[str, object]:
    return {
        "symbol": "ETH/EUR_LEVERAGE",
        "name": "ETH/EUR",
        "status": "TRADING",
        "assetType": "CRYPTOCURRENCY",
        "baseAsset": "ETH",
        "baseAssetPrecision": 3,
        "quoteAsset": "EUR",
        "quoteAssetId": "EUR_LEVERAGE",
        "quotePrecision": 3,
        "orderTypes": ["LIMIT", "MARKET", "STOP"],
        "filters": [
            {
                "filterType": "LOT_SIZE",
                "minQty": "0.001",
                "maxQty": "1000",
                "stepSize": "0.001",
            },
            {
                "filterType": "MIN_NOTIONAL",
                "minNotional": "2",
            },
        ],
        "marketModes": ["REGULAR"],
        "marketType": "LEVERAGE",
        "country": "",
        "sector": "",
        "industry": "",
        "tradingHours": "UTC; Mon - 21:00, 21:05 -",
        "tickSize": 0.01,
        "tickValue": 18.3415,
        "tradingFee": 0.06,
        "longRate": -0.01,
        "shortRate": 0.01,
        "swapChargeInterval": 480,
        "minSLGap": 0,
        "maxSLGap": 50.0,
        "minTPGap": 0,
        "maxTPGap": 50.0,
    }


def test_parse_complete_unwrapped_exchange_info() -> None:
    document = _validated_document(
        {
            "timezone": "UTC",
            "serverTime": 1783537921471,
            "rateLimits": [
                {
                    "interval": "MINUTE",
                    "intervalNum": 1,
                    "limit": 1200,
                    "rateLimitType": "REQUEST_WEIGHT",
                }
            ],
            "exchangeFilters": [],
            "symbols": [_complete_symbol()],
        }
    )

    response = parse_exchange_info(document)

    assert response.status is None
    assert response.correlation_id is None
    assert response.payload.timezone == "UTC"
    assert response.payload.server_time == 1783537921471
    assert len(response.payload.rate_limits) == 1
    assert len(response.payload.symbols) == 1

    symbol = response.payload.symbols[0]

    assert symbol.symbol == "ETH/EUR_LEVERAGE"
    assert symbol.name == "ETH/EUR"
    assert symbol.status == "TRADING"
    assert symbol.asset_type == "CRYPTOCURRENCY"
    assert symbol.base_asset == "ETH"
    assert symbol.quote_asset == "EUR"
    assert symbol.order_types == ("LIMIT", "MARKET", "STOP")
    assert symbol.market_modes == ("REGULAR",)
    assert symbol.tick_size == 0.01
    assert symbol.tick_value == 18.3415
    assert symbol.trading_fee == 0.06
    assert symbol.exchange_fee is None


def test_parse_wrapped_exchange_info_metadata() -> None:
    document = _validated_document(
        {
            "timezone": "UTC",
            "serverTime": 1628193845310,
            "symbols": [],
        },
        is_wrapped=True,
        status="OK",
        correlation_id="2",
    )

    response = parse_exchange_info(document)

    assert response.status == "OK"
    assert response.correlation_id == "2"
    assert response.payload.symbols == ()


def test_parse_known_instrument_filters() -> None:
    document = _validated_document(
        {
            "symbols": [_complete_symbol()],
        }
    )

    response = parse_exchange_info(document)
    filters = response.payload.symbols[0].filters

    assert isinstance(filters[0], DzengiLotSizeFilter)
    assert filters[0].min_qty == "0.001"
    assert filters[0].max_qty == "1000"
    assert filters[0].step_size == "0.001"

    assert isinstance(filters[1], DzengiMinNotionalFilter)
    assert filters[1].min_notional == "2"


def test_parse_unknown_instrument_filter() -> None:
    symbol = _complete_symbol()
    symbol["filters"] = [
        {
            "filterType": "FUTURE_FILTER",
            "enabled": True,
            "limit": 10,
            "mode": "STRICT",
        }
    ]

    document = _validated_document(
        {
            "symbols": [symbol],
        }
    )

    response = parse_exchange_info(document)
    parsed_filter = response.payload.symbols[0].filters[0]

    assert isinstance(parsed_filter, DzengiUnknownFilter)
    assert parsed_filter.filter_type == "FUTURE_FILTER"
    assert parsed_filter.fields == (
        ("enabled", True),
        ("limit", 10),
        ("mode", "STRICT"),
    )


def test_parse_exchange_filters_as_unknown_filters() -> None:
    document = _validated_document(
        {
            "exchangeFilters": [
                {
                    "filterType": "GLOBAL_LIMIT",
                    "enabled": True,
                    "limit": 100,
                }
            ],
            "symbols": [],
        }
    )

    response = parse_exchange_info(document)
    exchange_filter = response.payload.exchange_filters[0]

    assert exchange_filter.filter_type == "GLOBAL_LIMIT"
    assert exchange_filter.fields == (
        ("enabled", True),
        ("limit", 100),
    )


def test_parse_exchange_filter_without_filter_type() -> None:
    document = _validated_document(
        {
            "exchangeFilters": [
                {
                    "enabled": True,
                }
            ],
            "symbols": [],
        }
    )

    response = parse_exchange_info(document)

    assert response.payload.exchange_filters[0].filter_type == ""
    assert response.payload.exchange_filters[0].fields == (
        ("enabled", True),
    )


def test_parse_symbol_with_missing_optional_fields() -> None:
    document = _validated_document(
        {
            "symbols": [
                {
                    "symbol": "TEST/USD",
                    "name": "Test",
                    "status": "BREAK",
                    "baseAsset": "TEST",
                    "quoteAsset": "USD",
                    "marketType": "SPOT",
                }
            ],
        }
    )

    response = parse_exchange_info(document)
    symbol = response.payload.symbols[0]

    assert symbol.asset_type is None
    assert symbol.base_asset_precision is None
    assert symbol.quote_asset_id is None
    assert symbol.quote_precision is None
    assert symbol.order_types == ()
    assert symbol.filters == ()
    assert symbol.market_modes == ()
    assert symbol.country is None
    assert symbol.tick_size is None
    assert symbol.min_sl_gap is None


@pytest.mark.parametrize(
    "field",
    [
        "symbol",
        "name",
        "status",
        "baseAsset",
        "quoteAsset",
        "marketType",
    ],
)
def test_reject_missing_required_symbol_field(field: str) -> None:
    symbol = _complete_symbol()
    symbol.pop(field)

    document = _validated_document(
        {
            "symbols": [symbol],
        }
    )

    with pytest.raises(
        InstrumentReferenceParseError,
        match=rf"\.{field} должен быть строкой",
    ):
        parse_exchange_info(document)


def test_reject_invalid_required_string_type() -> None:
    symbol = _complete_symbol()
    symbol["symbol"] = 123

    document = _validated_document(
        {
            "symbols": [symbol],
        }
    )

    with pytest.raises(
        InstrumentReferenceParseError,
        match=r"\.symbol должен быть строкой",
    ):
        parse_exchange_info(document)


def test_reject_invalid_json_number_type() -> None:
    symbol = _complete_symbol()
    symbol["tickSize"] = "0.01"

    document = _validated_document(
        {
            "symbols": [symbol],
        }
    )

    with pytest.raises(
        InstrumentReferenceParseError,
        match=r"\.tickSize должен быть JSON-числом",
    ):
        parse_exchange_info(document)


def test_reject_bool_as_json_number() -> None:
    symbol = _complete_symbol()
    symbol["tickSize"] = True

    document = _validated_document(
        {
            "symbols": [symbol],
        }
    )

    with pytest.raises(
        InstrumentReferenceParseError,
        match=r"\.tickSize должен быть JSON-числом",
    ):
        parse_exchange_info(document)


def test_reject_float_as_integer_field() -> None:
    symbol = _complete_symbol()
    symbol["baseAssetPrecision"] = 3.0

    document = _validated_document(
        {
            "symbols": [symbol],
        }
    )

    with pytest.raises(
        InstrumentReferenceParseError,
        match=r"\.baseAssetPrecision должен быть целым числом",
    ):
        parse_exchange_info(document)


def test_reject_nested_unknown_filter_value() -> None:
    symbol = _complete_symbol()
    symbol["filters"] = [
        {
            "filterType": "FUTURE_FILTER",
            "settings": {
                "enabled": True,
            },
        }
    ]

    document = _validated_document(
        {
            "symbols": [symbol],
        }
    )

    with pytest.raises(
        InstrumentReferenceParseError,
        match=r"\.settings должен быть скалярным JSON-значением",
    ):
        parse_exchange_info(document)


def test_parse_result_uses_immutable_sequences() -> None:
    document = _validated_document(
        {
            "rateLimits": [],
            "exchangeFilters": [],
            "symbols": [_complete_symbol()],
        }
    )

    response = parse_exchange_info(document)
    symbol = response.payload.symbols[0]

    assert isinstance(response.payload.rate_limits, tuple)
    assert isinstance(response.payload.exchange_filters, tuple)
    assert isinstance(response.payload.symbols, tuple)
    assert isinstance(symbol.order_types, tuple)
    assert isinstance(symbol.filters, tuple)
    assert isinstance(symbol.market_modes, tuple)