"""Tests for AnthropicProvider._tool_result_block image_url conversion.

Regression for: tool results containing OpenAI-format image_url blocks
(e.g. from read_file on an image file, via build_image_content_blocks)
were passed to Anthropic unconverted, causing silent image drops with a
"Non-transient LLM error with image content, retrying without images"
warning.

Also tests that bare dicts without a "type" field are coerced to text
blocks, fixing Anthropic "content.0.type: Field required" rejections (#3993).
"""

from types import SimpleNamespace

from nanobot.providers.anthropic_provider import AnthropicProvider


def test_tool_result_block_converts_image_url_in_list_content():
    """image_url blocks inside tool_result list content must be translated
    to Anthropic-native image blocks; sibling text blocks pass through."""
    msg = {
        "role": "tool",
        "tool_call_id": "call_1",
        "content": [
            {
                "type": "image_url",
                "image_url": {"url": "data:image/png;base64,AAAA"},
                "_meta": {"path": "/tmp/x.png"},
            },
            {"type": "text", "text": "(Image file: /tmp/x.png)"},
        ],
    }
    block = AnthropicProvider._tool_result_block(msg)

    assert block["type"] == "tool_result"
    assert block["tool_use_id"] == "call_1"
    content = block["content"]
    assert isinstance(content, list)
    assert content[0] == {
        "type": "image",
        "source": {
            "type": "base64",
            "media_type": "image/png",
            "data": "AAAA",
        },
    }
    assert content[1] == {"type": "text", "text": "(Image file: /tmp/x.png)"}


def test_tool_result_block_preserves_string_content():
    """String content must be passed through unchanged; the image-conversion
    path for lists must not affect the string path."""
    msg = {
        "role": "tool",
        "tool_call_id": "call_2",
        "content": "plain tool output",
    }
    block = AnthropicProvider._tool_result_block(msg)

    assert block["type"] == "tool_result"
    assert block["tool_use_id"] == "call_2"
    assert block["content"] == "plain tool output"


def test_convert_user_content_coerces_typeless_dict():
    """Bare dicts without a "type" field must be coerced to text blocks.
    Regression for #3993: tools returning plain dicts caused Anthropic to
    reject the request with "content.0.type: Field required"."""
    result = AnthropicProvider._convert_user_content([
        {"foo": "bar"},
        {"type": "text", "text": "ok"},
    ])
    assert result[0] == {"type": "text", "text": '{"foo": "bar"}'}
    assert result[1] == {"type": "text", "text": "ok"}


def test_convert_user_content_coerces_mixed_typeless():
    """Multiple typeless items and non-dict items are all handled."""
    result = AnthropicProvider._convert_user_content([
        42,
        {"key": "val"},
    ])
    assert result[0] == {"type": "text", "text": "42"}
    assert result[1] == {"type": "text", "text": '{"key": "val"}'}


def test_assistant_blocks_coerce_typeless_dict_to_json_text():
    blocks = AnthropicProvider._assistant_blocks({
        "role": "assistant",
        "content": [{"answer": "ok", "count": 2}],
    })

    assert blocks == [{"type": "text", "text": '{"answer": "ok", "count": 2}'}]


def test_convert_assistant_message_repairs_history_tool_arguments():
    blocks = AnthropicProvider._assistant_blocks({
        "role": "assistant",
        "content": None,
        "tool_calls": [{
            "id": "toolu_1",
            "function": {"name": "read_file", "arguments": '{path:"foo.txt"}'},
        }],
    })

    assert blocks[0]["type"] == "tool_use"
    assert blocks[0]["input"] == {"path": "foo.txt"}


def test_anthropic_sanitizes_invalid_tool_ids_consistently():
    """Invalid restored IDs must be valid for Anthropic and keep pairs matched."""
    blocks = AnthropicProvider._assistant_blocks({
        "role": "assistant",
        "content": None,
        "tool_calls": [{
            "id": "call_abc|rs.same",
            "function": {"name": "read_file", "arguments": "{}"},
        }],
    })
    result = AnthropicProvider._tool_result_block({
        "role": "tool",
        "tool_call_id": "call_abc|rs.same",
        "content": "ok",
    })

    tool_id = blocks[0]["id"]
    assert tool_id == result["tool_use_id"]
    assert tool_id != "call_abc|rs.same"
    assert all(ch.isalnum() or ch in "_-" for ch in tool_id)


def test_anthropic_sanitized_tool_ids_avoid_simple_collisions():
    """Replacement-only sanitizing would collapse these two ids to call_a."""
    blocks = AnthropicProvider._assistant_blocks({
        "role": "assistant",
        "content": None,
        "tool_calls": [
            {"id": "call.a", "function": {"name": "a", "arguments": "{}"}},
            {"id": "call|a", "function": {"name": "b", "arguments": "{}"}},
        ],
    })

    ids = [block["id"] for block in blocks if block["type"] == "tool_use"]
    assert len(ids) == len(set(ids)) == 2
    assert all(all(ch.isalnum() or ch in "_-" for ch in tool_id) for tool_id in ids)


def test_anthropic_convert_messages_remaps_duplicate_history_tool_ids():
    provider = AnthropicProvider.__new__(AnthropicProvider)

    _system, messages = provider._convert_messages([
        {"role": "user", "content": "check both files"},
        {
            "role": "assistant",
            "content": "",
            "tool_calls": [
                {
                    "id": "toolu_same",
                    "type": "function",
                    "function": {"name": "read_file", "arguments": '{"path":"a.txt"}'},
                },
                {
                    "id": "toolu_same",
                    "type": "function",
                    "function": {"name": "read_file", "arguments": '{"path":"b.txt"}'},
                },
            ],
        },
        {"role": "tool", "tool_call_id": "toolu_same", "name": "read_file", "content": "a"},
        {"role": "tool", "tool_call_id": "toolu_same", "name": "read_file", "content": "b"},
    ])

    tool_uses = [
        block
        for block in messages[1]["content"]
        if isinstance(block, dict) and block.get("type") == "tool_use"
    ]
    tool_results = [
        block
        for block in messages[2]["content"]
        if isinstance(block, dict) and block.get("type") == "tool_result"
    ]
    tool_use_ids = [block["id"] for block in tool_uses]
    tool_result_ids = [block["tool_use_id"] for block in tool_results]

    assert len(tool_use_ids) == 2
    assert tool_use_ids[0] == "toolu_same"
    assert tool_use_ids[1] == "toolu_same__dedupe_2"
    assert tool_result_ids == tool_use_ids
    assert tool_uses[0]["input"] == {"path": "a.txt"}
    assert tool_uses[1]["input"] == {"path": "b.txt"}


def test_anthropic_parse_response_remaps_duplicate_tool_use_ids():
    response = SimpleNamespace(
        content=[
            SimpleNamespace(
                type="tool_use",
                id="toolu_same",
                name="read_file",
                input={"path": "a.txt"},
            ),
            SimpleNamespace(
                type="tool_use",
                id="toolu_same",
                name="read_file",
                input={"path": "b.txt"},
            ),
        ],
        stop_reason="tool_use",
        usage=None,
    )

    result = AnthropicProvider._parse_response(response)

    assert len(result.tool_calls) == 2
    assert result.tool_calls[0].id == "toolu_same"
    assert result.tool_calls[0].arguments == {"path": "a.txt"}
    assert result.tool_calls[1].id != "toolu_same"
    assert result.tool_calls[1].id.startswith("toolu_")
    assert result.tool_calls[1].arguments == {"path": "b.txt"}
