"""Тесты локального OSRM-клиента (#39 A2) — /table annotations=distance.

Сеть НЕ дёргается: подменяем httpx.Client на MockTransport. Покрывает:
- корректный URL (sources=0, annotations=distance, координаты в path);
- парсинг distances в МЕТРАХ + дроп self-index (row[0]);
- None для недостижимого POI;
- OsrmLocalUnavailableError на 500 / timeout / code!=Ok / битой форме;
- пустой destinations → [].
"""

from __future__ import annotations

import httpx
import pytest

from app.services.site_finder import osrm_client_local as osrm


def _install_transport(monkeypatch: pytest.MonkeyPatch, handler) -> None:
    """Подменить httpx.Client в модуле osrm_client_local на MockTransport(handler).

    Захватываем реальный httpx.Client ДО патча — иначе factory рекурсивно зовёт сама себя.
    """
    real_client = httpx.Client

    def client_factory(*_args, **kwargs):
        kwargs.pop("transport", None)
        return real_client(transport=httpx.MockTransport(handler), **kwargs)

    monkeypatch.setattr(osrm.httpx, "Client", client_factory)


def test_builds_correct_url_and_parses_meters(monkeypatch):
    """URL: /table/v1/driving/{coords}?sources=0&annotations=distance; distances в метрах."""
    seen: dict = {}

    def handler(request: httpx.Request) -> httpx.Response:
        seen["path"] = request.url.path
        seen["params"] = dict(request.url.params)
        # distances[0] = origin→[origin, d1, d2] — метры; self = 0.
        return httpx.Response(200, json={"code": "Ok", "distances": [[0.0, 1234.5, 6789.0]]})

    _install_transport(monkeypatch, handler)
    out = osrm.get_road_distances_m(
        60.6, 56.8, [(60.61, 56.81), (60.62, 56.82)]
    )

    # Дорожные расстояния (метры), self-index сброшен.
    assert out == [1234.5, 6789.0]
    # URL: профиль driving, координаты origin первой (lon,lat) и POI.
    assert seen["path"] == "/table/v1/driving/60.6,56.8;60.61,56.81;60.62,56.82"
    # Параметры sources=0, annotations=distance.
    assert seen["params"]["sources"] == "0"
    assert seen["params"]["annotations"] == "distance"


def test_base_url_override_hits_foot_url(monkeypatch):
    """base_url override (#39 A3) → URL строится из переданного base + profile=foot."""
    seen: dict = {}

    def handler(request: httpx.Request) -> httpx.Response:
        seen["host"] = request.url.host
        seen["path"] = request.url.path
        return httpx.Response(200, json={"code": "Ok", "distances": [[0.0, 321.0]]})

    _install_transport(monkeypatch, handler)
    out = osrm.get_road_distances_m(
        60.6,
        56.8,
        [(60.61, 56.81)],
        profile="foot",
        base_url="http://osrm-walk:5000/",  # trailing slash → должен быть срезан
    )

    assert out == [321.0]
    # Хост — foot-сервис, не дефолтный osrm; trailing slash снят (нет //table).
    assert seen["host"] == "osrm-walk"
    assert seen["path"] == "/table/v1/foot/60.6,56.8;60.61,56.81"


def test_base_url_none_uses_default_driving(monkeypatch):
    """base_url=None (дефолт) → settings.osrm_local_url + profile=driving (старое поведение)."""
    seen: dict = {}

    def handler(request: httpx.Request) -> httpx.Response:
        seen["path"] = request.url.path
        return httpx.Response(200, json={"code": "Ok", "distances": [[0.0, 100.0]]})

    _install_transport(monkeypatch, handler)
    out = osrm.get_road_distances_m(60.6, 56.8, [(60.61, 56.81)])

    assert out == [100.0]
    # Дефолтный профиль driving, дефолтный base (settings.osrm_local_url).
    assert seen["path"] == "/table/v1/driving/60.6,56.8;60.61,56.81"


def test_unreachable_poi_returns_none(monkeypatch):
    """OSRM вернул null (нет маршрута) → None в результате (caller оставит straight-line)."""

    def handler(_request):
        return httpx.Response(200, json={"code": "Ok", "distances": [[0.0, 500.0, None]]})

    _install_transport(monkeypatch, handler)
    out = osrm.get_road_distances_m(60.6, 56.8, [(60.61, 56.81), (60.62, 56.82)])
    assert out == [500.0, None]


def test_empty_destinations_returns_empty():
    """Пустой destinations → [] без HTTP-вызова."""
    assert osrm.get_road_distances_m(60.6, 56.8, []) == []


def test_http_500_raises_unavailable(monkeypatch):
    """HTTP 500 → OsrmLocalUnavailableError (не пробрасываем HTTPStatusError)."""

    def handler(_request):
        return httpx.Response(500, text="boom")

    _install_transport(monkeypatch, handler)
    with pytest.raises(osrm.OsrmLocalUnavailableError, match="HTTP 500"):
        osrm.get_road_distances_m(60.6, 56.8, [(60.61, 56.81)])


def test_timeout_raises_unavailable(monkeypatch):
    """Timeout/сетевой сбой → OsrmLocalUnavailableError."""

    def handler(_request):
        raise httpx.ConnectTimeout("timed out")

    _install_transport(monkeypatch, handler)
    with pytest.raises(osrm.OsrmLocalUnavailableError, match="request failed"):
        osrm.get_road_distances_m(60.6, 56.8, [(60.61, 56.81)])


def test_code_not_ok_raises_unavailable(monkeypatch):
    """code != Ok (например NoRoute) → OsrmLocalUnavailableError."""

    def handler(_request):
        return httpx.Response(200, json={"code": "NoRoute", "distances": None})

    _install_transport(monkeypatch, handler)
    with pytest.raises(osrm.OsrmLocalUnavailableError, match="code != Ok"):
        osrm.get_road_distances_m(60.6, 56.8, [(60.61, 56.81)])


def test_bad_shape_raises_unavailable(monkeypatch):
    """Ответ без distances → OsrmLocalUnavailableError."""

    def handler(_request):
        return httpx.Response(200, json={"code": "Ok", "unexpected": True})

    _install_transport(monkeypatch, handler)
    with pytest.raises(osrm.OsrmLocalUnavailableError):
        osrm.get_road_distances_m(60.6, 56.8, [(60.61, 56.81)])


def test_length_mismatch_raises_unavailable(monkeypatch):
    """Число расстояний != числу destinations → OsrmLocalUnavailableError (рассинхрон)."""

    def handler(_request):
        # 2 destinations, но distances содержит только self + 1.
        return httpx.Response(200, json={"code": "Ok", "distances": [[0.0, 500.0]]})

    _install_transport(monkeypatch, handler)
    with pytest.raises(osrm.OsrmLocalUnavailableError, match="!= destinations"):
        osrm.get_road_distances_m(60.6, 56.8, [(60.61, 56.81), (60.62, 56.82)])
