"""grok 이미지 요청은 **호출자가 준 화면 비율을 싣는다** (2026-09-19).

실측(컨트리로드 2판 S49sh14): nb2 가 거절해 교차 백엔드(grok)로 넘어간
롤이 세로 880×1184 로 나왔다. 조립 쪽은 16:9 를 넘겼는데 클라이언트가
「API 파라미터가 없다 — 참조 비율을 상속한다」며 버렸다. 참조는 가로
배경 한 장 + 세로 인물 참조 세 장이었고, grok 은 세로를 따랐다.

잠그는 것:
- 비율을 주면 요청 본문에 `image_config.aspect_ratio` 로 실린다.
- 안 주면 종전 그대로 **안 싣는다** — 도면 지도는 바탕 지도의 비율을
  따라가야 해서 일부러 안 넘긴다(`marker_map_engine`).
- 조립 경로(`make_nb2_gen_fn` 의 교차 백엔드 갈래)가 실제로 비율을 넘긴다.
"""
from __future__ import annotations

import json

from app.modules.llm import grok_image_client as gic

_OK = {"choices": [{"message": {"images": [
    {"image_url": {"url": "data:image/png;base64,aGk="}}]}}]}


class _FakeResponse:
    def __init__(self, payload):
        self._body = json.dumps(payload).encode("utf-8")

    def read(self):
        return self._body

    def __enter__(self):
        return self

    def __exit__(self, *_a):
        return False


def _client(monkeypatch, sent):
    def _urlopen(req, timeout=None):
        sent.append(json.loads(req.data.decode("utf-8")))
        return _FakeResponse(_OK)

    monkeypatch.setattr(gic.urllib.request, "urlopen", _urlopen)
    monkeypatch.setattr(gic._settings, "llm_timeout_image_gen", 5,
                        raising=False)
    monkeypatch.setattr(gic._settings, "llm_max_retries", 0, raising=False)
    monkeypatch.setattr(gic, "reserve_current_call", lambda **_kw: None)
    monkeypatch.setattr(gic, "log_llm_call", lambda **_kw: "call-1")
    monkeypatch.setattr(gic, "capture_generated_image", lambda *a, **k: None)
    monkeypatch.setattr(gic, "ensure_png_bytes", lambda b, context="": b"png")

    class _NullTracer:
        def log(self, **_kw):
            return None

    monkeypatch.setattr("app.modules.llm.image_tracer.get_image_tracer",
                        lambda: _NullTracer())
    return gic.GrokImageClient(api_key="k", model="m")


def test_given_aspect_ratio_is_sent(monkeypatch):
    sent = []
    client = _client(monkeypatch, sent)
    client.generate_image("p", labeled_references=[("L", b"x")],
                          aspect_ratio="16:9")
    assert sent[0]["image_config"] == {"aspect_ratio": "16:9"}


def test_no_aspect_ratio_means_nothing_is_sent(monkeypatch):
    """도면 지도처럼 비율을 안 넘기는 호출은 종전과 같은 본문이어야 한다."""
    sent = []
    client = _client(monkeypatch, sent)
    client.generate_image("p", labeled_references=[("L", b"x")])
    assert "image_config" not in sent[0]


def test_roll_ladder_cross_backend_passes_the_still_ratio(monkeypatch, tmp_path):
    """조립 경로의 교차 백엔드(nb2 거절 → grok)가 비율을 **넘기는지**.

    ★끝점(요청 본문)에서 잰다 — 사다리 안에서 grok 클라이언트를 새로 만들므로
     클래스의 `generate_image` 를 대신해 실제로 받은 인자를 적는다.
    """
    import app.modules.pipeline.multiroll_gemini as mg
    from app.modules.llm.gemini_image_client import GeminiImageClient

    monkeypatch.setattr(
        "app.core.config.settings.still_safety_fallback_enabled", True,
        raising=False)

    class _Refuses(GeminiImageClient):
        def __init__(self):  # 키·네트워크 없이
            pass

        def set_context(self, **kw):
            return self

        def generate_image(self, prompt, reference_images=None,
                           aspect_ratio="16:9", labeled_references=None):
            raise RuntimeError("blocked by safety")

    got = {}

    def _grok_generate(self, prompt, reference_images=None,
                       aspect_ratio=None, labeled_references=None):
        got["aspect_ratio"] = aspect_ratio
        return b"png", 1

    monkeypatch.setattr(gic.GrokImageClient, "__init__",
                        lambda self, *a, **k: None)
    monkeypatch.setattr(gic.GrokImageClient, "set_context",
                        lambda self, **k: None)
    monkeypatch.setattr(gic.GrokImageClient, "generate_image", _grok_generate)
    monkeypatch.setattr(mg, "atomic_write_bytes",
                        lambda p, b: p)

    gen = mg.make_nb2_gen_fn(project_id="P", episode_id="E",
                             gemini_client=_Refuses())
    gen("still_S1sh1_a", "prompt", [("L", b"x")], tmp_path / "o.png")
    assert got["aspect_ratio"] == "16:9"
