# app/tests/unit/market_data/acquisition/handlers/test_instrument_handler.py

from __future__ import annotations

from decimal import Decimal

import pytest

from src.market_data.acquisition.exceptions import (
    InstrumentReferenceMappingError,
    InstrumentReferenceParseError,
    InstrumentReferenceSchemaError,
    InstrumentReferenceValueError,
)
from src.market_data.acquisition.handlers.instrument_handler import (
    DzengiInstrumentDocumentHandler,
)
from src.market_data.acquisition.protocol import (
    InstrumentDocumentHandler,
)


def _valid_symbol_document() -> dict[str, object]:
    return {
        "symbol": "BTC/USD_LEVERAGE",
        "name": "BTC/USD",
        "status": "TRADING",
        "assetType": "CRYPTOCURRENCY",
        "baseAsset": "BTC",
        "baseAssetPrecision": 4,
        "quoteAsset": "USD",
        "quoteAssetId": "USD_LEVERAGE",
        "quotePrecision": 4,
        "orderTypes": [
            "LIMIT",
            "MARKET",
            "STOP",
        ],
        "filters": [
            {
                "filterType": "LOT_SIZE",
                "minQty": "0.0001",
                "maxQty": "1000",
                "stepSize": "0.0001",
            },
            {
                "filterType": "MIN_NOTIONAL",
                "minNotional": "1",
            },
        ],
        "marketModes": [
            "REGULAR",
        ],
        "marketType": "LEVERAGE",
        "country": "",
        "sector": "",
        "industry": "",
        "tradingHours": None,
        "tickSize": 0.05,
        "tickValue": 3878.86,
        "tradingFee": 0.06,
        "exchangeFee": None,
        "longRate": -0.01,
        "shortRate": 0.01,
        "swapChargeInterval": 480,
        "minSLGap": 0,
        "maxSLGap": 50.0,
        "minTPGap": 0,
        "maxTPGap": 50.0,
    }


def _valid_unwrapped_document() -> dict[str, object]:
    return {
        "timezone": "UTC",
        "serverTime": 1783537921471,
        "rateLimits": [],
        "exchangeFilters": [],
        "symbols": [
            _valid_symbol_document(),
        ],
    }


def _valid_wrapped_document() -> dict[str, object]:
    return {
        "status": "OK",
        "correlationId": "2",
        "payload": _valid_unwrapped_document(),
    }


def test_handler_satisfies_instrument_document_handler_protocol() -> None:
    handler = DzengiInstrumentDocumentHandler()

    assert isinstance(handler, InstrumentDocumentHandler)


def test_handler_processes_valid_unwrapped_document() -> None:
    handler = DzengiInstrumentDocumentHandler()

    instruments = handler.handle_instrument_document(
        _valid_unwrapped_document()
    )

    assert isinstance(instruments, tuple)
    assert len(instruments) == 1

    instrument = instruments[0]

    assert instrument.symbol == "BTC/USD_LEVERAGE"
    assert instrument.name == "BTC/USD"
    assert instrument.status == "TRADING"
    assert instrument.base_asset == "BTC"
    assert instrument.quote_asset == "USD"
    assert instrument.asset_type == "CRYPTOCURRENCY"
    assert instrument.market_type == "LEVERAGE"
    assert instrument.market_modes == ("REGULAR",)
    assert instrument.order_types == (
        "LIMIT",
        "MARKET",
        "STOP",
    )


def test_handler_processes_valid_wrapped_document() -> None:
    handler = DzengiInstrumentDocumentHandler()

    instruments = handler.handle_instrument_document(
        _valid_wrapped_document()
    )

    assert len(instruments) == 1
    assert instruments[0].symbol == "BTC/USD_LEVERAGE"


def test_handler_returns_exact_decimal_values() -> None:
    handler = DzengiInstrumentDocumentHandler()

    instruments = handler.handle_instrument_document(
        _valid_unwrapped_document()
    )

    instrument = instruments[0]

    assert instrument.tick_size == Decimal("0.05")
    assert instrument.tick_value == Decimal("3878.86")
    assert instrument.step_size == Decimal("0.0001")
    assert instrument.min_qty == Decimal("0.0001")
    assert instrument.max_qty == Decimal("1000")
    assert instrument.min_notional == Decimal("1")


def test_handler_returns_empty_tuple_for_empty_symbols() -> None:
    document = _valid_unwrapped_document()
    document["symbols"] = []

    handler = DzengiInstrumentDocumentHandler()

    instruments = handler.handle_instrument_document(document)

    assert instruments == ()


def test_handler_preserves_schema_error() -> None:
    handler = DzengiInstrumentDocumentHandler()

    with pytest.raises(InstrumentReferenceSchemaError):
        handler.handle_instrument_document([])


def test_handler_preserves_parse_error() -> None:
    document = _valid_unwrapped_document()
    symbol = _valid_symbol_document()
    symbol["baseAssetPrecision"] = True
    document["symbols"] = [symbol]

    handler = DzengiInstrumentDocumentHandler()

    with pytest.raises(InstrumentReferenceParseError):
        handler.handle_instrument_document(document)


def test_handler_preserves_value_error() -> None:
    document = _valid_unwrapped_document()
    symbol = _valid_symbol_document()
    symbol["tickSize"] = 0
    document["symbols"] = [symbol]

    handler = DzengiInstrumentDocumentHandler()

    with pytest.raises(InstrumentReferenceValueError):
        handler.handle_instrument_document(document)


def test_handler_preserves_mapping_error() -> None:
    document = _valid_unwrapped_document()
    symbol = _valid_symbol_document()

    lot_size = {
        "filterType": "LOT_SIZE",
        "minQty": "0.0001",
        "maxQty": "1000",
        "stepSize": "0.0001",
    }

    symbol["filters"] = [
        lot_size,
        lot_size.copy(),
    ]

    document["symbols"] = [symbol]

    handler = DzengiInstrumentDocumentHandler()

    with pytest.raises(
        InstrumentReferenceMappingError,
        match=r"несколько фильтров LOT_SIZE",
    ):
        handler.handle_instrument_document(document)