# app/tests/unit/market_data/acquisition/test_protocol.py

from __future__ import annotations

from decimal import Decimal

from src.market_data.acquisition.exceptions import (
    InstrumentReferenceMappingError,
    InstrumentReferenceParseError,
    InstrumentReferenceSchemaError,
    InstrumentReferenceTransportError,
    InstrumentReferenceValueError,
    MarketDataAcquisitionError,
)
from src.market_data.acquisition.models.instrument import Instrument
from src.market_data.acquisition.protocol import (
    InstrumentDocumentHandler,
    InstrumentDocumentSource,
    InstrumentFeedProtocol,
)


def _instrument() -> Instrument:
    return Instrument(
        symbol="BTC/USD_LEVERAGE",
        name="BTC/USD",
        status="TRADING",
        base_asset="BTC",
        quote_asset="USD",
        asset_type="CRYPTOCURRENCY",
        market_type="LEVERAGE",
        market_modes=("REGULAR",),
        order_types=("LIMIT", "MARKET"),
        base_asset_precision=4,
        quote_asset_precision=4,
        tick_size=Decimal("0.05"),
        tick_value=Decimal("3878.86"),
        step_size=Decimal("0.0001"),
        min_qty=Decimal("0.0001"),
        max_qty=Decimal("1000"),
        min_notional=Decimal("1"),
        country=None,
        sector=None,
        industry=None,
        trading_hours=None,
    )


class StubInstrumentDocumentSource:
    def fetch_instrument_document(self) -> object:
        return {
            "symbols": [],
        }


class StubInstrumentDocumentHandler:
    def handle_instrument_document(
        self,
        document: object,
    ) -> tuple[Instrument, ...]:
        del document
        return (_instrument(),)


class StubInstrumentFeed:
    def load_instruments(self) -> tuple[Instrument, ...]:
        return (_instrument(),)


class InvalidSource:
    pass


class InvalidHandler:
    pass


class InvalidFeed:
    pass


def test_document_source_satisfies_protocol() -> None:
    source = StubInstrumentDocumentSource()

    assert isinstance(source, InstrumentDocumentSource)
    assert source.fetch_instrument_document() == {
        "symbols": [],
    }


def test_document_handler_satisfies_protocol() -> None:
    handler = StubInstrumentDocumentHandler()

    assert isinstance(handler, InstrumentDocumentHandler)

    instruments = handler.handle_instrument_document(
        {
            "symbols": [],
        }
    )

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


def test_instrument_feed_satisfies_protocol() -> None:
    feed = StubInstrumentFeed()

    assert isinstance(feed, InstrumentFeedProtocol)

    instruments = feed.load_instruments()

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


def test_objects_without_required_methods_do_not_satisfy_protocols() -> None:
    assert not isinstance(InvalidSource(), InstrumentDocumentSource)
    assert not isinstance(InvalidHandler(), InstrumentDocumentHandler)
    assert not isinstance(InvalidFeed(), InstrumentFeedProtocol)


def test_protocols_support_structural_typing_without_inheritance() -> None:
    source: InstrumentDocumentSource = StubInstrumentDocumentSource()
    handler: InstrumentDocumentHandler = StubInstrumentDocumentHandler()
    feed: InstrumentFeedProtocol = StubInstrumentFeed()

    document = source.fetch_instrument_document()
    handled_instruments = handler.handle_instrument_document(document)
    loaded_instruments = feed.load_instruments()

    assert handled_instruments[0].symbol == "BTC/USD_LEVERAGE"
    assert loaded_instruments[0].symbol == "BTC/USD_LEVERAGE"


def test_transport_error_inherits_acquisition_error() -> None:
    error = InstrumentReferenceTransportError(
        "Не удалось получить exchangeInfo."
    )

    assert isinstance(error, MarketDataAcquisitionError)
    assert str(error) == "Не удалось получить exchangeInfo."


def test_all_instrument_reference_errors_share_base_type() -> None:
    errors = (
        InstrumentReferenceTransportError(),
        InstrumentReferenceSchemaError(),
        InstrumentReferenceParseError(),
        InstrumentReferenceValueError(),
        InstrumentReferenceMappingError(),
    )

    assert all(
        isinstance(error, MarketDataAcquisitionError)
        for error in errors
    )