# app/tests/unit/trading/market_analysis/test_service_candles.py

from __future__ import annotations

from datetime import datetime, timezone
from decimal import Decimal
from typing import Any, cast

import pytest

import src.trading.market_analysis.service as module
from src.market_data.acquisition.models.candle import Candle
from src.trading.market_analysis.service import MarketAnalysisService


def _set_test_attribute(
    target: object,
    name: str,
    value: object,
) -> None:
    setattr(cast(Any, target), name, value)


def _candle(
    *,
    index: int = 0,
    symbol: str = "BTC/USD_LEVERAGE",
    close_price: str = "100",
) -> Candle:
    return Candle(
        symbol=symbol,
        interval="5m",
        open_time=datetime.fromtimestamp(
            1_750_000_000 + index * 300,
            tz=timezone.utc,
        ),
        open_price=Decimal("99"),
        high_price=Decimal("101"),
        low_price=Decimal("98"),
        close_price=Decimal(close_price),
        volume=Decimal("10"),
        source="test",
    )


def test_analyze_uses_get_candles_with_exact_arguments(
    monkeypatch: pytest.MonkeyPatch,
) -> None:
    calls: list[dict[str, object]] = []
    candles = (_candle(symbol="BTC/USD_LEVERAGE"),)
    expected = object()

    class FakeExchangeService:
        def get_candles(
            self,
            symbol: str,
            *,
            interval: str,
            limit: int,
        ) -> tuple[Candle, ...]:
            calls.append(
                {
                    "symbol": symbol,
                    "interval": interval,
                    "limit": limit,
                }
            )
            return candles

    def fake_unknown(**kwargs: object) -> object:
        assert kwargs["symbol"] == "BTC/USD_LEVERAGE"
        assert kwargs["candles_count"] == 1
        return expected

    monkeypatch.setattr(module, "ExchangeService", FakeExchangeService)
    monkeypatch.setattr(
        module,
        "build_unknown_market_analysis_result",
        fake_unknown,
    )

    result = MarketAnalysisService().analyze(
        " btc/usd ",
        interval="5m",
        limit=123,
    )

    assert result is expected
    assert calls == [
        {
            "symbol": " btc/usd ",
            "interval": "5m",
            "limit": 123,
        }
    ]


def test_analyze_uses_requested_symbol_when_candles_are_empty(
    monkeypatch: pytest.MonkeyPatch,
) -> None:
    expected = object()

    class FakeExchangeService:
        def get_candles(
            self,
            symbol: str,
            *,
            interval: str,
            limit: int,
        ) -> tuple[Candle, ...]:
            return ()

    def fake_unknown(**kwargs: object) -> object:
        assert kwargs["symbol"] == "ETH/USD_LEVERAGE"
        assert kwargs["candles_count"] == 0
        assert kwargs["reason"] == "Недостаточно свечей для анализа рынка."
        return expected

    monkeypatch.setattr(module, "ExchangeService", FakeExchangeService)
    monkeypatch.setattr(
        module,
        "build_unknown_market_analysis_result",
        fake_unknown,
    )

    result = MarketAnalysisService().analyze("ETH/USD_LEVERAGE")

    assert result is expected


def test_analyze_returns_unknown_when_get_candles_fails(
    monkeypatch: pytest.MonkeyPatch,
) -> None:
    expected = object()
    original_error = RuntimeError("candles unavailable")

    class FakeExchangeService:
        def get_candles(
            self,
            symbol: str,
            *,
            interval: str,
            limit: int,
        ) -> tuple[Candle, ...]:
            raise original_error

    def fake_unknown(**kwargs: object) -> object:
        assert kwargs["symbol"] == "BTC/USD_LEVERAGE"
        assert kwargs["interval"] == "15m"
        assert kwargs["reason"] == (
            "Не удалось получить свечи: candles unavailable"
        )
        assert "candles_count" not in kwargs
        return expected

    monkeypatch.setattr(module, "ExchangeService", FakeExchangeService)
    monkeypatch.setattr(
        module,
        "build_unknown_market_analysis_result",
        fake_unknown,
    )

    result = MarketAnalysisService().analyze(
        "BTC/USD_LEVERAGE",
        interval="15m",
        limit=80,
    )

    assert result is expected


def test_analyze_rejects_non_finite_canonical_close_price(
    monkeypatch: pytest.MonkeyPatch,
) -> None:
    candles = tuple(
        _candle(
            index=index,
            close_price="NaN" if index == 10 else str(100 + index),
        )
        for index in range(60)
    )
    expected = object()

    class FakeExchangeService:
        def get_candles(
            self,
            symbol: str,
            *,
            interval: str,
            limit: int,
        ) -> tuple[Candle, ...]:
            return candles

    def fake_unknown(**kwargs: object) -> object:
        assert kwargs["symbol"] == "BTC/USD_LEVERAGE"
        assert kwargs["candles_count"] == 60
        assert kwargs["reason"] == (
            "Получены некорректные цены закрытия свечей."
        )
        return expected

    monkeypatch.setattr(module, "ExchangeService", FakeExchangeService)
    monkeypatch.setattr(
        module,
        "build_unknown_market_analysis_result",
        fake_unknown,
    )

    result = MarketAnalysisService().analyze("BTC/USD_LEVERAGE")

    assert result is expected


def test_analyze_converts_decimal_closes_to_float_before_indicators(
    monkeypatch: pytest.MonkeyPatch,
) -> None:
    candles = tuple(
        _candle(
            index=index,
            close_price=str(100 + index),
        )
        for index in range(60)
    )
    ema_calls: list[list[float]] = []
    expected = object()

    class FakeExchangeService:
        def get_candles(
            self,
            symbol: str,
            *,
            interval: str,
            limit: int,
        ) -> tuple[Candle, ...]:
            return candles

    def fake_ema(
        values: list[float],
        period: int,
    ) -> None:
        ema_calls.append(values)
        return None

    def fake_unknown(**kwargs: object) -> object:
        assert kwargs["reason"] == (
            "Недостаточно данных для расчёта EMA / ATR."
        )
        return expected

    monkeypatch.setattr(module, "ExchangeService", FakeExchangeService)
    monkeypatch.setattr(module, "ema", fake_ema)
    monkeypatch.setattr(module, "atr", lambda candles, period: 1.0)
    monkeypatch.setattr(module, "rsi", lambda values, period: 50.0)
    monkeypatch.setattr(
        module,
        "build_unknown_market_analysis_result",
        fake_unknown,
    )

    result = MarketAnalysisService().analyze("BTC/USD_LEVERAGE")

    assert result is expected
    assert len(ema_calls) == 2
    assert all(
        isinstance(value, float)
        for values in ema_calls
        for value in values
    )
    assert ema_calls[0][0] == 100.0
    assert ema_calls[0][-1] == 159.0
