# Copyright 2026 Google LLC
#
# 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.

"""Behavioral tests for other agent message processing in contents module."""

from google.adk.agents.llm_agent import Agent
from google.adk.agents.run_config import RunConfig
from google.adk.events.event import Event
from google.adk.flows.llm_flows.contents import request_processor
from google.adk.models.llm_request import LlmRequest
from google.genai import types
import pytest

from ... import testing_utils


@pytest.mark.asyncio
async def test_other_agent_message_appears_as_user_context():
  """Test that messages from other agents appear as user context."""
  agent = Agent(model="gemini-2.5-flash", name="current_agent")
  llm_request = LlmRequest(model="gemini-2.5-flash")
  invocation_context = await testing_utils.create_invocation_context(
      agent=agent
  )
  # Add event from another agent
  other_agent_event = Event(
      invocation_id="test_inv",
      author="other_agent",
      content=types.ModelContent("Hello from other agent"),
  )
  invocation_context.session.events = [other_agent_event]

  # Process the request
  async for _ in request_processor.run_async(invocation_context, llm_request):
    pass

  # Verify the other agent's message is presented as user context
  assert llm_request.contents[0].role == "user"
  assert llm_request.contents[0].parts == [
      types.Part(text="For context:"),
      types.Part(text="[other_agent] said: Hello from other agent"),
  ]


@pytest.mark.asyncio
async def test_other_agent_thoughts_are_excluded():
  """Test that thoughts from other agents are excluded from context."""
  agent = Agent(model="gemini-2.5-flash", name="current_agent")
  llm_request = LlmRequest(model="gemini-2.5-flash")
  invocation_context = await testing_utils.create_invocation_context(
      agent=agent
  )
  # Add event from other agent with both regular text and thoughts
  other_agent_event = Event(
      invocation_id="test_inv",
      author="other_agent",
      content=types.ModelContent([
          types.Part(text="Public message", thought=False),
          types.Part(text="Private thought", thought=True),
          types.Part(text="Another public message"),
      ]),
  )
  invocation_context.session.events = [other_agent_event]

  # Process the request
  async for _ in request_processor.run_async(invocation_context, llm_request):
    pass

  # Verify only non-thought parts are included (thoughts excluded)
  assert llm_request.contents[0].role == "user"
  assert llm_request.contents[0].parts == [
      types.Part(text="For context:"),
      types.Part(text="[other_agent] said: Public message"),
      types.Part(text="[other_agent] said: Another public message"),
  ]


@pytest.mark.asyncio
async def test_other_agent_thoughts_can_be_included_as_context():
  """Test opt-in inclusion of thoughts from other agents."""
  agent = Agent(model="gemini-2.5-flash", name="current_agent")
  llm_request = LlmRequest(model="gemini-2.5-flash")
  invocation_context = await testing_utils.create_invocation_context(
      agent=agent,
      run_config=RunConfig(include_thoughts_from_other_agents=True),
  )
  other_agent_event = Event(
      invocation_id="test_inv",
      author="other_agent",
      content=types.ModelContent([
          types.Part(text="Public message", thought=False),
          types.Part(text="Private thought", thought=True),
          types.Part(text="Another public message"),
      ]),
  )
  invocation_context.session.events = [other_agent_event]

  async for _ in request_processor.run_async(invocation_context, llm_request):
    pass

  assert llm_request.contents[0].role == "user"
  assert llm_request.contents[0].parts == [
      types.Part(text="For context:"),
      types.Part(text="[other_agent] said: Public message"),
      types.Part(text="[other_agent] thought: Private thought"),
      types.Part(text="[other_agent] said: Another public message"),
  ]


@pytest.mark.asyncio
async def test_other_agent_thought_only_message_can_be_included_as_context():
  """Test opt-in inclusion of thought-only messages from other agents."""
  agent = Agent(model="gemini-2.5-flash", name="current_agent")
  llm_request = LlmRequest(model="gemini-2.5-flash")
  invocation_context = await testing_utils.create_invocation_context(
      agent=agent,
      run_config=RunConfig(include_thoughts_from_other_agents=True),
  )
  other_agent_event = Event(
      invocation_id="test_inv",
      author="other_agent",
      content=types.ModelContent([
          types.Part(text="First private thought", thought=True),
          types.Part(text="Second private thought", thought=True),
      ]),
  )
  invocation_context.session.events = [other_agent_event]

  async for _ in request_processor.run_async(invocation_context, llm_request):
    pass

  assert llm_request.contents[0].role == "user"
  assert llm_request.contents[0].parts == [
      types.Part(text="For context:"),
      types.Part(text="[other_agent] thought: First private thought"),
      types.Part(text="[other_agent] thought: Second private thought"),
  ]


@pytest.mark.asyncio
async def test_other_agent_thoughts_excluded_from_current_turn_only_context():
  """Test include_contents='none' does not include other-agent thoughts."""
  agent = Agent(
      model="gemini-2.5-flash",
      name="current_agent",
      include_contents="none",
  )
  llm_request = LlmRequest(model="gemini-2.5-flash")
  invocation_context = await testing_utils.create_invocation_context(
      agent=agent,
      run_config=RunConfig(include_thoughts_from_other_agents=True),
  )
  invocation_context.session.events = [
      Event(
          invocation_id="inv1",
          author="user",
          content=types.UserContent("Earlier user message"),
      ),
      Event(
          invocation_id="inv2",
          author="other_agent",
          content=types.ModelContent([
              types.Part(text="Private thought", thought=True),
              types.Part(text="Visible handoff"),
          ]),
      ),
  ]

  async for _ in request_processor.run_async(invocation_context, llm_request):
    pass

  assert llm_request.contents == [
      types.Content(
          role="user",
          parts=[
              types.Part(text="For context:"),
              types.Part(text="[other_agent] said: Visible handoff"),
          ],
      )
  ]


@pytest.mark.asyncio
async def test_other_agent_function_calls():
  """Test that function calls from other agents are preserved in context."""
  agent = Agent(model="gemini-2.5-flash", name="current_agent")
  llm_request = LlmRequest(model="gemini-2.5-flash")
  invocation_context = await testing_utils.create_invocation_context(
      agent=agent
  )
  # Add event from other agent with function call
  function_call = types.FunctionCall(
      id="func_123", name="search_tool", args={"query": "test query"}
  )
  other_agent_event = Event(
      invocation_id="test_inv",
      author="other_agent",
      content=types.ModelContent([types.Part(function_call=function_call)]),
  )
  invocation_context.session.events = [other_agent_event]

  # Process the request
  async for _ in request_processor.run_async(invocation_context, llm_request):
    pass

  # Verify function call is presented as context
  assert llm_request.contents[0].role == "user"
  assert llm_request.contents[0].parts == [
      types.Part(text="For context:"),
      types.Part(
          text="""\
[other_agent] called tool `search_tool` with parameters: {'query': 'test query'}"""
      ),
  ]


@pytest.mark.asyncio
async def test_other_agent_function_responses():
  """Test that function responses from other agents are properly formatted."""
  agent = Agent(model="gemini-2.5-flash", name="current_agent")
  llm_request = LlmRequest(model="gemini-2.5-flash")
  invocation_context = await testing_utils.create_invocation_context(
      agent=agent
  )

  # Add event from other agent with function response
  function_response = types.FunctionResponse(
      id="func_123",
      name="search_tool",
      response={"results": ["item1", "item2"]},
  )
  other_agent_event = Event(
      invocation_id="test_inv",
      author="other_agent",
      content=types.Content(
          role="user", parts=[types.Part(function_response=function_response)]
      ),
  )
  invocation_context.session.events = [other_agent_event]

  # Process the request
  async for _ in request_processor.run_async(invocation_context, llm_request):
    pass

  # Verify function response is presented as context
  assert llm_request.contents[0].role == "user"
  assert llm_request.contents[0].parts == [
      types.Part(text="For context:"),
      types.Part(
          text=(
              "[other_agent] `search_tool` tool returned result: {'results':"
              " ['item1', 'item2']}"
          )
      ),
  ]


@pytest.mark.asyncio
async def test_other_agent_function_call_response():
  """Test function call and response sequence from other agents."""
  agent = Agent(model="gemini-2.5-flash", name="current_agent")
  llm_request = LlmRequest(model="gemini-2.5-flash")
  invocation_context = await testing_utils.create_invocation_context(
      agent=agent
  )
  # Add function call event from other agent
  function_call = types.FunctionCall(
      id="func_123", name="calc_tool", args={"query": "6x7"}
  )
  call_event = Event(
      invocation_id="test_inv1",
      author="other_agent",
      content=types.ModelContent([
          types.Part(text="Let me calculate this"),
          types.Part(function_call=function_call),
      ]),
  )
  # Add function response event
  function_response = types.FunctionResponse(
      id="func_123", name="calc_tool", response={"result": 42}
  )
  response_event = Event(
      invocation_id="test_inv2",
      author="other_agent",
      content=types.UserContent(
          parts=[types.Part(function_response=function_response)]
      ),
  )
  invocation_context.session.events = [call_event, response_event]

  # Process the request
  async for _ in request_processor.run_async(invocation_context, llm_request):
    pass

  # Verify function call and response are properly formatted
  assert len(llm_request.contents) == 2

  # Function call from other agent
  assert llm_request.contents[0].role == "user"
  assert llm_request.contents[0].parts == [
      types.Part(text="For context:"),
      types.Part(text="[other_agent] said: Let me calculate this"),
      types.Part(
          text=(
              "[other_agent] called tool `calc_tool` with parameters: {'query':"
              " '6x7'}"
          )
      ),
  ]
  # Function response from other agent
  assert llm_request.contents[1].role == "user"
  assert llm_request.contents[1].parts == [
      types.Part(text="For context:"),
      types.Part(
          text="[other_agent] `calc_tool` tool returned result: {'result': 42}"
      ),
  ]


@pytest.mark.asyncio
async def test_other_agent_empty_content():
  """Test that other agent messages with only thoughts or empty content are filtered out."""
  agent = Agent(model="gemini-2.5-flash", name="current_agent")
  llm_request = LlmRequest(model="gemini-2.5-flash")
  invocation_context = await testing_utils.create_invocation_context(
      agent=agent
  )
  # Add events: user message, other agents with empty content, user message
  events = [
      Event(
          invocation_id="inv1",
          author="user",
          content=types.UserContent("Hello"),
      ),
      # Other agent with only thoughts
      Event(
          invocation_id="inv2",
          author="other_agent1",
          content=types.ModelContent([
              types.Part(text="This is a private thought", thought=True),
              types.Part(text="Another private thought", thought=True),
          ]),
      ),
      # Other agent with empty text and thoughts
      Event(
          invocation_id="inv3",
          author="other_agent2",
          content=types.ModelContent([
              types.Part(text="", thought=False),
              types.Part(text="Secret thought", thought=True),
          ]),
      ),
      Event(
          invocation_id="inv4",
          author="user",
          content=types.UserContent("World"),
      ),
  ]
  invocation_context.session.events = events

  # Process the request
  async for _ in request_processor.run_async(invocation_context, llm_request):
    pass

  # Verify empty content events are completely filtered out
  assert llm_request.contents == [
      types.UserContent("Hello"),
      types.UserContent("World"),
  ]


@pytest.mark.asyncio
async def test_multiple_agents_in_conversation():
  """Test handling multiple agents in a conversation flow."""
  agent = Agent(model="gemini-2.5-flash", name="current_agent")
  llm_request = LlmRequest(model="gemini-2.5-flash")
  invocation_context = await testing_utils.create_invocation_context(
      agent=agent
  )

  # Create a multi-agent conversation
  events = [
      Event(
          invocation_id="inv1",
          author="user",
          content=types.UserContent("Hello everyone"),
      ),
      Event(
          invocation_id="inv2",
          author="agent1",
          content=types.ModelContent("Hi from agent1"),
      ),
      Event(
          invocation_id="inv3",
          author="agent2",
          content=types.ModelContent("Hi from agent2"),
      ),
  ]
  invocation_context.session.events = events

  # Process the request
  async for _ in request_processor.run_async(invocation_context, llm_request):
    pass

  # Verify all messages are properly processed
  assert len(llm_request.contents) == 3

  # User message should remain as user
  assert llm_request.contents[0] == types.UserContent("Hello everyone")
  # Other agents' messages should be converted to user context
  assert llm_request.contents[1].role == "user"
  assert llm_request.contents[1].parts == [
      types.Part(text="For context:"),
      types.Part(text="[agent1] said: Hi from agent1"),
  ]
  assert llm_request.contents[2].role == "user"
  assert llm_request.contents[2].parts == [
      types.Part(text="For context:"),
      types.Part(text="[agent2] said: Hi from agent2"),
  ]


@pytest.mark.asyncio
async def test_current_agent_messages_not_converted():
  """Test that the current agent's own messages are not converted."""
  agent = Agent(model="gemini-2.5-flash", name="current_agent")
  llm_request = LlmRequest(model="gemini-2.5-flash")
  invocation_context = await testing_utils.create_invocation_context(
      agent=agent
  )
  # Add events from both current agent and other agent
  events = [
      Event(
          invocation_id="inv1",
          author="current_agent",
          content=types.ModelContent("My own message"),
      ),
      Event(
          invocation_id="inv2",
          author="other_agent",
          content=types.ModelContent("Other agent message"),
      ),
  ]
  invocation_context.session.events = events

  # Process the request
  async for _ in request_processor.run_async(invocation_context, llm_request):
    pass

  # Verify current agent's message stays as model role
  # and other agent's message is converted to user context
  assert len(llm_request.contents) == 2
  assert llm_request.contents[0] == types.ModelContent("My own message")
  assert llm_request.contents[1].role == "user"
  assert llm_request.contents[1].parts == [
      types.Part(text="For context:"),
      types.Part(text="[other_agent] said: Other agent message"),
  ]


@pytest.mark.asyncio
async def test_user_messages_preserved():
  """Test that user messages are preserved as-is."""
  agent = Agent(model="gemini-2.5-flash", name="current_agent")
  llm_request = LlmRequest(model="gemini-2.5-flash")
  invocation_context = await testing_utils.create_invocation_context(
      agent=agent
  )
  # Add user message
  user_event = Event(
      invocation_id="inv1",
      author="user",
      content=types.UserContent("User message"),
  )
  invocation_context.session.events = [user_event]

  # Process the request
  async for _ in request_processor.run_async(invocation_context, llm_request):
    pass

  # Verify user message is preserved exactly
  assert len(llm_request.contents) == 1
  assert llm_request.contents[0] == types.UserContent("User message")
