/*
 * Copyright 2025 CloudWeGo Authors
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package schema

import (
	"context"
	"reflect"
	"testing"

	"github.com/stretchr/testify/assert"
	"github.com/stretchr/testify/require"
)

func TestConcatAgenticMessages(t *testing.T) {
	t.Run("single message", func(t *testing.T) {
		msg := &AgenticMessage{
			Role: AgenticRoleTypeAssistant,
			ContentBlocks: []*ContentBlock{
				{
					Type: ContentBlockTypeAssistantGenText,
					AssistantGenText: &AssistantGenText{
						Text: "Hello",
					},
				},
			},
		}

		result, err := ConcatAgenticMessages([]*AgenticMessage{msg})
		assert.NoError(t, err)
		assert.Equal(t, msg, result)
	})

	t.Run("nil message in stream", func(t *testing.T) {
		msgs := []*AgenticMessage{
			{Role: AgenticRoleTypeAssistant},
			nil,
			{Role: AgenticRoleTypeAssistant},
		}

		_, err := ConcatAgenticMessages(msgs)
		assert.Error(t, err)
		assert.ErrorContains(t, err, "message at index 1 is nil")
	})

	t.Run("different roles", func(t *testing.T) {
		msgs := []*AgenticMessage{
			{Role: AgenticRoleTypeUser},
			{Role: AgenticRoleTypeAssistant},
		}

		_, err := ConcatAgenticMessages(msgs)
		assert.Error(t, err)
		assert.ErrorContains(t, err, "cannot concat messages with different roles")
	})

	t.Run("concat text blocks", func(t *testing.T) {
		msgs := []*AgenticMessage{
			{
				Role: AgenticRoleTypeAssistant,
				ContentBlocks: []*ContentBlock{
					{
						Type: ContentBlockTypeAssistantGenText,
						AssistantGenText: &AssistantGenText{
							Text: "Hello ",
						},
						StreamingMeta: &StreamingMeta{Index: 0},
					},
				},
			},
			{
				Role: AgenticRoleTypeAssistant,
				ContentBlocks: []*ContentBlock{
					{
						Type: ContentBlockTypeAssistantGenText,
						AssistantGenText: &AssistantGenText{
							Text: "World!",
						},
						StreamingMeta: &StreamingMeta{Index: 0},
					},
				},
			},
		}

		result, err := ConcatAgenticMessages(msgs)
		assert.NoError(t, err)
		assert.Equal(t, AgenticRoleTypeAssistant, result.Role)
		assert.Len(t, result.ContentBlocks, 1)
		assert.Equal(t, "Hello World!", result.ContentBlocks[0].AssistantGenText.Text)
	})

	t.Run("concat reasoning with nil index", func(t *testing.T) {
		msgs := []*AgenticMessage{
			{
				Role: AgenticRoleTypeAssistant,
				ContentBlocks: []*ContentBlock{
					{
						Type: ContentBlockTypeReasoning,
						Reasoning: &Reasoning{
							Text: "First ",
						},
						StreamingMeta: &StreamingMeta{Index: 0},
					},
				},
			},
			{
				Role: AgenticRoleTypeAssistant,
				ContentBlocks: []*ContentBlock{
					{
						Type: ContentBlockTypeReasoning,
						Reasoning: &Reasoning{
							Text: "Second",
						},
						StreamingMeta: &StreamingMeta{Index: 0},
					},
				},
			},
		}

		result, err := ConcatAgenticMessages(msgs)
		assert.NoError(t, err)
		assert.Len(t, result.ContentBlocks, 1)
		assert.Equal(t, "First Second", result.ContentBlocks[0].Reasoning.Text)
	})

	t.Run("concat reasoning with index", func(t *testing.T) {
		msgs := []*AgenticMessage{
			{
				Role: AgenticRoleTypeAssistant,
				ContentBlocks: []*ContentBlock{
					{
						Type: ContentBlockTypeReasoning,
						Reasoning: &Reasoning{
							Text: "Part1-",
						},
						StreamingMeta: &StreamingMeta{Index: 0},
					},
				},
			},
			{
				Role: AgenticRoleTypeAssistant,
				ContentBlocks: []*ContentBlock{
					{
						Type: ContentBlockTypeReasoning,
						Reasoning: &Reasoning{
							Text: "Part3",
						},
						StreamingMeta: &StreamingMeta{Index: 0},
					},
				},
			},
		}

		result, err := ConcatAgenticMessages(msgs)
		assert.NoError(t, err)
		assert.Len(t, result.ContentBlocks, 1)
		assert.Equal(t, "Part1-Part3", result.ContentBlocks[0].Reasoning.Text)
	})

	t.Run("concat user input text", func(t *testing.T) {
		msgs := []*AgenticMessage{
			{
				Role: AgenticRoleTypeAssistant,
				ContentBlocks: []*ContentBlock{
					{
						Type: ContentBlockTypeAssistantGenText,
						AssistantGenText: &AssistantGenText{
							Text: "Hello ",
						},
						StreamingMeta: &StreamingMeta{Index: 0},
					},
				},
			},
			{
				Role: AgenticRoleTypeAssistant,
				ContentBlocks: []*ContentBlock{
					{
						Type: ContentBlockTypeAssistantGenText,
						AssistantGenText: &AssistantGenText{
							Text: "World!",
						},
						StreamingMeta: &StreamingMeta{Index: 0},
					},
				},
			},
		}

		result, err := ConcatAgenticMessages(msgs)
		assert.NoError(t, err)
		assert.Len(t, result.ContentBlocks, 1)
		assert.Equal(t, "Hello World!", result.ContentBlocks[0].AssistantGenText.Text)
	})

	t.Run("concat assistant gen image", func(t *testing.T) {
		base1 := "1"
		base2 := "2"

		msgs := []*AgenticMessage{
			{
				Role: AgenticRoleTypeAssistant,
				ContentBlocks: []*ContentBlock{
					{
						Type: ContentBlockTypeAssistantGenImage,
						AssistantGenImage: &AssistantGenImage{
							Base64Data: base1,
						},
						StreamingMeta: &StreamingMeta{Index: 0},
					},
				},
			},
			{
				Role: AgenticRoleTypeAssistant,
				ContentBlocks: []*ContentBlock{
					{
						Type: ContentBlockTypeAssistantGenImage,
						AssistantGenImage: &AssistantGenImage{
							Base64Data: base2,
						},
						StreamingMeta: &StreamingMeta{Index: 0},
					},
				},
			},
		}

		result, err := ConcatAgenticMessages(msgs)
		assert.NoError(t, err)
		assert.Len(t, result.ContentBlocks, 1)
		assert.Equal(t, "12", result.ContentBlocks[0].AssistantGenImage.Base64Data)
	})

	t.Run("concat user input audio - should error", func(t *testing.T) {
		url1 := "https://example.com/audio1.mp3"
		url2 := "https://example.com/audio2.mp3"

		msgs := []*AgenticMessage{
			{
				Role: AgenticRoleTypeUser,
				ContentBlocks: []*ContentBlock{
					{
						Type: ContentBlockTypeUserInputAudio,
						UserInputAudio: &UserInputAudio{
							URL: url1,
						},
						StreamingMeta: &StreamingMeta{Index: 0},
					},
				},
			},
			{
				Role: AgenticRoleTypeUser,
				ContentBlocks: []*ContentBlock{
					{
						Type: ContentBlockTypeUserInputAudio,
						UserInputAudio: &UserInputAudio{
							URL: url2,
						},
						StreamingMeta: &StreamingMeta{Index: 0},
					},
				},
			},
		}

		_, err := ConcatAgenticMessages(msgs)
		assert.Error(t, err)
		assert.ErrorContains(t, err, "cannot concat multiple user input audios")
	})

	t.Run("concat user input video - should error", func(t *testing.T) {
		url1 := "https://example.com/video1.mp4"
		url2 := "https://example.com/video2.mp4"

		msgs := []*AgenticMessage{
			{
				Role: AgenticRoleTypeUser,
				ContentBlocks: []*ContentBlock{
					{
						Type: ContentBlockTypeUserInputVideo,
						UserInputVideo: &UserInputVideo{
							URL: url1,
						},
						StreamingMeta: &StreamingMeta{Index: 0},
					},
				},
			},
			{
				Role: AgenticRoleTypeUser,
				ContentBlocks: []*ContentBlock{
					{
						Type: ContentBlockTypeUserInputVideo,
						UserInputVideo: &UserInputVideo{
							URL: url2,
						},
						StreamingMeta: &StreamingMeta{Index: 0},
					},
				},
			},
		}

		_, err := ConcatAgenticMessages(msgs)
		assert.Error(t, err)
		assert.ErrorContains(t, err, "cannot concat multiple user input videos")
	})

	t.Run("concat assistant gen text", func(t *testing.T) {
		msgs := []*AgenticMessage{
			{
				Role: AgenticRoleTypeAssistant,
				ContentBlocks: []*ContentBlock{
					{
						Type: ContentBlockTypeAssistantGenText,
						AssistantGenText: &AssistantGenText{
							Text: "Generated ",
						},
						StreamingMeta: &StreamingMeta{Index: 0},
					},
				},
			},
			{
				Role: AgenticRoleTypeAssistant,
				ContentBlocks: []*ContentBlock{
					{
						Type: ContentBlockTypeAssistantGenText,
						AssistantGenText: &AssistantGenText{
							Text: "Text",
						},
						StreamingMeta: &StreamingMeta{Index: 0},
					},
				},
			},
		}

		result, err := ConcatAgenticMessages(msgs)
		assert.NoError(t, err)
		assert.Len(t, result.ContentBlocks, 1)
		assert.Equal(t, "Generated Text", result.ContentBlocks[0].AssistantGenText.Text)
	})

	t.Run("concat assistant gen image", func(t *testing.T) {
		msgs := []*AgenticMessage{
			{
				Role: AgenticRoleTypeAssistant,
				ContentBlocks: []*ContentBlock{
					{
						Type: ContentBlockTypeAssistantGenImage,
						AssistantGenImage: &AssistantGenImage{
							Base64Data: "part1",
						},
						StreamingMeta: &StreamingMeta{Index: 0},
					},
				},
			},
			{
				Role: AgenticRoleTypeAssistant,
				ContentBlocks: []*ContentBlock{
					{
						Type: ContentBlockTypeAssistantGenImage,
						AssistantGenImage: &AssistantGenImage{
							Base64Data: "part2",
						},
						StreamingMeta: &StreamingMeta{Index: 0},
					},
				},
			},
		}

		result, err := ConcatAgenticMessages(msgs)
		assert.NoError(t, err)
		assert.Len(t, result.ContentBlocks, 1)
		assert.Equal(t, "part1part2", result.ContentBlocks[0].AssistantGenImage.Base64Data)
	})

	t.Run("concat assistant gen audio", func(t *testing.T) {
		msgs := []*AgenticMessage{
			{
				Role: AgenticRoleTypeAssistant,
				ContentBlocks: []*ContentBlock{
					{
						Type: ContentBlockTypeAssistantGenAudio,
						AssistantGenAudio: &AssistantGenAudio{
							Base64Data: "audio1",
						},
						StreamingMeta: &StreamingMeta{Index: 0},
					},
				},
			},
			{
				Role: AgenticRoleTypeAssistant,
				ContentBlocks: []*ContentBlock{
					{
						Type: ContentBlockTypeAssistantGenAudio,
						AssistantGenAudio: &AssistantGenAudio{
							Base64Data: "audio2",
						},
						StreamingMeta: &StreamingMeta{Index: 0},
					},
				},
			},
		}

		result, err := ConcatAgenticMessages(msgs)
		assert.NoError(t, err)
		assert.Len(t, result.ContentBlocks, 1)
		assert.Equal(t, "audio1audio2", result.ContentBlocks[0].AssistantGenAudio.Base64Data)
	})

	t.Run("concat assistant gen video", func(t *testing.T) {
		msgs := []*AgenticMessage{
			{
				Role: AgenticRoleTypeAssistant,
				ContentBlocks: []*ContentBlock{
					{
						Type: ContentBlockTypeAssistantGenVideo,
						AssistantGenVideo: &AssistantGenVideo{
							Base64Data: "video1",
						},
						StreamingMeta: &StreamingMeta{Index: 0},
					},
				},
			},
			{
				Role: AgenticRoleTypeAssistant,
				ContentBlocks: []*ContentBlock{
					{
						Type: ContentBlockTypeAssistantGenVideo,
						AssistantGenVideo: &AssistantGenVideo{
							Base64Data: "video2",
						},
						StreamingMeta: &StreamingMeta{Index: 0},
					},
				},
			},
		}

		result, err := ConcatAgenticMessages(msgs)
		assert.NoError(t, err)
		assert.Len(t, result.ContentBlocks, 1)
		assert.Equal(t, "video1video2", result.ContentBlocks[0].AssistantGenVideo.Base64Data)
	})

	t.Run("concat function tool call", func(t *testing.T) {
		msgs := []*AgenticMessage{
			{
				Role: AgenticRoleTypeAssistant,
				ContentBlocks: []*ContentBlock{
					{
						Type: ContentBlockTypeFunctionToolCall,
						FunctionToolCall: &FunctionToolCall{
							CallID:    "call_123",
							Name:      "get_weather",
							Arguments: `{"location`,
						},
						StreamingMeta: &StreamingMeta{Index: 0},
					},
				},
			},
			{
				Role: AgenticRoleTypeAssistant,
				ContentBlocks: []*ContentBlock{
					{
						Type: ContentBlockTypeFunctionToolCall,
						FunctionToolCall: &FunctionToolCall{
							Arguments: `":"NYC"}`,
						},
						StreamingMeta: &StreamingMeta{Index: 0},
					},
				},
			},
		}

		result, err := ConcatAgenticMessages(msgs)
		assert.NoError(t, err)
		assert.Len(t, result.ContentBlocks, 1)
		assert.Equal(t, "call_123", result.ContentBlocks[0].FunctionToolCall.CallID)
		assert.Equal(t, "get_weather", result.ContentBlocks[0].FunctionToolCall.Name)
		assert.Equal(t, `{"location":"NYC"}`, result.ContentBlocks[0].FunctionToolCall.Arguments)
	})

	t.Run("concat function tool result", func(t *testing.T) {
		msgs := []*AgenticMessage{
			{
				Role: AgenticRoleTypeAssistant,
				ContentBlocks: []*ContentBlock{
					{
						Type: ContentBlockTypeFunctionToolResult,
						FunctionToolResult: &FunctionToolResult{
							CallID: "call_123",
							Name:   "get_weather",
							Content: []*FunctionToolResultContentBlock{
								{Type: FunctionToolResultContentBlockTypeText, Text: &UserInputText{Text: `{"temp`}},
							},
						},
						StreamingMeta: &StreamingMeta{Index: 0},
					},
				},
			},
			{
				Role: AgenticRoleTypeAssistant,
				ContentBlocks: []*ContentBlock{
					{
						Type: ContentBlockTypeFunctionToolResult,
						FunctionToolResult: &FunctionToolResult{
							Content: []*FunctionToolResultContentBlock{
								{Type: FunctionToolResultContentBlockTypeText, Text: &UserInputText{Text: `":72}`}},
							},
						},
						StreamingMeta: &StreamingMeta{Index: 0},
					},
				},
			},
		}

		result, err := ConcatAgenticMessages(msgs)
		assert.NoError(t, err)
		assert.Len(t, result.ContentBlocks, 1)
		assert.Equal(t, "call_123", result.ContentBlocks[0].FunctionToolResult.CallID)
		assert.Equal(t, "get_weather", result.ContentBlocks[0].FunctionToolResult.Name)
		assert.Equal(t, 2, len(result.ContentBlocks[0].FunctionToolResult.Content))
		assert.Equal(t, `{"temp`, result.ContentBlocks[0].FunctionToolResult.Content[0].Text.Text)
		assert.Equal(t, `":72}`, result.ContentBlocks[0].FunctionToolResult.Content[1].Text.Text)
	})

	t.Run("concat server tool call", func(t *testing.T) {
		msgs := []*AgenticMessage{
			{
				Role: AgenticRoleTypeAssistant,
				ContentBlocks: []*ContentBlock{
					{
						Type: ContentBlockTypeServerToolCall,
						ServerToolCall: &ServerToolCall{
							CallID: "server_call_1",
							Name:   "server_func",
						},
						StreamingMeta: &StreamingMeta{Index: 0},
					},
				},
			},
			{
				Role: AgenticRoleTypeAssistant,
				ContentBlocks: []*ContentBlock{
					{
						Type: ContentBlockTypeServerToolCall,
						ServerToolCall: &ServerToolCall{
							Arguments: map[string]any{"key": "value"},
						},
						StreamingMeta: &StreamingMeta{Index: 0},
					},
				},
			},
		}

		result, err := ConcatAgenticMessages(msgs)
		assert.NoError(t, err)
		assert.Len(t, result.ContentBlocks, 1)
		assert.Equal(t, "server_call_1", result.ContentBlocks[0].ServerToolCall.CallID)
		assert.Equal(t, "server_func", result.ContentBlocks[0].ServerToolCall.Name)
		assert.NotNil(t, result.ContentBlocks[0].ServerToolCall.Arguments)
	})

	t.Run("concat server tool result", func(t *testing.T) {
		msgs := []*AgenticMessage{
			{
				Role: AgenticRoleTypeAssistant,
				ContentBlocks: []*ContentBlock{
					{
						Type: ContentBlockTypeServerToolResult,
						ServerToolResult: &ServerToolResult{
							CallID:  "server_call_1",
							Name:    "server_func",
							Content: "result1",
						},
						StreamingMeta: &StreamingMeta{Index: 0},
					},
				},
			},
			{
				Role: AgenticRoleTypeAssistant,
				ContentBlocks: []*ContentBlock{
					{
						Type:             ContentBlockTypeServerToolResult,
						ServerToolResult: &ServerToolResult{},
						StreamingMeta:    &StreamingMeta{Index: 0},
					},
				},
			},
		}

		result, err := ConcatAgenticMessages(msgs)
		assert.NoError(t, err)
		assert.Len(t, result.ContentBlocks, 1)
		assert.Equal(t, "server_call_1", result.ContentBlocks[0].ServerToolResult.CallID)
		assert.Equal(t, "server_func", result.ContentBlocks[0].ServerToolResult.Name)
		assert.Equal(t, "result1", result.ContentBlocks[0].ServerToolResult.Content)
	})

	t.Run("concat mcp tool call", func(t *testing.T) {
		msgs := []*AgenticMessage{
			{
				Role: AgenticRoleTypeAssistant,
				ContentBlocks: []*ContentBlock{
					{
						Type: ContentBlockTypeMCPToolCall,
						MCPToolCall: &MCPToolCall{
							ServerLabel: "mcp-server",
							CallID:      "mcp_call_1",
							Name:        "mcp_func",
							Arguments:   `{"arg`,
						},
						StreamingMeta: &StreamingMeta{Index: 0},
					},
				},
			},
			{
				Role: AgenticRoleTypeAssistant,
				ContentBlocks: []*ContentBlock{
					{
						Type: ContentBlockTypeMCPToolCall,
						MCPToolCall: &MCPToolCall{
							Arguments: `":123}`,
						},
						StreamingMeta: &StreamingMeta{Index: 0},
					},
				},
			},
		}

		result, err := ConcatAgenticMessages(msgs)
		assert.NoError(t, err)
		assert.Len(t, result.ContentBlocks, 1)
		assert.Equal(t, "mcp-server", result.ContentBlocks[0].MCPToolCall.ServerLabel)
		assert.Equal(t, "mcp_call_1", result.ContentBlocks[0].MCPToolCall.CallID)
		assert.Equal(t, "mcp_func", result.ContentBlocks[0].MCPToolCall.Name)
		assert.Equal(t, `{"arg":123}`, result.ContentBlocks[0].MCPToolCall.Arguments)
	})

	t.Run("concat mcp tool result", func(t *testing.T) {
		msgs := []*AgenticMessage{
			{
				Role: AgenticRoleTypeAssistant,
				ContentBlocks: []*ContentBlock{
					{
						Type: ContentBlockTypeMCPToolResult,
						MCPToolResult: &MCPToolResult{
							ServerLabel: "mcp-server",
							CallID:      "mcp_call_1",
							Name:        "mcp_func",
							Content:     `First`,
						},
						StreamingMeta: &StreamingMeta{Index: 0},
					},
				},
			},
			{
				Role: AgenticRoleTypeAssistant,
				ContentBlocks: []*ContentBlock{
					{
						Type: ContentBlockTypeMCPToolResult,
						MCPToolResult: &MCPToolResult{
							Content: `Second`,
						},
						StreamingMeta: &StreamingMeta{Index: 0},
					},
				},
			},
		}

		result, err := ConcatAgenticMessages(msgs)
		assert.NoError(t, err)
		assert.Len(t, result.ContentBlocks, 1)
		assert.Equal(t, "mcp-server", result.ContentBlocks[0].MCPToolResult.ServerLabel)
		assert.Equal(t, "mcp_call_1", result.ContentBlocks[0].MCPToolResult.CallID)
		assert.Equal(t, "mcp_func", result.ContentBlocks[0].MCPToolResult.Name)
		assert.Equal(t, `Second`, result.ContentBlocks[0].MCPToolResult.Content)
	})

	t.Run("concat mcp list tools", func(t *testing.T) {
		msgs := []*AgenticMessage{
			{
				Role: AgenticRoleTypeAssistant,
				ContentBlocks: []*ContentBlock{
					{
						Type: ContentBlockTypeMCPListToolsResult,
						MCPListToolsResult: &MCPListToolsResult{
							ServerLabel: "mcp-server",
							Tools: []*MCPListToolsItem{
								{Name: "tool1"},
							},
						},
						StreamingMeta: &StreamingMeta{Index: 0},
					},
				},
			},
			{
				Role: AgenticRoleTypeAssistant,
				ContentBlocks: []*ContentBlock{
					{
						Type: ContentBlockTypeMCPListToolsResult,
						MCPListToolsResult: &MCPListToolsResult{
							Tools: []*MCPListToolsItem{
								{Name: "tool2"},
							},
						},
						StreamingMeta: &StreamingMeta{Index: 0},
					},
				},
			},
		}

		result, err := ConcatAgenticMessages(msgs)
		assert.NoError(t, err)
		assert.Len(t, result.ContentBlocks, 1)
		assert.Equal(t, "mcp-server", result.ContentBlocks[0].MCPListToolsResult.ServerLabel)
		assert.Len(t, result.ContentBlocks[0].MCPListToolsResult.Tools, 2)
	})

	t.Run("concat mcp tool approval request", func(t *testing.T) {
		msgs := []*AgenticMessage{
			{
				Role: AgenticRoleTypeAssistant,
				ContentBlocks: []*ContentBlock{
					{
						Type: ContentBlockTypeMCPToolApprovalRequest,
						MCPToolApprovalRequest: &MCPToolApprovalRequest{
							ID:          "approval_1",
							Name:        "approval_func",
							ServerLabel: "mcp-server",
							Arguments:   `{"request`,
						},
						StreamingMeta: &StreamingMeta{Index: 0},
					},
				},
			},
			{
				Role: AgenticRoleTypeAssistant,
				ContentBlocks: []*ContentBlock{
					{
						Type: ContentBlockTypeMCPToolApprovalRequest,
						MCPToolApprovalRequest: &MCPToolApprovalRequest{
							Arguments: `":1}`,
						},
						StreamingMeta: &StreamingMeta{Index: 0},
					},
				},
			},
		}

		result, err := ConcatAgenticMessages(msgs)
		assert.NoError(t, err)
		assert.Len(t, result.ContentBlocks, 1)
		assert.Equal(t, "approval_1", result.ContentBlocks[0].MCPToolApprovalRequest.ID)
		assert.Equal(t, "approval_func", result.ContentBlocks[0].MCPToolApprovalRequest.Name)
		assert.Equal(t, "mcp-server", result.ContentBlocks[0].MCPToolApprovalRequest.ServerLabel)
		assert.Equal(t, `{"request":1}`, result.ContentBlocks[0].MCPToolApprovalRequest.Arguments)
	})

	t.Run("concat mcp tool approval response - should error", func(t *testing.T) {
		response1 := &MCPToolApprovalResponse{
			ApprovalRequestID: "approval_1",
			Approve:           false,
		}
		response2 := &MCPToolApprovalResponse{
			ApprovalRequestID: "approval_1",
			Approve:           true,
		}

		msgs := []*AgenticMessage{
			{
				Role: AgenticRoleTypeAssistant,
				ContentBlocks: []*ContentBlock{
					{
						Type:                    ContentBlockTypeMCPToolApprovalResponse,
						MCPToolApprovalResponse: response1,
						StreamingMeta:           &StreamingMeta{Index: 0},
					},
				},
			},
			{
				Role: AgenticRoleTypeAssistant,
				ContentBlocks: []*ContentBlock{
					{
						Type:                    ContentBlockTypeMCPToolApprovalResponse,
						MCPToolApprovalResponse: response2,
						StreamingMeta:           &StreamingMeta{Index: 0},
					},
				},
			},
		}

		_, err := ConcatAgenticMessages(msgs)
		assert.Error(t, err)
		assert.ErrorContains(t, err, "cannot concat multiple mcp tool approval responses")
	})

	t.Run("concat response meta", func(t *testing.T) {
		msgs := []*AgenticMessage{
			{
				Role: AgenticRoleTypeAssistant,
				ResponseMeta: &AgenticResponseMeta{
					TokenUsage: &TokenUsage{
						PromptTokens:     10,
						CompletionTokens: 5,
						PromptTokenDetails: PromptTokenDetails{
							CachedTokens: 3,
						},
						CompletionTokensDetails: CompletionTokensDetails{
							ReasoningTokens: 2,
						},
						TotalTokens: 15,
					},
				},
			},
			{
				Role: AgenticRoleTypeAssistant,
				ResponseMeta: &AgenticResponseMeta{
					TokenUsage: &TokenUsage{
						PromptTokens:     8,
						CompletionTokens: 15,
						PromptTokenDetails: PromptTokenDetails{
							CachedTokens: 5,
						},
						CompletionTokensDetails: CompletionTokensDetails{
							ReasoningTokens: 4,
						},
						TotalTokens: 23,
					},
				},
			},
		}

		result, err := ConcatAgenticMessages(msgs)
		assert.NoError(t, err)
		assert.NotNil(t, result.ResponseMeta)
		assert.Equal(t, 15, result.ResponseMeta.TokenUsage.CompletionTokens)
		assert.Equal(t, 10, result.ResponseMeta.TokenUsage.PromptTokens)
		assert.Equal(t, 23, result.ResponseMeta.TokenUsage.TotalTokens)
		assert.Equal(t, 5, result.ResponseMeta.TokenUsage.PromptTokenDetails.CachedTokens)
		assert.Equal(t, 4, result.ResponseMeta.TokenUsage.CompletionTokensDetails.ReasoningTokens)
	})

	t.Run("mixed streaming and non-streaming blocks error", func(t *testing.T) {
		msgs := []*AgenticMessage{
			{
				Role: AgenticRoleTypeAssistant,
				ContentBlocks: []*ContentBlock{
					{
						Type: ContentBlockTypeAssistantGenText,
						AssistantGenText: &AssistantGenText{
							Text: "Hello",
						},
						StreamingMeta: &StreamingMeta{Index: 0},
					},
				},
			},
			{
				Role: AgenticRoleTypeAssistant,
				ContentBlocks: []*ContentBlock{
					{
						Type: ContentBlockTypeAssistantGenText,
						AssistantGenText: &AssistantGenText{
							Text: "World",
						},
						// No StreamingMeta - non-streaming
					},
				},
			},
		}

		_, err := ConcatAgenticMessages(msgs)
		assert.Error(t, err)
		assert.ErrorContains(t, err, "found non-streaming block after streaming blocks")
	})

	t.Run("concat MCP tool call", func(t *testing.T) {
		msgs := []*AgenticMessage{
			{
				Role: AgenticRoleTypeAssistant,
				ContentBlocks: []*ContentBlock{
					{
						Type: ContentBlockTypeMCPToolCall,
						MCPToolCall: &MCPToolCall{
							ServerLabel: "mcp-server",
							CallID:      "call_456",
							Name:        "list_files",
							Arguments:   `{"path`,
						},
						StreamingMeta: &StreamingMeta{Index: 0},
					},
				},
			},
			{
				Role: AgenticRoleTypeAssistant,
				ContentBlocks: []*ContentBlock{
					{
						Type: ContentBlockTypeMCPToolCall,
						MCPToolCall: &MCPToolCall{
							Arguments: `":"/tmp"}`,
						},
						StreamingMeta: &StreamingMeta{Index: 0},
					},
				},
			},
		}

		result, err := ConcatAgenticMessages(msgs)
		assert.NoError(t, err)
		assert.Len(t, result.ContentBlocks, 1)
		assert.Equal(t, "mcp-server", result.ContentBlocks[0].MCPToolCall.ServerLabel)
		assert.Equal(t, "call_456", result.ContentBlocks[0].MCPToolCall.CallID)
		assert.Equal(t, `{"path":"/tmp"}`, result.ContentBlocks[0].MCPToolCall.Arguments)
	})

	t.Run("concat user input text - should error", func(t *testing.T) {
		msgs := []*AgenticMessage{
			{
				Role: AgenticRoleTypeUser,
				ContentBlocks: []*ContentBlock{
					{
						Type: ContentBlockTypeUserInputText,
						UserInputText: &UserInputText{
							Text: "What is ",
						},
						StreamingMeta: &StreamingMeta{Index: 0},
					},
				},
			},
			{
				Role: AgenticRoleTypeUser,
				ContentBlocks: []*ContentBlock{
					{
						Type: ContentBlockTypeUserInputText,
						UserInputText: &UserInputText{
							Text: "the weather?",
						},
						StreamingMeta: &StreamingMeta{Index: 0},
					},
				},
			},
		}

		_, err := ConcatAgenticMessages(msgs)
		assert.Error(t, err)
		assert.ErrorContains(t, err, "cannot concat multiple user input texts")
	})

	t.Run("multiple stream indexes - sparse indexes", func(t *testing.T) {
		msgs := []*AgenticMessage{
			{
				Role: AgenticRoleTypeAssistant,
				ContentBlocks: []*ContentBlock{
					{
						Type: ContentBlockTypeAssistantGenText,
						AssistantGenText: &AssistantGenText{
							Text: "Index0-",
						},
						StreamingMeta: &StreamingMeta{Index: 0},
					},
					{
						Type: ContentBlockTypeAssistantGenText,
						AssistantGenText: &AssistantGenText{
							Text: "Index2-",
						},
						StreamingMeta: &StreamingMeta{Index: 2},
					},
				},
			},
			{
				Role: AgenticRoleTypeAssistant,
				ContentBlocks: []*ContentBlock{
					{
						Type: ContentBlockTypeAssistantGenText,
						AssistantGenText: &AssistantGenText{
							Text: "Part2",
						},
						StreamingMeta: &StreamingMeta{Index: 0},
					},
					{
						Type: ContentBlockTypeAssistantGenText,
						AssistantGenText: &AssistantGenText{
							Text: "Part2",
						},
						StreamingMeta: &StreamingMeta{Index: 2},
					},
				},
			},
		}

		result, err := ConcatAgenticMessages(msgs)
		assert.NoError(t, err)
		assert.Len(t, result.ContentBlocks, 2)
		assert.Equal(t, "Index0-Part2", result.ContentBlocks[0].AssistantGenText.Text)
		assert.Equal(t, "Index2-Part2", result.ContentBlocks[1].AssistantGenText.Text)
	})

	t.Run("multiple stream indexes - mixed content types", func(t *testing.T) {
		msgs := []*AgenticMessage{
			{
				Role: AgenticRoleTypeAssistant,
				ContentBlocks: []*ContentBlock{
					{
						Type: ContentBlockTypeAssistantGenText,
						AssistantGenText: &AssistantGenText{
							Text: "Text ",
						},
						StreamingMeta: &StreamingMeta{Index: 0},
					},
					{
						Type: ContentBlockTypeFunctionToolCall,
						FunctionToolCall: &FunctionToolCall{
							CallID:    "call_1",
							Name:      "func1",
							Arguments: `{"a`,
						},
						StreamingMeta: &StreamingMeta{Index: 1},
					},
				},
			},
			{
				Role: AgenticRoleTypeAssistant,
				ContentBlocks: []*ContentBlock{
					{
						Type: ContentBlockTypeAssistantGenText,
						AssistantGenText: &AssistantGenText{
							Text: "Content",
						},
						StreamingMeta: &StreamingMeta{Index: 0},
					},
					{
						Type: ContentBlockTypeFunctionToolCall,
						FunctionToolCall: &FunctionToolCall{
							Arguments: `":1}`,
						},
						StreamingMeta: &StreamingMeta{Index: 1},
					},
				},
			},
		}

		result, err := ConcatAgenticMessages(msgs)
		assert.NoError(t, err)
		assert.Len(t, result.ContentBlocks, 2)
		assert.Equal(t, "Text Content", result.ContentBlocks[0].AssistantGenText.Text)
		assert.Equal(t, "call_1", result.ContentBlocks[1].FunctionToolCall.CallID)
		assert.Equal(t, "func1", result.ContentBlocks[1].FunctionToolCall.Name)
		assert.Equal(t, `{"a":1}`, result.ContentBlocks[1].FunctionToolCall.Arguments)
	})

	t.Run("multiple stream indexes - three indexes", func(t *testing.T) {
		msgs := []*AgenticMessage{
			{
				Role: AgenticRoleTypeAssistant,
				ContentBlocks: []*ContentBlock{
					{
						Type: ContentBlockTypeAssistantGenText,
						AssistantGenText: &AssistantGenText{
							Text: "A",
						},
						StreamingMeta: &StreamingMeta{Index: 0},
					},
					{
						Type: ContentBlockTypeAssistantGenText,
						AssistantGenText: &AssistantGenText{
							Text: "B",
						},
						StreamingMeta: &StreamingMeta{Index: 1},
					},
					{
						Type: ContentBlockTypeAssistantGenText,
						AssistantGenText: &AssistantGenText{
							Text: "C",
						},
						StreamingMeta: &StreamingMeta{Index: 2},
					},
				},
			},
			{
				Role: AgenticRoleTypeAssistant,
				ContentBlocks: []*ContentBlock{
					{
						Type: ContentBlockTypeAssistantGenText,
						AssistantGenText: &AssistantGenText{
							Text: "1",
						},
						StreamingMeta: &StreamingMeta{Index: 0},
					},
					{
						Type: ContentBlockTypeAssistantGenText,
						AssistantGenText: &AssistantGenText{
							Text: "2",
						},
						StreamingMeta: &StreamingMeta{Index: 1},
					},
					{
						Type: ContentBlockTypeAssistantGenText,
						AssistantGenText: &AssistantGenText{
							Text: "3",
						},
						StreamingMeta: &StreamingMeta{Index: 2},
					},
				},
			},
		}

		result, err := ConcatAgenticMessages(msgs)
		assert.NoError(t, err)
		assert.Len(t, result.ContentBlocks, 3)
		assert.Equal(t, "A1", result.ContentBlocks[0].AssistantGenText.Text)
		assert.Equal(t, "B2", result.ContentBlocks[1].AssistantGenText.Text)
		assert.Equal(t, "C3", result.ContentBlocks[2].AssistantGenText.Text)
	})
}

func TestAgenticMessageFormat(t *testing.T) {
	m := &AgenticMessage{
		Role: AgenticRoleTypeUser,
		ContentBlocks: []*ContentBlock{
			{
				Type:          ContentBlockTypeUserInputText,
				UserInputText: &UserInputText{Text: "{a}"},
			},
			{
				Type: ContentBlockTypeUserInputImage,
				UserInputImage: &UserInputImage{
					URL:        "{b}",
					Base64Data: "{c}",
				},
			},
			{
				Type: ContentBlockTypeUserInputAudio,
				UserInputAudio: &UserInputAudio{
					URL:        "{d}",
					Base64Data: "{e}",
				},
			},
			{
				Type: ContentBlockTypeUserInputVideo,
				UserInputVideo: &UserInputVideo{
					URL:        "{f}",
					Base64Data: "{g}",
				},
			},
			{
				Type: ContentBlockTypeUserInputFile,
				UserInputFile: &UserInputFile{
					URL:        "{h}",
					Base64Data: "{i}",
				},
			},
		},
	}

	result, err := m.Format(context.Background(), map[string]any{
		"a": "1", "b": "2", "c": "3", "d": "4", "e": "5", "f": "6", "g": "7", "h": "8", "i": "9",
	}, FString)
	assert.NoError(t, err)
	assert.Equal(t, []*AgenticMessage{{
		Role: AgenticRoleTypeUser,
		ContentBlocks: []*ContentBlock{
			{
				Type:          ContentBlockTypeUserInputText,
				UserInputText: &UserInputText{Text: "1"},
			},
			{
				Type: ContentBlockTypeUserInputImage,
				UserInputImage: &UserInputImage{
					URL:        "2",
					Base64Data: "3",
				},
			},
			{
				Type: ContentBlockTypeUserInputAudio,
				UserInputAudio: &UserInputAudio{
					URL:        "4",
					Base64Data: "5",
				},
			},
			{
				Type: ContentBlockTypeUserInputVideo,
				UserInputVideo: &UserInputVideo{
					URL:        "6",
					Base64Data: "7",
				},
			},
			{
				Type: ContentBlockTypeUserInputFile,
				UserInputFile: &UserInputFile{
					URL:        "8",
					Base64Data: "9",
				},
			},
		},
	}}, result)
}

func TestAgenticPlaceholderFormat(t *testing.T) {
	ctx := context.Background()
	ph := AgenticMessagesPlaceholder("a", false)

	result, err := ph.Format(ctx, map[string]any{
		"a": []*AgenticMessage{{Role: AgenticRoleTypeUser}, {Role: AgenticRoleTypeUser}},
	}, FString)
	assert.NoError(t, err)
	assert.Equal(t, 2, len(result))

	ph = AgenticMessagesPlaceholder("a", true)

	result, err = ph.Format(ctx, map[string]any{}, FString)
	assert.NoError(t, err)
	assert.Equal(t, 0, len(result))
}

func ptrOf[T any](v T) *T {
	return &v
}

func TestAgenticMessageString(t *testing.T) {
	longBase64 := "iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mNk+M9QDwADhgGAWjR9awAAAABJRU5ErkJggg=="

	msg := &AgenticMessage{
		Role: AgenticRoleTypeAssistant,
		ContentBlocks: []*ContentBlock{
			{
				Type: ContentBlockTypeUserInputText,
				UserInputText: &UserInputText{
					Text: "What's the weather like in New York City today?",
				},
			},
			{
				Type: ContentBlockTypeUserInputImage,
				UserInputImage: &UserInputImage{
					URL:        "https://example.com/weather-map.jpg",
					Base64Data: longBase64,
					MIMEType:   "image/jpeg",
					Detail:     ImageURLDetailHigh,
				},
			},
			{
				Type: ContentBlockTypeUserInputAudio,
				UserInputAudio: &UserInputAudio{
					URL:        "http://audio.com",
					Base64Data: "audio_data",
					MIMEType:   "audio/mp3",
				},
			},
			{
				Type: ContentBlockTypeUserInputVideo,
				UserInputVideo: &UserInputVideo{
					URL:        "http://video.com",
					Base64Data: "video_data",
					MIMEType:   "video/mp4",
				},
			},
			{
				Type: ContentBlockTypeUserInputFile,
				UserInputFile: &UserInputFile{
					URL:        "http://file.com",
					Name:       "file.txt",
					Base64Data: "file_data",
					MIMEType:   "text/plain",
				},
			},
			{
				Type: ContentBlockTypeAssistantGenText,
				AssistantGenText: &AssistantGenText{
					Text: "I'll check the current weather in New York City for you.",
				},
			},
			{
				Type: ContentBlockTypeAssistantGenImage,
				AssistantGenImage: &AssistantGenImage{
					URL:        "http://gen_image.com",
					Base64Data: "gen_image_data",
					MIMEType:   "image/png",
				},
			},
			{
				Type: ContentBlockTypeAssistantGenAudio,
				AssistantGenAudio: &AssistantGenAudio{
					URL:        "http://gen_audio.com",
					Base64Data: "gen_audio_data",
					MIMEType:   "audio/wav",
				},
			},
			{
				Type: ContentBlockTypeAssistantGenVideo,
				AssistantGenVideo: &AssistantGenVideo{
					URL:        "http://gen_video.com",
					Base64Data: "gen_video_data",
					MIMEType:   "video/mp4",
				},
			},
			{
				Type: ContentBlockTypeReasoning,
				Reasoning: &Reasoning{
					Text: "First, I need to identify the location (New York City) from the user's query.\n" +
						"Then, I should call the weather API to get current conditions.\n" +
						"Finally, I'll format the response in a user-friendly way with temperature and conditions.",
					Signature: "encrypted_reasoning_content_that_is_very_long_and_will_be_truncated_for_display",
				},
			},
			{
				Type: ContentBlockTypeFunctionToolCall,
				FunctionToolCall: &FunctionToolCall{
					CallID:    "call_weather_123",
					Name:      "get_current_weather",
					Arguments: `{"location":"New York City","unit":"fahrenheit"}`,
				},
				StreamingMeta: &StreamingMeta{Index: 0},
			},
			{
				Type: ContentBlockTypeFunctionToolResult,
				FunctionToolResult: &FunctionToolResult{
					CallID: "call_weather_123",
					Name:   "get_current_weather",
					Content: []*FunctionToolResultContentBlock{
						{Type: FunctionToolResultContentBlockTypeText, Text: &UserInputText{Text: `{"temperature":72,"condition":"sunny","humidity":45,"wind_speed":8}`}},
					},
				},
			},
			{
				Type: ContentBlockTypeServerToolCall,
				ServerToolCall: &ServerToolCall{
					Name:      "server_tool",
					CallID:    "call_1",
					Arguments: map[string]any{"a": 1},
				},
			},
			{
				Type: ContentBlockTypeServerToolResult,
				ServerToolResult: &ServerToolResult{
					Name:    "server_tool",
					CallID:  "call_1",
					Content: map[string]any{"success": true},
				},
			},
			{
				Type: ContentBlockTypeMCPToolApprovalRequest,
				MCPToolApprovalRequest: &MCPToolApprovalRequest{
					ID:          "req_1",
					Name:        "mcp_tool",
					ServerLabel: "mcp_server",
					Arguments:   "{}",
				},
			},
			{
				Type: ContentBlockTypeMCPToolApprovalResponse,
				MCPToolApprovalResponse: &MCPToolApprovalResponse{
					ApprovalRequestID: "req_1",
					Approve:           true,
					Reason:            "looks good",
				},
			},
			{
				Type: ContentBlockTypeMCPToolCall,
				MCPToolCall: &MCPToolCall{
					ServerLabel: "weather-mcp-server",
					CallID:      "mcp_forecast_456",
					Name:        "get_7day_forecast",
					Arguments:   `{"city":"New York","days":7}`,
				},
			},
			{
				Type: ContentBlockTypeMCPToolResult,
				MCPToolResult: &MCPToolResult{
					CallID:  "mcp_forecast_456",
					Name:    "get_7day_forecast",
					Content: `{"status":"partial","days_available":3}`,
					Error: &MCPToolCallError{
						Code:    ptrOf[int64](503),
						Message: "Service temporarily unavailable for full 7-day forecast",
					},
				},
			},
			{
				Type: ContentBlockTypeMCPListToolsResult,
				MCPListToolsResult: &MCPListToolsResult{
					ServerLabel: "weather-mcp-server",
					Tools: []*MCPListToolsItem{
						{Name: "get_current_weather", Description: "Get current weather conditions for a location"},
						{Name: "get_7day_forecast", Description: "Get 7-day weather forecast"},
						{Name: "get_weather_alerts", Description: "Get active weather alerts and warnings"},
					},
				},
			},
		},
		ResponseMeta: &AgenticResponseMeta{
			TokenUsage: &TokenUsage{
				PromptTokens:     250,
				CompletionTokens: 180,
				TotalTokens:      430,
			},
		},
	}

	// Print the formatted output
	output := msg.String()

	assert.Equal(t, `role: assistant
content_blocks:
  [0] type: user_input_text
      text: What's the weather like in New York City today?
  [1] type: user_input_image
      url: https://example.com/weather-map.jpg
      base64_data: iVBORw0KGgoAAAANSUhE...... (96 bytes)
      mime_type: image/jpeg
      detail: high
  [2] type: user_input_audio
      url: http://audio.com
      base64_data: audio_data... (10 bytes)
      mime_type: audio/mp3
  [3] type: user_input_video
      url: http://video.com
      base64_data: video_data... (10 bytes)
      mime_type: video/mp4
  [4] type: user_input_file
      name: file.txt
      url: http://file.com
      base64_data: file_data... (9 bytes)
      mime_type: text/plain
  [5] type: assistant_gen_text
      text: I'll check the current weather in New York City for you.
  [6] type: assistant_gen_image
      url: http://gen_image.com
      base64_data: gen_image_data... (14 bytes)
      mime_type: image/png
  [7] type: assistant_gen_audio
      url: http://gen_audio.com
      base64_data: gen_audio_data... (14 bytes)
      mime_type: audio/wav
  [8] type: assistant_gen_video
      url: http://gen_video.com
      base64_data: gen_video_data... (14 bytes)
      mime_type: video/mp4
  [9] type: reasoning
      text: First, I need to identify the location (New York City) from the user's query.
Then, I should call the weather API to get current conditions.
Finally, I'll format the response in a user-friendly way with temperature and conditions.
      signature: encrypted_reasoning_content_that_is_very_long_and_...
  [10] type: function_tool_call
      call_id: call_weather_123
      name: get_current_weather
      arguments: {"location":"New York City","unit":"fahrenheit"}
      stream_index: 0
  [11] type: function_tool_result
      call_id: call_weather_123
      name: get_current_weather
      content: (1 blocks)
        [0]       text: {"temperature":72,"condition":"sunny","humidity":45,"wind_speed":8}
  [12] type: server_tool_call
      name: server_tool
      call_id: call_1
      arguments: {
  "a": 1
}
  [13] type: server_tool_result
      name: server_tool
      call_id: call_1
      content: {
  "success": true
}
  [14] type: mcp_tool_approval_request
      server_label: mcp_server
      id: req_1
      name: mcp_tool
      arguments: {}
  [15] type: mcp_tool_approval_response
      approval_request_id: req_1
      approve: true
      reason: looks good
  [16] type: mcp_tool_call
      server_label: weather-mcp-server
      call_id: mcp_forecast_456
      name: get_7day_forecast
      arguments: {"city":"New York","days":7}
  [17] type: mcp_tool_result
      call_id: mcp_forecast_456
      name: get_7day_forecast
      content: {"status":"partial","days_available":3}
      error: [503] Service temporarily unavailable for full 7-day forecast
  [18] type: mcp_list_tools_result
      server_label: weather-mcp-server
      tools: 3 items
        - get_current_weather: Get current weather conditions for a location
        - get_7day_forecast: Get 7-day weather forecast
        - get_weather_alerts: Get active weather alerts and warnings
response_meta:
  token_usage: prompt=250, completion=180, total=430
`, output)

	t.Run("nil/empty fields", func(t *testing.T) {
		msg := &AgenticMessage{
			Role: AgenticRoleTypeUser,
			ContentBlocks: []*ContentBlock{
				{Type: ContentBlockTypeUserInputAudio, UserInputAudio: &UserInputAudio{}}, // empty
				{Type: ContentBlockTypeUserInputVideo, UserInputVideo: &UserInputVideo{}},
				{Type: ContentBlockTypeUserInputFile, UserInputFile: &UserInputFile{}},
				{Type: ContentBlockTypeAssistantGenImage, AssistantGenImage: &AssistantGenImage{}},
				{Type: ContentBlockTypeAssistantGenAudio, AssistantGenAudio: &AssistantGenAudio{}},
				{Type: ContentBlockTypeAssistantGenVideo, AssistantGenVideo: &AssistantGenVideo{}},
				{Type: ContentBlockTypeServerToolCall, ServerToolCall: &ServerToolCall{Name: "t"}},                                 // No CallID
				{Type: ContentBlockTypeServerToolResult, ServerToolResult: &ServerToolResult{Name: "t"}},                           // No CallID
				{Type: ContentBlockTypeMCPToolResult, MCPToolResult: &MCPToolResult{Name: "t"}},                                    // No Error
				{Type: ContentBlockTypeMCPListToolsResult, MCPListToolsResult: &MCPListToolsResult{}},                              // No Error
				{Type: ContentBlockTypeMCPToolApprovalResponse, MCPToolApprovalResponse: &MCPToolApprovalResponse{Approve: false}}, // No Reason
				nil, // Nil block in slice
			},
		}

		s := msg.String()
		assert.Contains(t, s, "type: user_input_audio")
		assert.NotContains(t, s, "mime_type:")
		assert.Contains(t, s, "type: server_tool_call")
	})

	t.Run("nil content struct in block", func(t *testing.T) {
		// Test cases where the specific content struct is nil but type is set
		// This shouldn't crash and should just print type
		msg := &AgenticMessage{
			ContentBlocks: []*ContentBlock{
				{Type: ContentBlockTypeReasoning, Reasoning: nil},
				{Type: ContentBlockTypeUserInputText, UserInputText: nil},
				{Type: ContentBlockTypeUserInputImage, UserInputImage: nil},
				{Type: ContentBlockTypeUserInputAudio, UserInputAudio: nil},
				{Type: ContentBlockTypeUserInputVideo, UserInputVideo: nil},
				{Type: ContentBlockTypeUserInputFile, UserInputFile: nil},
				{Type: ContentBlockTypeAssistantGenText, AssistantGenText: nil},
				{Type: ContentBlockTypeAssistantGenImage, AssistantGenImage: nil},
				{Type: ContentBlockTypeAssistantGenAudio, AssistantGenAudio: nil},
				{Type: ContentBlockTypeAssistantGenVideo, AssistantGenVideo: nil},
				{Type: ContentBlockTypeFunctionToolCall, FunctionToolCall: nil},
				{Type: ContentBlockTypeFunctionToolResult, FunctionToolResult: nil},
				{Type: ContentBlockTypeServerToolCall, ServerToolCall: nil},
				{Type: ContentBlockTypeServerToolResult, ServerToolResult: nil},
				{Type: ContentBlockTypeMCPToolCall, MCPToolCall: nil},
				{Type: ContentBlockTypeMCPToolResult, MCPToolResult: nil},
				{Type: ContentBlockTypeMCPListToolsResult, MCPListToolsResult: nil},
				{Type: ContentBlockTypeMCPToolApprovalRequest, MCPToolApprovalRequest: nil},
				{Type: ContentBlockTypeMCPToolApprovalResponse, MCPToolApprovalResponse: nil},
			},
		}
		s := msg.String()
		assert.Contains(t, s, "type: reasoning")
		// ensure no panic and basic output present
	})
}

func TestSystemAgenticMessage(t *testing.T) {
	t.Run("basic", func(t *testing.T) {
		msg := SystemAgenticMessage("system")
		assert.Equal(t, AgenticRoleTypeSystem, msg.Role)
		assert.Len(t, msg.ContentBlocks, 1)
		assert.Equal(t, "system", msg.ContentBlocks[0].UserInputText.Text)
	})
}

func TestUserAgenticMessage(t *testing.T) {
	t.Run("basic", func(t *testing.T) {
		msg := UserAgenticMessage("user")
		assert.Equal(t, AgenticRoleTypeUser, msg.Role)
		assert.Len(t, msg.ContentBlocks, 1)
		assert.Equal(t, "user", msg.ContentBlocks[0].UserInputText.Text)
	})
}

func TestNewContentBlock(t *testing.T) {
	cbType := reflect.TypeOf(ContentBlock{})
	for i := 0; i < cbType.NumField(); i++ {
		field := cbType.Field(i)

		// Skip non-content fields
		if field.Name == "Type" || field.Name == "Extra" || field.Name == "StreamingMeta" {
			continue
		}

		t.Run(field.Name, func(t *testing.T) {
			// Ensure field is a pointer
			assert.Equal(t, reflect.Ptr, field.Type.Kind(), "Field %s should be a pointer", field.Name)

			// Create a new instance of the field's type
			// field.Type is *T, so Elem() is T. reflect.New(T) returns *T.
			elemType := field.Type.Elem()
			inputVal := reflect.New(elemType)
			input := inputVal.Interface()

			// Call NewContentBlock (generic) via type switch
			var block *ContentBlock
			switch v := input.(type) {
			case *Reasoning:
				block = NewContentBlock(v)
			case *UserInputText:
				block = NewContentBlock(v)
			case *UserInputImage:
				block = NewContentBlock(v)
			case *UserInputAudio:
				block = NewContentBlock(v)
			case *UserInputVideo:
				block = NewContentBlock(v)
			case *UserInputFile:
				block = NewContentBlock(v)
			case *ToolSearchFunctionToolResult:
				block = NewContentBlock(v)
			case *AssistantGenText:
				block = NewContentBlock(v)
			case *AssistantGenImage:
				block = NewContentBlock(v)
			case *AssistantGenAudio:
				block = NewContentBlock(v)
			case *AssistantGenVideo:
				block = NewContentBlock(v)
			case *FunctionToolCall:
				block = NewContentBlock(v)
			case *FunctionToolResult:
				block = NewContentBlock(v)
			case *ServerToolCall:
				block = NewContentBlock(v)
			case *ServerToolResult:
				block = NewContentBlock(v)
			case *MCPToolCall:
				block = NewContentBlock(v)
			case *MCPToolResult:
				block = NewContentBlock(v)
			case *MCPListToolsResult:
				block = NewContentBlock(v)
			case *MCPToolApprovalRequest:
				block = NewContentBlock(v)
			case *MCPToolApprovalResponse:
				block = NewContentBlock(v)
			default:
				t.Fatalf("unsupported ContentBlock field type: %T", input)
			}

			// Assertions
			assert.NotNil(t, block, "NewContentBlock should return non-nil for type %T", input)

			// Check if the corresponding field in block is set equals to input
			blockVal := reflect.ValueOf(block).Elem()
			fieldVal := blockVal.FieldByName(field.Name)
			assert.True(t, fieldVal.IsValid(), "Field %s not found in result", field.Name)
			assert.Equal(t, input, fieldVal.Interface(), "Field %s should match input", field.Name)

			// Check Type is set
			typeVal := blockVal.FieldByName("Type")
			assert.NotEmpty(t, typeVal.String(), "Type should be set for %s", field.Name)
		})
	}
}

func TestNewContentBlockChunk_NilMeta(t *testing.T) {
	require.NotPanics(t, func() {
		block := NewContentBlockChunk(&AssistantGenText{Text: "test"}, nil)
		require.NotNil(t, block)
		assert.Nil(t, block.StreamingMeta)
	}, "NewContentBlockChunk should handle nil meta without panic")
}

func TestConcatAssistantGenTexts_ExtensionOverwrite(t *testing.T) {
	type testExtension struct {
		Value string
	}

	texts := []*AssistantGenText{
		{Text: "Hello ", Extension: &testExtension{Value: "ext1"}},
		{Text: "world", Extension: &testExtension{Value: "ext2"}},
	}

	result, err := concatAssistantGenTexts(texts)
	if err != nil {
		t.Logf("Concat error (may be expected if ConcatSliceValue doesn't handle this type): %v", err)
		t.Skip("Skipping: ConcatSliceValue doesn't support test type")
	}
	require.NotNil(t, result)

	assert.Equal(t, "Hello world", result.Text)

	if result.Extension != nil {
		t.Logf("Extension type: %T, value: %v", result.Extension, result.Extension)
		_, isSlice := result.Extension.([]*testExtension)
		if isSlice {
			t.Log("WARNING: Extension is a raw slice instead of a concatenated value. " +
				"Line 1381 in agentic_message.go overwrites the ConcatSliceValue result " +
				"with extensions.Interface(), discarding the concatenation.")
		}
	}
}

func TestFunctionToolResultBlockString(t *testing.T) {
	t.Run("empty type", func(t *testing.T) {
		b := &FunctionToolResultContentBlock{Text: &UserInputText{Text: "x"}}
		assert.Equal(t, "unknown block type: <empty>\n", b.String())
	})

	t.Run("known type but empty payload", func(t *testing.T) {
		b := &FunctionToolResultContentBlock{Type: FunctionToolResultContentBlockTypeText}
		assert.Equal(t, "empty text block\n", b.String())
	})

	t.Run("unknown type value", func(t *testing.T) {
		b := &FunctionToolResultContentBlock{Type: FunctionToolResultContentBlockType("weird")}
		assert.Equal(t, "unknown block type: weird\n", b.String())
	})
}

func TestConcatFunctionToolResults(t *testing.T) {
	t.Run("direct append", func(t *testing.T) {
		results := []*FunctionToolResult{
			{CallID: "c1", Name: "tool1", Content: []*FunctionToolResultContentBlock{
				{Type: FunctionToolResultContentBlockTypeText, Text: &UserInputText{Text: "hello"}},
			}},
			{CallID: "c1", Name: "tool1", Content: []*FunctionToolResultContentBlock{
				{Type: FunctionToolResultContentBlockTypeImage, Image: &UserInputImage{URL: "http://img.png"}},
			}},
		}
		got, err := concatFunctionToolResults(results)
		require.NoError(t, err)
		assert.Len(t, got.Content, 2)
		assert.Equal(t, "hello", got.Content[0].Text.Text)
		assert.Equal(t, "http://img.png", got.Content[1].Image.URL)
	})
}
