"""Тесты ORS-клиента (#41) — /matrix durations.

Сеть НЕ дёргается (ORS free tier 2000/день): подменяем httpx.Client на
MockTransport. Покрывает: парсинг durations, усечение до лимита, 429 → OrsUnavailableError,
нет ключа → OrsUnavailableError, None-маршруты.
"""

from __future__ import annotations

import httpx
import pytest

from app.core.config import settings
from app.services.site_finder import ors_client


@pytest.fixture
def with_key(monkeypatch: pytest.MonkeyPatch):
    """Задать фиктивный ORS-ключ на время теста."""
    monkeypatch.setattr(settings, "openrouteservice_api_key", "test-key-123")
    yield


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

    Захватываем реальный httpx.Client ДО патча — иначе factory рекурсивно зовёт
    сама себя (ors_client.httpx — общий модуль httpx).
    """
    captured: dict = {}
    real_client = httpx.Client

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

    monkeypatch.setattr(ors_client.httpx, "Client", client_factory)
    return captured


def test_matrix_durations_parses_minutes(with_key, monkeypatch):
    """durations (секунды) → минуты, длина = числу destinations."""

    def handler(request: httpx.Request) -> httpx.Response:
        assert request.url.path.endswith("/foot-walking")
        return httpx.Response(200, json={"durations": [[0.0, 300.0, 600.0]]})

    _install_transport(monkeypatch, handler)
    out = ors_client.matrix_durations_min(
        60.6, 56.8, [(60.61, 56.81), (60.62, 56.82), (60.63, 56.83)]
    )
    assert out == [0.0, 5.0, 10.0]


def test_matrix_none_route_preserved(with_key, monkeypatch):
    """ORS вернул null (нет маршрута) → None в результате."""

    def handler(_request):
        return httpx.Response(200, json={"durations": [[120.0, None]]})

    _install_transport(monkeypatch, handler)
    out = ors_client.matrix_durations_min(60.6, 56.8, [(60.61, 56.81), (60.62, 56.82)])
    assert out == [2.0, None]


def test_matrix_no_key_raises():
    """Нет API-ключа → OrsUnavailableError (caller сделает straight-line fallback)."""
    # settings по умолчанию пустой ключ (testing). Явно убедимся.
    if settings.openrouteservice_api_key:
        pytest.skip("ключ задан в окружении")
    with pytest.raises(ors_client.OrsUnavailableError):
        ors_client.matrix_durations_min(60.6, 56.8, [(60.61, 56.81)])


def test_matrix_429_raises_unavailable(with_key, monkeypatch):
    """429 (rate-limit) → OrsUnavailableError, не пробрасываем HTTPStatusError."""

    def handler(_request):
        return httpx.Response(429, json={"error": "rate limit"})

    _install_transport(monkeypatch, handler)
    with pytest.raises(ors_client.OrsUnavailableError, match="daily limit"):
        ors_client.matrix_durations_min(60.6, 56.8, [(60.61, 56.81)])


def test_matrix_empty_destinations_returns_empty(with_key):
    assert ors_client.matrix_durations_min(60.6, 56.8, []) == []


def test_matrix_invalid_profile_raises(with_key):
    with pytest.raises(ors_client.OrsUnavailableError, match="profile"):
        ors_client.matrix_durations_min(60.6, 56.8, [(60.61, 56.81)], profile="teleport")


def test_matrix_truncates_to_limit(with_key, monkeypatch):
    """>MAX_MATRIX_DESTINATIONS усекается; body.locations = 1 source + лимит."""
    seen: dict = {}

    def handler(request: httpx.Request) -> httpx.Response:
        import json

        body = json.loads(request.content)
        seen["n_locations"] = len(body["locations"])
        n_dest = len(body["destinations"])
        return httpx.Response(200, json={"durations": [[60.0] * n_dest]})

    _install_transport(monkeypatch, handler)
    dests = [(60.0 + i * 0.001, 56.0) for i in range(ors_client.MAX_MATRIX_DESTINATIONS + 50)]
    out = ors_client.matrix_durations_min(60.6, 56.8, dests)
    # 1 source + MAX destinations
    assert seen["n_locations"] == ors_client.MAX_MATRIX_DESTINATIONS + 1
    assert len(out) == ors_client.MAX_MATRIX_DESTINATIONS


def test_matrix_bad_response_raises(with_key, monkeypatch):
    """Битый ответ без durations → OrsUnavailableError."""

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

    _install_transport(monkeypatch, handler)
    with pytest.raises(ors_client.OrsUnavailableError):
        ors_client.matrix_durations_min(60.6, 56.8, [(60.61, 56.81)])
