"""Тесты для dadata_client.clean_address (#2177 geo-pass).

httpx.Client замокан — реальная сеть/DaData не дёргается. settings-credentials
патчатся через monkeypatch (литерала ключа нигде нет).
"""

from __future__ import annotations

from typing import Any
from unittest.mock import MagicMock, patch

import httpx
import pytest

from app.services import dadata_client
from app.services.dadata_client import clean_address


@pytest.fixture(autouse=True)
def _creds(monkeypatch: pytest.MonkeyPatch) -> None:
    """Задаём фейковые credentials, чтобы clean_address не отваливался на guard."""
    monkeypatch.setattr(dadata_client.settings, "dadata_api_token", "test-token")
    monkeypatch.setattr(dadata_client.settings, "dadata_api_secret", "test-secret")


def _mock_client(response: MagicMock) -> MagicMock:
    """httpx.Client() context-manager, post() → response."""
    client = MagicMock()
    client.__enter__ = MagicMock(return_value=client)
    client.__exit__ = MagicMock(return_value=False)
    client.post.return_value = response
    return client


def _resp(status: int, json_body: Any) -> MagicMock:
    resp = MagicMock()
    resp.status_code = status
    resp.json.return_value = json_body
    resp.text = ""
    return resp


def test_clean_address_returns_coords() -> None:
    """Успешный ответ с geo_lat/geo_lon → (lat, lon) float-пара."""
    resp = _resp(200, [{"geo_lat": "56.8389", "geo_lon": "60.6057", "qc_geo": 0}])
    with patch("httpx.Client", return_value=_mock_client(resp)):
        result = clean_address("г Екатеринбург, ул Малышева, 125")
    assert result == (56.8389, 60.6057)


def test_clean_address_no_coords_returns_none() -> None:
    """Ответ без geo_lat/geo_lon (адрес не геокодирован) → None."""
    resp = _resp(200, [{"qc_geo": 5, "result": None}])
    with patch("httpx.Client", return_value=_mock_client(resp)):
        assert clean_address("нераспознаваемо") is None


def test_clean_address_empty_array_returns_none() -> None:
    """Пустой массив → None."""
    resp = _resp(200, [])
    with patch("httpx.Client", return_value=_mock_client(resp)):
        assert clean_address("г Екатеринбург, ул X, 1") is None


def test_clean_address_short_input_returns_none() -> None:
    """Короткий/пустой адрес → None без сетевого вызова."""
    assert clean_address("") is None
    assert clean_address("ab") is None


def test_clean_address_no_credentials_returns_none(monkeypatch: pytest.MonkeyPatch) -> None:
    """Нет credentials → graceful None (geo-pass reject'ит, не падает)."""
    monkeypatch.setattr(dadata_client.settings, "dadata_api_token", None)
    monkeypatch.setattr(dadata_client.settings, "dadata_api_secret", None)
    assert clean_address("г Екатеринбург, ул Малышева, 125") is None


def test_clean_address_429_returns_none() -> None:
    """HTTP 429 quota → None."""
    resp = _resp(429, {})
    with patch("httpx.Client", return_value=_mock_client(resp)):
        assert clean_address("г Екатеринбург, ул X, 1") is None


def test_clean_address_timeout_returns_none() -> None:
    """Сетевой timeout → None, не пробрасываем исключение."""
    client = MagicMock()
    client.__enter__ = MagicMock(return_value=client)
    client.__exit__ = MagicMock(return_value=False)
    client.post.side_effect = httpx.TimeoutException("timed out")
    with patch("httpx.Client", return_value=client):
        assert clean_address("г Екатеринбург, ул X, 1") is None


def test_clean_address_403_falls_back_to_suggest(monkeypatch) -> None:
    """Фича CLEAN отключена (403) → фолбэк на suggest/address отдаёт координаты.

    Прод-инцидент 2026-07-03: «Feature 'CLEAN' disabled for token» при живом
    бесплатном suggestions API.
    """
    from app.services import dadata_client as dc

    monkeypatch.setattr(dc.settings, "dadata_api_token", "test-token")
    monkeypatch.setattr(dc.settings, "dadata_api_secret", "test-secret")

    class _Resp:
        def __init__(self, status_code, payload):
            self.status_code = status_code
            self._payload = payload

        def json(self):
            return self._payload

    class _Client:
        def __init__(self, *a, **k):
            pass

        def __enter__(self):
            return self

        def __exit__(self, *a):
            return False

        def post(self, url, **kwargs):
            if "cleaner" in url:
                return _Resp(403, {"message": "Feature 'CLEAN' disabled"})
            assert "suggestions" in url
            assert "X-Secret" not in kwargs.get("headers", {})
            return _Resp(
                200,
                {
                    "suggestions": [
                        {
                            "value": "г Екатеринбург, ул Мира, д 19",
                            "data": {"geo_lat": "56.8447", "geo_lon": "60.6547"},
                        }
                    ]
                },
            )

    monkeypatch.setattr(dc.httpx, "Client", _Client)
    coords = dc.clean_address("г Екатеринбург, ул Мира, 19")
    assert coords is not None
    assert abs(coords[0] - 56.8447) < 1e-6 and abs(coords[1] - 60.6547) < 1e-6
