# app/tests/unit/integrations/exchange/test_service_validate_symbol.py

from __future__ import annotations

from decimal import Decimal
from types import SimpleNamespace
from typing import cast

import pytest

from src.core.config import Settings
from src.integrations.exchange.models import SymbolValidationResult
from src.integrations.exchange.service import ExchangeService
from src.market_data.acquisition.models.instrument import Instrument


def _settings(
    *,
    exchange_enabled: bool = True,
) -> SimpleNamespace:
    return SimpleNamespace(
        exchange_enabled=exchange_enabled,
    )


def _service(
    *,
    exchange_enabled: bool = True,
) -> ExchangeService:
    service = object.__new__(ExchangeService)
    service.settings = cast(
        Settings,
        _settings(
            exchange_enabled=exchange_enabled,
        ),
    )

    return service


def _instrument(
    *,
    symbol: str = "BTC/USD_LEVERAGE",
    name: str = "BTC/USD",
    base_asset: str = "BTC",
) -> Instrument:
    return Instrument(
        symbol=symbol,
        name=name,
        status="TRADING",
        base_asset=base_asset,
        quote_asset="USD",
        asset_type="CRYPTOCURRENCY",
        market_type="LEVERAGE",
        market_modes=("REGULAR",),
        order_types=("LIMIT", "MARKET", "STOP"),
        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,
    )


def _set_instruments(
    monkeypatch: pytest.MonkeyPatch,
    service: ExchangeService,
    instruments: tuple[Instrument, ...],
) -> list[int]:
    call_count = [0]

    def get_instruments() -> tuple[Instrument, ...]:
        call_count[0] += 1
        return instruments

    monkeypatch.setattr(
        service,
        "get_instruments",
        get_instruments,
    )

    return call_count


def test_validate_symbol_rejects_empty_symbol(
    monkeypatch: pytest.MonkeyPatch,
) -> None:
    service = _service()

    call_count = _set_instruments(
        monkeypatch,
        service,
        (),
    )

    result = service.validate_symbol("   ")

    assert result == SymbolValidationResult(
        requested_symbol="",
        normalized_symbol="",
        is_valid=False,
        message="Символ пустой.",
        symbol_info=None,
    )
    assert call_count[0] == 0


def test_validate_symbol_accepts_symbol_in_mock_mode(
    monkeypatch: pytest.MonkeyPatch,
) -> None:
    service = _service(
        exchange_enabled=False,
    )

    call_count = _set_instruments(
        monkeypatch,
        service,
        (),
    )

    result = service.validate_symbol(
        " btc/usd_leverage "
    )

    assert result == SymbolValidationResult(
        requested_symbol="BTC/USD_LEVERAGE",
        normalized_symbol="BTC/USD_LEVERAGE",
        is_valid=True,
        message="Mock mode active.",
        symbol_info=None,
    )
    assert call_count[0] == 0


def test_validate_symbol_finds_exact_instrument(
    monkeypatch: pytest.MonkeyPatch,
) -> None:
    service = _service()
    instrument = _instrument()

    _set_instruments(
        monkeypatch,
        service,
        (
            instrument,
        ),
    )

    result = service.validate_symbol(
        "BTC/USD_LEVERAGE"
    )

    assert result.is_valid
    assert result.symbol_info is instrument


def test_validate_symbol_is_case_insensitive(
    monkeypatch: pytest.MonkeyPatch,
) -> None:
    service = _service()
    instrument = _instrument()

    _set_instruments(
        monkeypatch,
        service,
        (
            instrument,
        ),
    )

    result = service.validate_symbol(
        "btc/usd_leverage"
    )

    assert result.is_valid
    assert result.normalized_symbol == "BTC/USD_LEVERAGE"


def test_validate_symbol_ignores_outer_spaces(
    monkeypatch: pytest.MonkeyPatch,
) -> None:
    service = _service()
    instrument = _instrument()

    _set_instruments(
        monkeypatch,
        service,
        (
            instrument,
        ),
    )

    result = service.validate_symbol(
        "  btc/usd_leverage  "
    )

    assert result.is_valid
    assert result.requested_symbol == "BTC/USD_LEVERAGE"


def test_validate_symbol_supports_encoded_separator(
    monkeypatch: pytest.MonkeyPatch,
) -> None:
    service = _service()
    instrument = _instrument()

    _set_instruments(
        monkeypatch,
        service,
        (
            instrument,
        ),
    )

    result = service.validate_symbol(
        "btc%2fusd_leverage"
    )

    assert result.is_valid
    assert result.symbol_info is instrument


def test_validate_symbol_supports_internal_spaces(
    monkeypatch: pytest.MonkeyPatch,
) -> None:
    service = _service()
    instrument = _instrument()

    _set_instruments(
        monkeypatch,
        service,
        (
            instrument,
        ),
    )

    result = service.validate_symbol(
        "btc / usd_leverage"
    )

    assert result.is_valid
    assert result.symbol_info is instrument


def test_validate_symbol_rejects_missing_symbol(
    monkeypatch: pytest.MonkeyPatch,
) -> None:
    service = _service()

    _set_instruments(
        monkeypatch,
        service,
        (
            _instrument(),
        ),
    )

    result = service.validate_symbol(
        "XRP/USD_LEVERAGE"
    )

    assert result == SymbolValidationResult(
        requested_symbol="XRP/USD_LEVERAGE",
        normalized_symbol="XRP/USD_LEVERAGE",
        is_valid=False,
        message=(
            "Символ 'XRP/USD_LEVERAGE' "
            "не найден в exchangeInfo."
        ),
        symbol_info=None,
    )


def test_validate_symbol_returns_original_instrument_object(
    monkeypatch: pytest.MonkeyPatch,
) -> None:
    service = _service()
    instrument = _instrument()

    _set_instruments(
        monkeypatch,
        service,
        (
            instrument,
        ),
    )

    result = service.validate_symbol(
        "btc/usd_leverage"
    )

    assert result.symbol_info is instrument


def test_validate_symbol_normalizes_actual_matched_symbol(
    monkeypatch: pytest.MonkeyPatch,
) -> None:
    service = _service()

    instrument = _instrument(
        symbol=" btc/usd_leverage ",
    )

    _set_instruments(
        monkeypatch,
        service,
        (
            instrument,
        ),
    )

    result = service.validate_symbol(
        "BTC/USD_LEVERAGE"
    )

    assert result.normalized_symbol == "BTC/USD_LEVERAGE"


def test_validate_symbol_preserves_success_message(
    monkeypatch: pytest.MonkeyPatch,
) -> None:
    service = _service()

    _set_instruments(
        monkeypatch,
        service,
        (
            _instrument(),
        ),
    )

    result = service.validate_symbol(
        "BTC/USD_LEVERAGE"
    )

    assert result.message == "Символ найден в exchangeInfo."


def test_validate_symbol_calls_get_instruments_once(
    monkeypatch: pytest.MonkeyPatch,
) -> None:
    service = _service()

    call_count = _set_instruments(
        monkeypatch,
        service,
        (
            _instrument(),
        ),
    )

    service.validate_symbol(
        "BTC/USD_LEVERAGE"
    )

    assert call_count[0] == 1


def test_validate_symbol_uses_canonical_instruments(
    monkeypatch: pytest.MonkeyPatch,
) -> None:
    service = _service()

    instrument = _instrument()

    call_count = _set_instruments(
        monkeypatch,
        service,
        (
            instrument,
        ),
    )

    result = service.validate_symbol(
        "BTC/USD_LEVERAGE"
    )

    assert result.is_valid
    assert result.symbol_info is instrument
    assert call_count[0] == 1


def test_validate_symbol_does_not_call_acquisition_directly(
    monkeypatch: pytest.MonkeyPatch,
) -> None:
    service = _service()

    _set_instruments(
        monkeypatch,
        service,
        (
            _instrument(),
        ),
    )

    def fail_if_called() -> tuple[Instrument, ...]:
        raise AssertionError(
            "validate_symbol() must use get_instruments() "
            "and must not call acquisition directly."
        )

    monkeypatch.setattr(
        service,
        "_load_instruments_via_acquisition",
        fail_if_called,
    )

    result = service.validate_symbol(
        "BTC/USD_LEVERAGE"
    )

    assert result.is_valid


def test_validate_symbol_preserves_candidate_priority(
    monkeypatch: pytest.MonkeyPatch,
) -> None:
    service = _service()

    decoded_instrument = _instrument(
        symbol="BTC/USD_LEVERAGE",
    )
    encoded_instrument = _instrument(
        symbol="BTC%2FUSD_LEVERAGE",
    )

    _set_instruments(
        monkeypatch,
        service,
        (
            decoded_instrument,
            encoded_instrument,
        ),
    )

    result = service.validate_symbol(
        "BTC%2FUSD_LEVERAGE"
    )

    assert result.symbol_info is encoded_instrument


def test_validate_symbol_returns_first_duplicate(
    monkeypatch: pytest.MonkeyPatch,
) -> None:
    service = _service()

    first = _instrument(
        name="First",
    )
    second = _instrument(
        name="Second",
    )

    _set_instruments(
        monkeypatch,
        service,
        (
            first,
            second,
        ),
    )

    result = service.validate_symbol(
        "BTC/USD_LEVERAGE"
    )

    assert result.symbol_info is first


def test_validate_symbol_returns_symbol_validation_result(
    monkeypatch: pytest.MonkeyPatch,
) -> None:
    service = _service()

    _set_instruments(
        monkeypatch,
        service,
        (
            _instrument(),
        ),
    )

    result = service.validate_symbol(
        "BTC/USD_LEVERAGE"
    )

    assert isinstance(
        result,
        SymbolValidationResult,
    )


def test_validate_symbol_returns_canonical_instrument(
    monkeypatch: pytest.MonkeyPatch,
) -> None:
    service = _service()
    instrument = _instrument()

    _set_instruments(
        monkeypatch,
        service,
        (
            instrument,
        ),
    )

    result = service.validate_symbol(
        "BTC/USD_LEVERAGE"
    )

    assert isinstance(
        result.symbol_info,
        Instrument,
    )
    assert result.symbol_info is instrument


def test_exchange_service_has_no_legacy_get_exchange_symbols() -> None:
    assert not hasattr(
        ExchangeService,
        "get_exchange_symbols",
    )