"""Interpreter translation client (#143) — unit tests with mocked HTTP (no DB,
no real network)."""
import pytest

from backend.app.services import interpreter_client as ic


class _Resp:
    def __init__(self, status_code=200, payload=None, headers=None):
        self.status_code = status_code
        self._payload = payload or {}
        self.headers = headers or {}

    def json(self):
        return self._payload

    def raise_for_status(self):
        if self.status_code >= 400:
            raise ic.requests.HTTPError(f"status {self.status_code}")


def test_translate_maps_batch_in_order(monkeypatch):
    captured = {}

    def fake_post(url, json, timeout):
        captured["json"] = json
        return _Resp(200, {
            "translatedText": ["The cat is cute", "Blonde gal!"],
            "detectedLanguage": {"language": "ja", "confidence": 0.98},
            "interpreter": {"engine": "llm", "engine_version": "ollama:x:12b"},
        })

    monkeypatch.setattr(ic.session, "post", fake_post)
    out = ic.translate(["ねこが可愛い", "金髪ギャル！"], base_url="http://i.lan")
    assert out["translations"] == ["The cat is cute", "Blonde gal!"]
    assert out["detected_lang"] == "ja"
    assert out["detected_confidence"] == 0.98   # surfaced for the detection guard
    assert out["engine_version"] == "ollama:x:12b"
    assert captured["json"]["q"] == ["ねこが可愛い", "金髪ギャル！"]
    assert captured["json"]["target"] == "en"
    # Source MUST stay "auto": an explicit source makes Interpreter skip detection
    # and report confidence 1.0, which would defeat the acceptance gate.
    assert captured["json"]["source"] == "auto"


def test_translate_passthrough_unchanged(monkeypatch):
    # Already-English items come back unchanged in their slot (engine "none").
    monkeypatch.setattr(ic.session, "post", lambda *a, **k: _Resp(200, {
        "translatedText": ["already english"],
        "detectedLanguage": {"language": "en"},
        "interpreter": {"engine": "none", "engine_version": None},
    }))
    out = ic.translate(["already english"], base_url="http://i.lan")
    assert out["translations"] == ["already english"]
    assert out["detected_lang"] == "en"
    assert out["engine"] == "none"


def test_translate_503_raises_unavailable(monkeypatch):
    monkeypatch.setattr(ic.session, "post", lambda *a, **k: _Resp(503, {}))
    with pytest.raises(ic.InterpreterUnavailable):
        ic.translate(["x"], base_url="http://i.lan")


def test_translate_connection_error_raises_unavailable(monkeypatch):
    def boom(*a, **k):
        raise ic.requests.ConnectionError("refused")

    monkeypatch.setattr(ic.session, "post", boom)
    with pytest.raises(ic.InterpreterUnavailable):
        ic.translate(["x"], base_url="http://i.lan")


@pytest.mark.parametrize("code", [429, 500, 502, 503, 504])
def test_translate_429_and_5xx_raise_unavailable(monkeypatch, code):
    # A gracefully-draining service behind a reverse proxy returns 429/502/503/504
    # — all mean "retry later", not an opaque error.
    monkeypatch.setattr(ic.session, "post", lambda *a, **k: _Resp(code, {}))
    with pytest.raises(ic.InterpreterUnavailable) as ei:
        ic.translate(["x"], base_url="http://i.lan")
    assert ei.value.retry_after is None


def test_translate_honours_retry_after_seconds(monkeypatch):
    monkeypatch.setattr(
        ic.session, "post",
        lambda *a, **k: _Resp(503, {}, headers={"Retry-After": "42"}),
    )
    with pytest.raises(ic.InterpreterUnavailable) as ei:
        ic.translate(["x"], base_url="http://i.lan")
    assert ei.value.retry_after == 42


def test_translate_retry_after_past_http_date_clamps_to_zero(monkeypatch):
    # HTTP-date form; a past date → non-negative clamp to 0 (deterministic).
    monkeypatch.setattr(
        ic.session, "post",
        lambda *a, **k: _Resp(
            503, {}, headers={"Retry-After": "Wed, 21 Oct 2015 07:28:00 GMT"}),
    )
    with pytest.raises(ic.InterpreterUnavailable) as ei:
        ic.translate(["x"], base_url="http://i.lan")
    assert ei.value.retry_after == 0.0


def test_translate_400_raises_bad_request(monkeypatch):
    monkeypatch.setattr(
        ic.session, "post", lambda *a, **k: _Resp(400, {"error": "bad target"})
    )
    with pytest.raises(ic.InterpreterBadRequest):
        ic.translate(["x"], base_url="http://i.lan")


def test_translate_empty_is_noop(monkeypatch):
    def boom(*a, **k):
        raise AssertionError("should not call the service for an empty batch")

    monkeypatch.setattr(ic.session, "post", boom)
    assert ic.translate([], base_url="http://i.lan")["translations"] == []


def test_health_true_when_llm_up(monkeypatch):
    monkeypatch.setattr(
        ic.session, "get", lambda *a, **k: _Resp(200, {"engines": {"llm": True}})
    )
    assert ic.health("http://i.lan") is True


def test_health_false_when_engine_down(monkeypatch):
    monkeypatch.setattr(
        ic.session, "get", lambda *a, **k: _Resp(200, {"engines": {"llm": False}})
    )
    assert ic.health("http://i.lan") is False


def test_health_false_on_error(monkeypatch):
    def boom(*a, **k):
        raise ic.requests.ConnectionError("refused")

    monkeypatch.setattr(ic.session, "get", boom)
    assert ic.health("http://i.lan") is False


def test_health_false_when_url_empty():
    assert ic.health("") is False
