# app/tests/unit/market_data/acquisition/feeds/test_quotes_feed.py

from __future__ import annotations

from datetime import datetime, timezone
from decimal import Decimal

import pytest

from src.market_data.acquisition.exceptions import (
    QuoteTransportError,
    QuoteValueError,
)
from src.market_data.acquisition.feeds.quotes_feed import QuotesFeed
from src.market_data.acquisition.models.quote import Quote
from src.market_data.acquisition.protocol import QuoteFeedProtocol


def _quote(
    *,
    symbol: str = "BTC/USD_LEVERAGE",
) -> Quote:
    return Quote(
        symbol=symbol,
        last_price=Decimal("64159.45"),
        bid_price=Decimal("64159.45"),
        ask_price=Decimal("64159.55"),
        exchange_timestamp=datetime(
            2026,
            7,
            12,
            16,
            14,
            30,
            tzinfo=timezone.utc,
        ),
        received_at=datetime(
            2026,
            7,
            12,
            16,
            14,
            31,
            tzinfo=timezone.utc,
        ),
        source="dzengi",
    )


class StubQuoteDocumentSource:
    def __init__(
        self,
        *,
        document: object,
        error: Exception | None = None,
    ) -> None:
        self.document = document
        self.error = error
        self.symbols: list[str] = []

    def fetch_quote_document(
        self,
        symbol: str,
    ) -> object:
        self.symbols.append(symbol)

        if self.error is not None:
            raise self.error

        return self.document


class StubQuoteDocumentHandler:
    def __init__(
        self,
        *,
        quote: Quote,
        error: Exception | None = None,
    ) -> None:
        self.quote = quote
        self.error = error
        self.documents: list[object] = []

    def handle_quote_document(
        self,
        document: object,
    ) -> Quote:
        self.documents.append(document)

        if self.error is not None:
            raise self.error

        return self.quote


def test_feed_satisfies_quote_feed_protocol() -> None:
    feed = QuotesFeed(
        source=StubQuoteDocumentSource(document={}),
        handler=StubQuoteDocumentHandler(quote=_quote()),
    )

    assert isinstance(feed, QuoteFeedProtocol)


def test_feed_calls_source_once() -> None:
    source = StubQuoteDocumentSource(document={})
    feed = QuotesFeed(
        source=source,
        handler=StubQuoteDocumentHandler(quote=_quote()),
    )

    feed.load_quote("BTC/USD_LEVERAGE")

    assert source.symbols == ["BTC/USD_LEVERAGE"]


def test_feed_passes_symbol_to_source_without_changes() -> None:
    source = StubQuoteDocumentSource(document={})
    feed = QuotesFeed(
        source=source,
        handler=StubQuoteDocumentHandler(quote=_quote()),
    )

    feed.load_quote("  btc/usd_leverage  ")

    assert source.symbols == ["  btc/usd_leverage  "]


def test_feed_calls_handler_once() -> None:
    handler = StubQuoteDocumentHandler(quote=_quote())
    feed = QuotesFeed(
        source=StubQuoteDocumentSource(document={}),
        handler=handler,
    )

    feed.load_quote("BTC/USD_LEVERAGE")

    assert len(handler.documents) == 1


def test_feed_passes_document_to_handler_without_changes() -> None:
    document = {
        "symbol": "BTC/USD_LEVERAGE",
        "lastPrice": "64159.45",
    }
    handler = StubQuoteDocumentHandler(quote=_quote())
    feed = QuotesFeed(
        source=StubQuoteDocumentSource(document=document),
        handler=handler,
    )

    feed.load_quote("BTC/USD_LEVERAGE")

    assert handler.documents == [document]
    assert handler.documents[0] is document


def test_feed_returns_handler_result_without_copying() -> None:
    quote = _quote()
    feed = QuotesFeed(
        source=StubQuoteDocumentSource(document={}),
        handler=StubQuoteDocumentHandler(quote=quote),
    )

    result = feed.load_quote("BTC/USD_LEVERAGE")

    assert result is quote


def test_feed_preserves_transport_error_without_wrapping() -> None:
    original_error = QuoteTransportError("Network error.")
    handler = StubQuoteDocumentHandler(quote=_quote())
    feed = QuotesFeed(
        source=StubQuoteDocumentSource(
            document=None,
            error=original_error,
        ),
        handler=handler,
    )

    with pytest.raises(QuoteTransportError) as exc_info:
        feed.load_quote("BTC/USD_LEVERAGE")

    assert exc_info.value is original_error
    assert handler.documents == []


def test_feed_preserves_handler_error_without_wrapping() -> None:
    document = {"symbol": "BTC/USD_LEVERAGE"}
    original_error = QuoteValueError("Invalid quote.")
    handler = StubQuoteDocumentHandler(
        quote=_quote(),
        error=original_error,
    )
    feed = QuotesFeed(
        source=StubQuoteDocumentSource(document=document),
        handler=handler,
    )

    with pytest.raises(QuoteValueError) as exc_info:
        feed.load_quote("BTC/USD_LEVERAGE")

    assert exc_info.value is original_error
    assert handler.documents == [document]


def test_feed_does_not_retry_source_after_error() -> None:
    source = StubQuoteDocumentSource(
        document=None,
        error=QuoteTransportError("Network error."),
    )
    feed = QuotesFeed(
        source=source,
        handler=StubQuoteDocumentHandler(quote=_quote()),
    )

    with pytest.raises(QuoteTransportError):
        feed.load_quote("BTC/USD_LEVERAGE")

    assert source.symbols == ["BTC/USD_LEVERAGE"]
