"""End-to-end integration tests – Dotprompt render → OpenAI adapter generate.

Covers:
- Basic adapter.generate() with config overrides
- Full Dotprompt → render → generate pipeline
- Multi-turn conversation
- Structured JSON schema output
- Image input (vision)
- Batch execution via execute_batch()
- Output format validation (PromptOutputConfig)
- {{ SCHEMA }} auto-variable injection
- Dynamic model name via frontmatter variables
- JSON Schema object output pipeline
- execute_batch() convenience function for single and batch execution
"""

import json
import logging
from typing import Any

import jsonschema
import pytest

from dotpromptz.executor import execute_batch
from dotpromptz.typing import (
    MediaContent,
    MediaPart,
    Message,
    PromptOutputConfig,
    RenderedPrompt,
    Role,
    TextPart,
)

logger = logging.getLogger(__name__)

pytestmark = pytest.mark.integration

MODEL = 'gpt-5.2'
IMAGE_MODEL = 'gpt-image-1.5'
_PYTHON_PNG_URL = 'https://raw.githubusercontent.com/github/explore/main/topics/python/python.png'


def _render_batch(
    dotprompt_instance: Any,
    source: str,
    input_list: list[dict[str, Any]],
) -> list[RenderedPrompt]:
    """Render one prompt per batch input item for runner execution tests."""
    return [dotprompt_instance.render(source, input=item) for item in input_list]


# ---------------------------------------------------------------------------
# Test A – adapter.generate() with richer config
# ---------------------------------------------------------------------------


@pytest.mark.asyncio
async def test_adapter_generate_with_temperature(openai_adapter) -> None:
    """Call generate() with explicit temperature / max_tokens."""
    rendered = RenderedPrompt(
        config={'model': MODEL, 'temperature': 1, 'max_tokens': 30},
        messages=[
            Message(role=Role.SYSTEM, content=[TextPart(text='You are a concise assistant.')]),
            Message(role=Role.USER, content=[TextPart(text='What is 2 + 2?')]),
        ],
    )

    response = await openai_adapter.generate(rendered)

    assert response.text is not None
    assert '4' in response.text
    assert response.model is not None
    assert response.usage is not None
    assert response.usage.prompt_tokens > 0
    assert response.usage.completion_tokens > 0
    logger.info('[e2e] model=%s  text=%r', response.model, response.text)


# ---------------------------------------------------------------------------
# Test B – full Dotprompt → render → generate pipeline
# ---------------------------------------------------------------------------

_PROMPT_SOURCE = """\
---
version: 2
config:
  model: gpt-5.2
  temperature: 1
---
{{ role("system") }}
You are a geography expert. Answer in one sentence.
{{ role("user") }}
What is the capital of {{ country }}?
"""


@pytest.mark.asyncio
async def test_dotprompt_render_then_generate(dotprompt_instance, openai_adapter) -> None:
    """Render a .prompt template and send it through the OpenAI adapter."""
    rendered = dotprompt_instance.render(
        _PROMPT_SOURCE,
        input={'country': 'France'},
    )

    # Verify rendering produced the expected messages
    assert len(rendered.messages) >= 2
    assert rendered.messages[0].role == Role.SYSTEM
    assert rendered.messages[-1].role == Role.USER

    # Call the real API
    response = await openai_adapter.generate(rendered)

    assert response.text is not None
    assert 'paris' in response.text.lower()
    logger.info('[e2e] rendered → response: %r', response.text)


# ---------------------------------------------------------------------------
# Test C – multi-turn conversation
# ---------------------------------------------------------------------------

_MULTITURN_SOURCE = """\
---
version: 2
config:
  model: gpt-5.2
  temperature: 1
---
{{ role("system") }}
You are a math tutor. Be concise.
{{ role("user") }}
What is 10 * 5?
{{ role("model") }}
50
{{ role("user") }}
Now divide that by 2.
"""


@pytest.mark.asyncio
async def test_multiturn_conversation(dotprompt_instance, openai_adapter) -> None:
    """Render a multi-turn prompt and verify the model follows context."""
    rendered = dotprompt_instance.render(
        _MULTITURN_SOURCE,
    )

    # Should have system + user + assistant + user = 4 messages
    assert len(rendered.messages) >= 4

    response = await openai_adapter.generate(rendered)

    assert response.text is not None
    assert '25' in response.text
    logger.info('[e2e] multi-turn response: %r', response.text)


# ---------------------------------------------------------------------------
# Test D – structured JSON schema output
# ---------------------------------------------------------------------------

# A strict schema with enum constraint to prove enforcement, not guessing.
_PERSON_SCHEMA = {
    'type': 'object',
    'properties': {
        'name': {'type': 'string'},
        'age': {'type': 'integer'},
        'favorite_color': {'type': 'string', 'enum': ['red', 'green', 'blue']},
    },
    'required': ['name', 'age', 'favorite_color'],
    'additionalProperties': False,
}


@pytest.mark.asyncio
async def test_structured_json_schema_output(openai_adapter) -> None:
    """Verify the model response conforms to a strict JSON schema.

    Uses additionalProperties=false and an enum constraint so that
    schema enforcement (not model luck) guarantees conformance.
    """
    rendered = RenderedPrompt(
        config={'model': MODEL, 'temperature': 1},
        messages=[
            Message(
                role=Role.SYSTEM,
                content=[TextPart(text='You are a data extraction assistant. Return ONLY valid JSON.')],
            ),
            Message(
                role=Role.USER,
                content=[TextPart(text=('Extract person info: Alice is 30 years old and her favorite color is blue.'))],
            ),
        ],
        output=PromptOutputConfig(format='json', schema_value=_PERSON_SCHEMA, file_name='output'),
    )

    response = await openai_adapter.generate(rendered)

    assert response.text is not None
    data = json.loads(response.text)

    # Validate against the JSON schema – this would fail if the provider
    # didn't enforce the schema and the model added extra fields or used
    # wrong types.
    jsonschema.validate(instance=data, schema=_PERSON_SCHEMA)

    # Verify concrete values
    assert data['name'] == 'Alice'
    assert isinstance(data['age'], int)
    assert data['age'] == 30
    assert data['favorite_color'] in ('red', 'green', 'blue')
    assert data['favorite_color'] == 'blue'

    # No extra keys (additionalProperties=false enforced by provider)
    assert set(data.keys()) == {'name', 'age', 'favorite_color'}
    logger.info('[e2e] structured output: %s', data)


# ---------------------------------------------------------------------------
# Test E – image input (vision)
# ---------------------------------------------------------------------------


@pytest.mark.asyncio
async def test_image_input_vision(openai_adapter) -> None:
    """Send an image via MediaPart and verify the model describes it."""
    rendered = RenderedPrompt(
        config={'model': MODEL, 'thinking': 'low', 'temperature': 1, 'max_tokens': 200},
        messages=[
            Message(
                role=Role.SYSTEM,
                content=[TextPart(text='You are a concise assistant. Describe images in one short sentence.')],
            ),
            Message(
                role=Role.USER,
                content=[
                    MediaPart(
                        media=MediaContent(
                            url=_PYTHON_PNG_URL,
                            contentType='image/png',
                        ),
                    ),
                    TextPart(
                        text='What programming language logo is shown in this image? Answer with just the language name.'
                    ),
                ],
            ),
        ],
    )

    response = await openai_adapter.generate(rendered)

    assert response.text is not None
    assert len(response.text.strip()) > 0
    assert 'python' in response.text.lower()
    assert response.usage is not None
    assert response.usage.prompt_tokens > 0
    logger.info('[e2e] image input response: %r', response.text)


# ===========================================================================
# NEW TESTS: batch run, output, @{{schema}}, model name, etc.
# ===========================================================================


# ---------------------------------------------------------------------------
# Test G – execute_batch() single item
# ---------------------------------------------------------------------------

_RUN_SINGLE_SOURCE = """\
---
version: 2
config:
  model: gpt-5.2
  temperature: 1
  max_tokens: 20
---
{{ role("user") }}
What is 3 + 7? Reply with just the number.
"""


@pytest.mark.asyncio
async def test_run_single(dotprompt_instance, openai_adapter) -> None:
    """Verify execute_batch() with a single input renders and generates correctly."""
    inputs = [{}]
    results = await execute_batch(
        _render_batch(dotprompt_instance, _RUN_SINGLE_SOURCE, inputs),
        inputs,
        openai_adapter,
    )

    assert len(results) == 1
    assert results[0].status == 'success'
    assert results[0].response is not None
    assert results[0].response.text is not None
    assert '10' in results[0].response.text
    assert results[0].response.usage is not None
    logger.info('[e2e] run single response: %r', results[0].response.text)


# ---------------------------------------------------------------------------
# Test H – execute_batch() with multiple inputs
# ---------------------------------------------------------------------------

_BATCH_SOURCE = """\
---
version: 2
config:
  model: gpt-5.2
  temperature: 1
  max_tokens: 30
---
{{ role("user") }}
What is the capital of {{ country }}? Answer in one word.
"""


@pytest.mark.asyncio
async def test_run_batch(dotprompt_instance, openai_adapter) -> None:
    """Verify execute_batch() executes multiple prompts with concurrency control."""
    inputs = [
        {'country': 'Japan'},
        {'country': 'Germany'},
        {'country': 'Brazil'},
    ]

    results = await execute_batch(
        _render_batch(dotprompt_instance, _BATCH_SOURCE, inputs),
        inputs,
        openai_adapter,
        max_workers=3,
    )

    assert len(results) == 3
    # Results are sorted by index
    assert [r.index for r in results] == [0, 1, 2]

    for r in results:
        assert r.status == 'success', f'Batch item {r.index} failed: {r.error}'
        assert r.response is not None
        assert r.response.text is not None
        assert r.elapsed > 0
        assert r.attempts == 1  # No retries expected
        logger.info('[e2e] batch[%d] country=%s → %r', r.index, r.input['country'], r.response.text)

    # Verify content correctness
    assert results[0].response is not None and results[0].response.text is not None
    assert results[1].response is not None and results[1].response.text is not None
    assert results[2].response is not None and results[2].response.text is not None
    assert 'tokyo' in results[0].response.text.lower()
    assert 'berlin' in results[1].response.text.lower()
    assert 'bras' in results[2].response.text.lower()  # Brasília / Brasilia


# ---------------------------------------------------------------------------
# Test I – execute_batch() with on_item_complete callback
# ---------------------------------------------------------------------------


@pytest.mark.asyncio
async def test_run_batch_with_callback(dotprompt_instance, openai_adapter) -> None:
    """Verify on_item_complete callback is invoked for each batch item."""
    completed_indices: list[int] = []

    def on_complete(result) -> None:
        completed_indices.append(result.index)

    inputs = [
        {'country': 'France'},
        {'country': 'Italy'},
    ]

    results = await execute_batch(
        _render_batch(dotprompt_instance, _BATCH_SOURCE, inputs),
        inputs,
        openai_adapter,
        max_workers=2,
        on_item_complete=on_complete,
    )

    assert len(results) == 2
    assert all(r.status == 'success' for r in results)
    # Callback was invoked for each item (order may vary due to concurrency)
    assert sorted(completed_indices) == [0, 1]
    logger.info('[e2e] batch callback indices: %s', completed_indices)


# ---------------------------------------------------------------------------
# Test J – output config: JSON format without schema
# ---------------------------------------------------------------------------


@pytest.mark.asyncio
async def test_output_json_format_no_schema(openai_adapter) -> None:
    """Verify output.format='json' without schema returns valid JSON."""
    rendered = RenderedPrompt(
        config={'model': MODEL, 'temperature': 1, 'max_tokens': 100},
        messages=[
            Message(
                role=Role.SYSTEM,
                content=[TextPart(text='You are a helpful assistant. Always respond in valid JSON.')],
            ),
            Message(
                role=Role.USER,
                content=[TextPart(text='List the 3 primary colors as a JSON array with key "colors".')],
            ),
        ],
        output=PromptOutputConfig(format='json', file_name='output'),
    )

    response = await openai_adapter.generate(rendered)

    assert response.text is not None
    data = json.loads(response.text)
    assert 'colors' in data
    assert isinstance(data['colors'], list)
    assert len(data['colors']) == 3
    logger.info('[e2e] json output (no schema): %s', data)


# ---------------------------------------------------------------------------
# Test K – output config: JSON format with complex nested schema
# ---------------------------------------------------------------------------

_BOOK_SCHEMA = {
    'type': 'object',
    'properties': {
        'title': {'type': 'string'},
        'author': {'type': 'string'},
        'year': {'type': 'integer'},
        'genres': {
            'type': 'array',
            'items': {'type': 'string'},
        },
    },
    'required': ['title', 'author', 'year', 'genres'],
    'additionalProperties': False,
}


@pytest.mark.asyncio
async def test_output_json_with_nested_schema(openai_adapter) -> None:
    """Verify structured output with a nested schema (array field)."""
    rendered = RenderedPrompt(
        config={'model': MODEL, 'temperature': 1},
        messages=[
            Message(
                role=Role.SYSTEM,
                content=[TextPart(text='You are a book data extraction assistant.')],
            ),
            Message(
                role=Role.USER,
                content=[
                    TextPart(
                        text=(
                            'Extract book info: "1984" by George Orwell, published in 1949. '
                            'Genres: dystopian, science fiction.'
                        )
                    )
                ],
            ),
        ],
        output=PromptOutputConfig(format='json', schema_value=_BOOK_SCHEMA, file_name='output'),
    )

    response = await openai_adapter.generate(rendered)

    assert response.text is not None
    data = json.loads(response.text)
    jsonschema.validate(instance=data, schema=_BOOK_SCHEMA)

    assert data['title'] == '1984'
    assert 'orwell' in data['author'].lower()
    assert data['year'] == 1949
    assert isinstance(data['genres'], list)
    assert len(data['genres']) >= 2
    assert set(data.keys()) == {'title', 'author', 'year', 'genres'}
    logger.info('[e2e] nested schema output: %s', data)


# ---------------------------------------------------------------------------
# Test L – {{ SCHEMA }} auto-variable in template
# ---------------------------------------------------------------------------

_SCHEMA_VAR_PROMPT = """\
---
version: 2
config:
  model: gpt-5.2
  temperature: 1
  max_tokens: 200
output:
  format: json
  schema:
    type: object
    properties:
      city:
        type: string
      population:
        type: integer
    required:
      - city
      - population
    additionalProperties: false
---
{{ role("system") }}
You are a data extraction assistant. Return ONLY valid JSON conforming to this schema:
{{ SCHEMA }}
{{ role("user") }}
Tell me about {{ city }}: what is the city name and its approximate population?
"""


@pytest.mark.asyncio
async def test_schema_auto_variable(dotprompt_instance, openai_adapter) -> None:
    """Verify {{ SCHEMA }} is injected into the rendered template and the output conforms."""
    rendered = dotprompt_instance.render(
        _SCHEMA_VAR_PROMPT,
        input={'city': 'Tokyo'},
    )

    # Verify SCHEMA was rendered into the system message
    system_msg = rendered.messages[0]
    assert system_msg.role == Role.SYSTEM
    system_text = ''.join(p.text for p in system_msg.content if hasattr(p, 'text'))
    assert '"city"' in system_text
    assert '"population"' in system_text

    # Call the real API
    response = await openai_adapter.generate(rendered)

    assert response.text is not None
    data = json.loads(response.text)
    assert 'city' in data
    assert 'population' in data
    assert isinstance(data['population'], int)
    assert data['population'] > 0
    logger.info('[e2e] SCHEMA output: %s', data)


# ---------------------------------------------------------------------------
# Test M – dynamic model name via frontmatter variable
# ---------------------------------------------------------------------------

_DYNAMIC_MODEL_PROMPT = """\
---
version: 2
config:
  model: "{{ model_name }}"
  temperature: 1
  max_tokens: 20
---
{{ role("user") }}
What is 5 * 5? Reply with just the number.
"""


@pytest.mark.asyncio
async def test_dynamic_model_name(dotprompt_instance, openai_adapter) -> None:
    """Verify model name can be set dynamically via frontmatter variables."""
    rendered = dotprompt_instance.render(
        _DYNAMIC_MODEL_PROMPT,
        input={'model_name': MODEL},
    )

    # Verify the model was resolved in the config
    assert rendered.config is not None
    assert rendered.config.get('model') == MODEL

    response = await openai_adapter.generate(rendered)

    assert response.text is not None
    assert '25' in response.text
    assert response.model is not None
    logger.info('[e2e] dynamic model=%s  text=%r', response.model, response.text)


# ---------------------------------------------------------------------------
# Test N – model name from frontmatter (static)
# ---------------------------------------------------------------------------

_STATIC_MODEL_PROMPT = """\
---
version: 2
config:
  model: gpt-5.2
  temperature: 1
  max_tokens: 20
---
{{ role("user") }}
What is 8 + 8? Reply with just the number.
"""


@pytest.mark.asyncio
async def test_static_model_name(dotprompt_instance, openai_adapter) -> None:
    """Verify model name set statically in frontmatter works correctly."""
    rendered = dotprompt_instance.render(
        _STATIC_MODEL_PROMPT,
    )

    response = await openai_adapter.generate(rendered)

    assert response.text is not None
    assert '16' in response.text
    # The response model should reflect the requested model family
    assert response.model is not None
    assert 'gpt' in response.model.lower()
    logger.info('[e2e] static model=%s  text=%r', response.model, response.text)


# ---------------------------------------------------------------------------
# Test O – JSON Schema object through Dotprompt → structured output pipeline
# ---------------------------------------------------------------------------

_JSON_SCHEMA_PROMPT = """\
---
version: 2
config:
  model: gpt-5.2
  temperature: 1
output:
  format: json
  schema:
    type: object
    properties:
      name:
        type: string
      age:
        type: integer
      employed:
        type: boolean
    required:
      - name
      - age
      - employed
    additionalProperties: false
---
{{ role("system") }}
You are a data extraction assistant. Return structured JSON.
{{ role("user") }}
Extract person info: Bob Smith is 42 years old and currently employed.
"""


@pytest.mark.asyncio
async def test_json_schema_object_to_structured_output(dotprompt_instance, openai_adapter) -> None:
    """Verify JSON Schema object in frontmatter produces valid structured output."""
    rendered = dotprompt_instance.render(
        _JSON_SCHEMA_PROMPT,
    )

    # Verify the output schema was resolved
    assert rendered.output is not None
    assert rendered.output.format == 'json'
    assert rendered.output.schema_value is not None

    response = await openai_adapter.generate(rendered)

    assert response.text is not None
    data = json.loads(response.text)
    assert data['name'] == 'Bob Smith'
    assert data['age'] == 42
    assert data['employed'] is True
    logger.info('[e2e] json schema object output: %s', data)


# ---------------------------------------------------------------------------
# Test P – batch run with structured JSON output
# ---------------------------------------------------------------------------

_BATCH_JSON_SOURCE = """\
---
version: 2
config:
  model: gpt-5.2
  temperature: 1
output:
  format: json
  schema:
    type: object
    properties:
      animal:
        type: string
      legs:
        type: integer
    required:
      - animal
      - legs
    additionalProperties: false
---
{{ role("user") }}
How many legs does a {{ animal }} have? Return as JSON.
"""


@pytest.mark.asyncio
async def test_run_batch_with_json_output(dotprompt_instance, openai_adapter) -> None:
    """Verify batch execution produces structured JSON output for each item."""
    inputs = [
        {'animal': 'cat'},
        {'animal': 'spider'},
    ]

    results = await execute_batch(
        _render_batch(dotprompt_instance, _BATCH_JSON_SOURCE, inputs),
        inputs,
        openai_adapter,
        max_workers=2,
    )

    assert len(results) == 2
    for r in results:
        assert r.status == 'success', f'Batch item {r.index} failed: {r.error}'
        assert r.response is not None
        assert r.response.text is not None
        data = json.loads(r.response.text)
        assert 'animal' in data
        assert 'legs' in data
        assert isinstance(data['legs'], int)

    # Verify specific values
    assert results[0].response is not None and results[0].response.text is not None
    assert results[1].response is not None and results[1].response.text is not None
    cat_data = json.loads(results[0].response.text)
    spider_data = json.loads(results[1].response.text)
    assert cat_data['legs'] == 4
    assert spider_data['legs'] == 8
    logger.info('[e2e] batch json: cat=%s, spider=%s', cat_data, spider_data)


# ---------------------------------------------------------------------------
# Test Q – {{ SCHEMA }} combined with batch run
# ---------------------------------------------------------------------------

_BATCH_SCHEMA_VAR_SOURCE = """\
---
version: 2
config:
  model: gpt-5.2
  temperature: 1
  max_tokens: 100
output:
  format: json
  schema:
    type: object
    properties:
      name:
        type: string
      category:
        type: string
        enum: ["fruit", "vegetable", "grain"]
    required:
      - name
      - category
    additionalProperties: false
---
{{ role("system") }}
Classify the food item. Output JSON conforming to: {{ SCHEMA }}
{{ role("user") }}
Classify: {{ food }}
"""


@pytest.mark.asyncio
async def test_run_batch_with_schema_variable(dotprompt_instance, openai_adapter) -> None:
    """Verify batch run with {{ SCHEMA }} auto-variable produces correct classifications."""
    inputs = [
        {'food': 'apple'},
        {'food': 'carrot'},
    ]

    results = await execute_batch(
        _render_batch(dotprompt_instance, _BATCH_SCHEMA_VAR_SOURCE, inputs),
        inputs,
        openai_adapter,
        max_workers=2,
    )

    assert len(results) == 2
    for r in results:
        assert r.status == 'success', f'Batch item {r.index} failed: {r.error}'
        assert r.response is not None
        assert r.response.text is not None
        data = json.loads(r.response.text)
        assert data['category'] in ('fruit', 'vegetable', 'grain')

    assert results[0].response is not None and results[0].response.text is not None
    assert results[1].response is not None and results[1].response.text is not None
    apple_data = json.loads(results[0].response.text)
    carrot_data = json.loads(results[1].response.text)
    assert apple_data['category'] == 'fruit'
    assert carrot_data['category'] == 'vegetable'
    logger.info('[e2e] batch SCHEMA: apple=%s, carrot=%s', apple_data, carrot_data)


# ---------------------------------------------------------------------------
# Test R – response metadata: model and finish_reason
# ---------------------------------------------------------------------------


@pytest.mark.asyncio
async def test_response_metadata(openai_adapter) -> None:
    """Verify response contains model name, usage, and finish_reason metadata."""
    rendered = RenderedPrompt(
        config={'model': MODEL, 'temperature': 1, 'max_tokens': 10},
        messages=[
            Message(role=Role.USER, content=[TextPart(text='Say "hello".')]),
        ],
    )

    response = await openai_adapter.generate(rendered)

    assert response.text is not None
    assert response.model is not None
    assert 'gpt' in response.model.lower()
    assert response.finish_reason in ('stop', 'length')
    assert response.usage is not None
    assert response.usage.prompt_tokens > 0
    assert response.usage.completion_tokens > 0
    assert response.usage.total_tokens == response.usage.prompt_tokens + response.usage.completion_tokens
    logger.info(
        '[e2e] metadata: model=%s finish=%s tokens=%d',
        response.model,
        response.finish_reason,
        response.usage.total_tokens,
    )


@pytest.mark.asyncio
async def test_image_generate_openai(openai_image_adapter) -> None:
    """Generate an image with OpenAI image model and validate PNG output bytes."""
    rendered = RenderedPrompt(
        config={
            'model': IMAGE_MODEL,
            'resolution': '1024x1024',
            'aspect_ratio': '1:1',
            'transparent': False,
        },
        output=PromptOutputConfig(format='image', file_name='image'),
        messages=[
            Message(role=Role.USER, content=[TextPart(text='A minimal flat icon of a red apple on white background')]),
        ],
    )

    response = await openai_image_adapter.generate(rendered)

    assert response.images is not None
    assert len(response.images) > 0
    first = response.images[0]
    assert first.mime_type == 'image/png'
    assert first.data.startswith(b'\x89PNG\r\n\x1a\n')
    logger.info('[e2e] openai image generate bytes=%d', len(first.data))


@pytest.mark.asyncio
async def test_image_edit_openai(openai_image_adapter) -> None:
    """Edit an input image with OpenAI image model and validate PNG output bytes."""
    rendered = RenderedPrompt(
        config={
            'model': IMAGE_MODEL,
            'resolution': '1024x1024',
            'aspect_ratio': '1:1',
            'transparent': False,
        },
        output=PromptOutputConfig(format='image', file_name='image'),
        messages=[
            Message(
                role=Role.USER,
                content=[
                    MediaPart(media=MediaContent(url=_PYTHON_PNG_URL, contentType='image/png')),
                    TextPart(text='Keep the shape, change the color to blue.'),
                ],
            ),
        ],
    )

    response = await openai_image_adapter.generate(rendered)

    assert response.images is not None
    assert len(response.images) > 0
    first = response.images[0]
    assert first.mime_type == 'image/png'
    assert first.data.startswith(b'\x89PNG\r\n\x1a\n')
    logger.info('[e2e] openai image edit bytes=%d', len(first.data))
