"""Regression: the final Anthropic request must carry no blank text block.

`convert_messages_to_anthropic` runs per-message converters that coerce blanks
they produce, but a blank/whitespace-only text block can be synthesized *after*
those run — a compression summary message, a role merge, or an upstream message
whose content arrives pre-shaped as content blocks. Any single blank text block
makes Anthropic reject the whole request with HTTP 400 "text content blocks must
contain non-whitespace text", which then replays on every turn and wedges the
session.

`_scrub_blank_text_blocks` is the final backstop on the fully-assembled message
list, and the system-block path coerces blanks at extraction time (a blank block
carrying a cache_control breakpoint cannot be dropped).
Ref #69512 / #70909 (follow-up: request-level guard, not just per-message).
"""
from agent.anthropic_adapter import (
    _EMPTY_TEXT_PLACEHOLDER,
    convert_messages_to_anthropic,
)


def _all_text_blocks(messages):
    for m in messages:
        content = m.get("content")
        if isinstance(content, list):
            for b in content:
                if isinstance(b, dict) and b.get("type") == "text":
                    yield b


def _assert_no_blank(system, messages):
    if isinstance(system, list):
        for b in system:
            if isinstance(b, dict) and b.get("type") == "text":
                assert b["text"].strip(), f"blank text block in system: {b!r}"
    for b in _all_text_blocks(messages):
        assert b["text"].strip(), f"blank text block survived: {b!r}"


def test_pre_shaped_blank_block_in_user_content_is_coerced():
    # Content arrives already as blocks with a blank text part — the per-message
    # user converter does not walk-and-coerce these, so the final guard must.
    messages = [
        {"role": "user", "content": [
            {"type": "text", "text": "   "},
            {"type": "text", "text": "real question"},
        ]},
    ]
    system, result = convert_messages_to_anthropic(messages)
    _assert_no_blank(system, result)


def test_blank_summary_style_user_message_is_coerced():
    # A compression summary that came back empty becomes a whitespace user
    # message; it must not reach the wire as a blank block.
    messages = [
        {"role": "user", "content": "hi"},
        {"role": "assistant", "content": "answer"},
        {"role": "user", "content": "\n\n"},  # empty "summary"-style turn
    ]
    system, result = convert_messages_to_anthropic(messages)
    _assert_no_blank(system, result)


def test_blank_system_block_is_coerced():
    messages = [
        {"role": "system", "content": [
            {"type": "text", "text": "  ", "cache_control": {"type": "ephemeral"}},
        ]},
        {"role": "user", "content": "hello"},
    ]
    system, result = convert_messages_to_anthropic(messages)
    _assert_no_blank(system, result)


def test_prepended_leading_user_turn_is_not_blank():
    """The root cause: _ensure_leading_user_turn prepends a placeholder turn when
    messages[0] is not a user turn (post-compaction histories start with an
    assistant summary). That placeholder must be NON-whitespace — a bare " "
    is itself a blank text block and 400s the whole request, wedging every turn.
    Bedrock's equivalent already uses the shared placeholder.
    """
    messages = [
        {"role": "assistant", "content": "summary of earlier turns"},
        {"role": "user", "content": "continue"},
    ]
    system, result = convert_messages_to_anthropic(messages)
    assert result[0]["role"] == "user", "a leading user turn must be prepended"
    _assert_no_blank(system, result)


def test_real_text_is_left_untouched():
    # A real, non-blank turn keeps its text verbatim and is never replaced by
    # the placeholder (the guard only touches blank blocks).
    messages = [
        {"role": "user", "content": "what is 2+2?"},
    ]
    system, result = convert_messages_to_anthropic(messages)
    # Content may be a plain string or a list of blocks; collect text either way.
    texts = []
    for m in result:
        c = m.get("content")
        if isinstance(c, str):
            texts.append(c)
        elif isinstance(c, list):
            texts.extend(b.get("text", "") for b in c if isinstance(b, dict) and b.get("type") == "text")
    assert "what is 2+2?" in texts
    assert _EMPTY_TEXT_PLACEHOLDER not in texts
