"""콘티 변환을 xAI 직접 편집 경로로 (2026-09-18 컨트리로드 실측).

## 무엇이 결함이었나

콘티 변환은 OpenRouter chat 으로 `x-ai/grok-imagine-image-quality` 를 불렀다.
14:16 까지 성공 142건, **14:18 부터 전부 404**(실측 llm_call_log):

    OpenRouter  x-ai/grok-imagine-image-quality → 404 (이미지 출력 엔드포인트 없음)
    OpenRouter  x-ai/grok-imagine-image         → 400 (유효한 모델 ID 아님)
    xAI 직접    /v1/images/generations          → 200 이지만 **원본을 무시**하고 새로 그림
                                                  (구조 차이 131 vs 글만 119)
    xAI 직접    /v1/images/edits                → 200 · 원본 유지(구조 차이 17~28)

실패한 샷은 변환 없이 원본으로 저장된다 — 그림이 사라지진 않지만 손질이 빠진다.

## 이 시험이 잠그는 것

① 원본이 없으면 **선다**(편집 API 는 원본 없이도 새 그림을 그려 준다 — 그건 변환이 아니다)
② 보내는 몸통: 편집 URL · 모델 · `image.{url,type}` · 설정의 quality
③ 응답 b64/url 양쪽 파싱
④ 404 는 재시도 없이 실패, 429·503 은 재시도
⑤ 제공자 배선: 신원(provider·endpoint·model)과 client 종류
"""
from __future__ import annotations

import base64
import io
import json

import pytest
from PIL import Image


def _png_bytes(color=(10, 20, 30)) -> bytes:
    buf = io.BytesIO()
    Image.new("RGB", (8, 8), color).save(buf, format="PNG")
    return buf.getvalue()


class _Resp:
    def __init__(self, status: int, payload=None, text: str = ""):
        self.status_code = status
        self._payload = payload
        self.text = text or json.dumps(payload or {})

    def json(self):
        return self._payload


class _FakeClient:
    """httpx.Client 대역 — 보낸 것을 기록하고 정해진 응답을 돌려준다."""

    sent: list = []
    responses: list = []

    def __init__(self, *a, **kw):
        pass

    def __enter__(self):
        return self

    def __exit__(self, *a):
        return False

    def post(self, url, json=None, headers=None):
        _FakeClient.sent.append({"url": url, "body": json, "headers": headers})
        return _FakeClient.responses.pop(0)

    def get(self, url, **kw):
        _FakeClient.sent.append({"url": url, "body": None, "headers": None})
        return _Resp(200, None, "")


@pytest.fixture
def client(monkeypatch):
    import httpx

    from app.modules.llm.xai_image_client import XaiImageClient

    _FakeClient.sent = []
    _FakeClient.responses = []
    monkeypatch.setattr(httpx, "Client", _FakeClient)
    monkeypatch.setattr("app.core.config.settings.xai_api_key", "k-test", raising=False)
    monkeypatch.setattr("app.core.config.settings.xai_image_model", "grok-imagine-image", raising=False)
    monkeypatch.setattr("app.core.config.settings.xai_image_quality", "high", raising=False)
    monkeypatch.setattr("app.core.config.settings.llm_max_retries", 2, raising=False)
    monkeypatch.setattr("app.modules.llm.xai_image_client.log_llm_call", lambda **kw: "call-1")
    monkeypatch.setattr("app.modules.llm.xai_image_client.capture_generated_image", lambda *a, **kw: None)

    class _Tracer:
        def log(self, **kw):
            pass

    monkeypatch.setattr("app.modules.llm.image_tracer.get_image_tracer", lambda: _Tracer())
    monkeypatch.setattr("time.sleep", lambda *_: None)
    c = XaiImageClient()
    c.set_context(operation_type="still_cine_transform", step="scene_image_pipeline")
    return c


def test_source_image_is_required(client):
    """★원본 없이 부르면 선다 — 조용히 새 그림을 그리면 그건 변환이 아니다."""
    with pytest.raises(RuntimeError, match="원본 이미지가 없다"):
        client.generate_image("prompt")
    assert _FakeClient.sent == [], "보내지도 말아야 한다"


def test_body_is_the_edit_shape_with_quality(client):
    src = _png_bytes()
    _FakeClient.responses = [
        _Resp(200, {"data": [{"b64_json": base64.b64encode(_png_bytes((99, 99, 99))).decode()}]})]
    png, ms = client.generate_image("cine prompt", labeled_references=[("SOURCE IMAGE:", src)])
    assert png[:8] == b"\x89PNG\r\n\x1a\n" and ms >= 0
    sent = _FakeClient.sent[0]
    assert sent["url"].endswith("/v1/images/edits")
    assert sent["body"]["model"] == "grok-imagine-image"
    assert sent["body"]["quality"] == "high"
    img = sent["body"]["image"]
    assert img["type"] == "image_url"
    assert img["url"].startswith("data:image/png;base64,")
    assert base64.b64decode(img["url"].split(",", 1)[1]) == src
    assert sent["headers"]["Authorization"] == "Bearer k-test"


def test_404_is_terminal_no_retry(client):
    _FakeClient.responses = [_Resp(404, None, '{"code":"not-found","error":"model x"}')]
    with pytest.raises(RuntimeError, match="404"):
        client.generate_image("p", labeled_references=[("SOURCE IMAGE:", _png_bytes())])
    assert len(_FakeClient.sent) == 1, "확정 거부는 다시 보내지 않는다"


def test_503_is_retried_then_succeeds(client):
    _FakeClient.responses = [
        _Resp(503, None, "overloaded"),
        _Resp(200, {"data": [{"b64_json": base64.b64encode(_png_bytes()).decode()}]}),
    ]
    png, _ = client.generate_image("p", labeled_references=[("SOURCE IMAGE:", _png_bytes())])
    assert png[:4] == b"\x89PNG"
    assert len(_FakeClient.sent) == 2


def test_moderation_is_not_retried(client):
    _FakeClient.responses = [_Resp(400, None, "Content moderation blocked: SAFETY")]
    with pytest.raises(RuntimeError, match="moderation"):
        client.generate_image("p", labeled_references=[("SOURCE IMAGE:", _png_bytes())])
    assert len(_FakeClient.sent) == 1


# ── 제공자 배선 ──────────────────────────────────────────────────


def test_provider_wiring(monkeypatch):
    from app.modules.pipeline.cine_provider import (
        KNOWN_PROVIDERS, XAI_ENDPOINT, build_cine_client, cine_provider_identity)
    from app.modules.llm.xai_image_client import XaiImageClient

    assert "xai" in KNOWN_PROVIDERS
    monkeypatch.setattr("app.core.config.settings.xai_image_model", "grok-imagine-image", raising=False)
    ident = cine_provider_identity("xai")
    assert ident == {"provider": "xai", "endpoint": XAI_ENDPOINT,
                     "model": "grok-imagine-image"}
    assert isinstance(build_cine_client("xai"), XaiImageClient)
    # 옛 grok 신원은 그대로 — 지난 산출의 지문이 흔들리면 안 된다
    assert cine_provider_identity("grok")["provider"] == "grok"


def test_settings_accept_xai_provider():
    from app.core.config import Settings

    assert Settings._still_cine_provider_known("xai") == "xai"
    with pytest.raises(ValueError):
        Settings._still_cine_provider_known("nope")


def test_xai_route_has_a_resend_policy():
    from app.modules.llm.image_send_state import classify_http_status

    assert classify_http_status(429, via="xai") == "retryable"
    assert classify_http_status(503, via="xai") == "retryable"
    assert classify_http_status(404, via="xai") == "terminal"
    # 상태를 모르는 것은 재전송하지 않는다
    assert classify_http_status(500, via="xai") == "submission_unknown"
