# Copyright 2025 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.

import sys
from unittest.mock import Mock
from unittest.mock import patch

import pytest

# Skip all tests in this module if Python version is less than 3.10
pytestmark = pytest.mark.skipif(
    sys.version_info < (3, 10), reason="A2A requires Python 3.10+"
)

# Import dependencies with version checking
try:
  from a2a.types import AgentCapabilities
  from a2a.types import AgentCard
  from a2a.types import AgentProvider
  from a2a.types import AgentSkill
  from a2a.types import SecurityScheme
  from google.adk.a2a.utils.agent_card_builder import _build_agent_description
  from google.adk.a2a.utils.agent_card_builder import _build_llm_agent_description_with_instructions
  from google.adk.a2a.utils.agent_card_builder import _build_loop_description
  from google.adk.a2a.utils.agent_card_builder import _build_orchestration_skill
  from google.adk.a2a.utils.agent_card_builder import _build_parallel_description
  from google.adk.a2a.utils.agent_card_builder import _build_sequential_description
  from google.adk.a2a.utils.agent_card_builder import _convert_example_tool_examples
  from google.adk.a2a.utils.agent_card_builder import _extract_examples_from_instruction
  from google.adk.a2a.utils.agent_card_builder import _get_agent_skill_name
  from google.adk.a2a.utils.agent_card_builder import _get_agent_type
  from google.adk.a2a.utils.agent_card_builder import _get_default_description
  from google.adk.a2a.utils.agent_card_builder import _get_input_modes
  from google.adk.a2a.utils.agent_card_builder import _get_output_modes
  from google.adk.a2a.utils.agent_card_builder import _get_workflow_description
  from google.adk.a2a.utils.agent_card_builder import _replace_pronouns
  from google.adk.a2a.utils.agent_card_builder import AgentCardBuilder
  from google.adk.agents.base_agent import BaseAgent
  from google.adk.agents.llm_agent import LlmAgent
  from google.adk.agents.loop_agent import LoopAgent
  from google.adk.agents.parallel_agent import ParallelAgent
  from google.adk.agents.sequential_agent import SequentialAgent
  from google.adk.tools.example_tool import ExampleTool
except ImportError as e:
  if sys.version_info < (3, 10):
    # Imports are not needed since tests will be skipped due to pytestmark.
    # The imported names are only used within test methods, not at module level,
    # so no NameError occurs during module compilation.
    pass
  else:
    raise e


class TestAgentCardBuilder:
  """Test suite for AgentCardBuilder class."""

  def test_init_with_valid_agent(self):
    """Test successful initialization with valid agent."""
    # Arrange
    mock_agent = Mock(spec=BaseAgent)
    mock_agent.name = "test_agent"

    # Act
    builder = AgentCardBuilder(agent=mock_agent)

    # Assert
    assert builder._agent == mock_agent
    assert builder._rpc_url == "http://localhost:80/a2a"
    assert isinstance(builder._capabilities, AgentCapabilities)
    assert builder._doc_url is None
    assert builder._provider is None
    assert builder._security_schemes is None
    assert builder._agent_version == "0.0.1"

  def test_init_with_custom_parameters(self):
    """Test initialization with custom parameters."""
    # Arrange
    mock_agent = Mock(spec=BaseAgent)
    mock_agent.name = "test_agent"
    mock_capabilities = Mock(spec=AgentCapabilities)
    mock_provider = Mock(spec=AgentProvider)
    mock_security_schemes = {"test": Mock(spec=SecurityScheme)}

    # Act
    builder = AgentCardBuilder(
        agent=mock_agent,
        rpc_url="https://example.com/a2a",
        capabilities=mock_capabilities,
        doc_url="https://docs.example.com",
        provider=mock_provider,
        agent_version="1.2.3",
        security_schemes=mock_security_schemes,
    )

    # Assert
    assert builder._agent == mock_agent
    assert builder._rpc_url == "https://example.com/a2a"
    assert builder._capabilities == mock_capabilities
    assert builder._doc_url == "https://docs.example.com"
    assert builder._provider == mock_provider
    assert builder._security_schemes == mock_security_schemes
    assert builder._agent_version == "1.2.3"

  def test_init_with_none_agent(self):
    """Test initialization with None agent raises ValueError."""
    # Act & Assert
    with pytest.raises(ValueError, match="Agent cannot be None or empty."):
      AgentCardBuilder(agent=None)

  def test_init_with_empty_agent(self):
    """Test initialization with empty agent raises ValueError."""
    # Arrange
    mock_agent = None

    # Act & Assert
    with pytest.raises(ValueError, match="Agent cannot be None or empty."):
      AgentCardBuilder(agent=mock_agent)

  @patch("google.adk.a2a.utils.agent_card_builder._build_primary_skills")
  @patch("google.adk.a2a.utils.agent_card_builder._build_sub_agent_skills")
  async def test_build_success(
      self, mock_build_sub_skills, mock_build_primary_skills
  ):
    """Test successful agent card building."""
    # Arrange
    mock_agent = Mock(spec=BaseAgent)
    mock_agent.name = "test_agent"
    mock_agent.description = "Test agent description"

    mock_primary_skill = Mock(spec=AgentSkill)
    mock_sub_skill = Mock(spec=AgentSkill)
    mock_build_primary_skills.return_value = [mock_primary_skill]
    mock_build_sub_skills.return_value = [mock_sub_skill]

    builder = AgentCardBuilder(agent=mock_agent)

    # Act
    result = await builder.build()

    # Assert
    assert isinstance(result, AgentCard)
    assert result.name == "test_agent"
    assert result.description == "Test agent description"
    assert result.documentation_url is None
    assert result.url == "http://localhost:80/a2a"
    assert result.version == "0.0.1"
    assert result.skills == [mock_primary_skill, mock_sub_skill]
    assert result.default_input_modes == ["text/plain"]
    assert result.default_output_modes == ["text/plain"]
    assert result.supports_authenticated_extended_card is False
    assert result.provider is None
    assert result.security_schemes is None

  @patch("google.adk.a2a.utils.agent_card_builder._build_primary_skills")
  @patch("google.adk.a2a.utils.agent_card_builder._build_sub_agent_skills")
  async def test_build_with_custom_parameters(
      self, mock_build_sub_skills, mock_build_primary_skills
  ):
    """Test agent card building with custom parameters."""
    # Arrange
    mock_agent = Mock(spec=BaseAgent)
    mock_agent.name = "test_agent"
    mock_agent.description = None  # Should use default description

    mock_primary_skill = Mock(spec=AgentSkill)
    mock_sub_skill = Mock(spec=AgentSkill)
    mock_build_primary_skills.return_value = [mock_primary_skill]
    mock_build_sub_skills.return_value = [mock_sub_skill]

    mock_provider = Mock(spec=AgentProvider)
    mock_security_schemes = {"test": Mock(spec=SecurityScheme)}

    builder = AgentCardBuilder(
        agent=mock_agent,
        rpc_url="https://example.com/a2a/",
        doc_url="https://docs.example.com",
        provider=mock_provider,
        agent_version="2.0.0",
        security_schemes=mock_security_schemes,
    )

    # Act
    result = await builder.build()

    # Assert
    assert result.name == "test_agent"
    assert result.description == "An ADK Agent"  # Default description
    # The source code uses doc_url parameter but AgentCard expects documentation_url
    # Since the source code doesn't map doc_url to documentation_url, it will be None
    assert result.documentation_url is None
    assert (
        result.url == "https://example.com/a2a"
    )  # Should strip trailing slash
    assert result.version == "2.0.0"
    assert result.provider == mock_provider
    assert result.security_schemes == mock_security_schemes

  @patch("google.adk.a2a.utils.agent_card_builder._build_primary_skills")
  @patch("google.adk.a2a.utils.agent_card_builder._build_sub_agent_skills")
  async def test_build_raises_runtime_error_on_failure(
      self, mock_build_sub_skills, mock_build_primary_skills
  ):
    """Test that build raises RuntimeError when underlying functions fail."""
    # Arrange
    mock_agent = Mock(spec=BaseAgent)
    mock_agent.name = "test_agent"
    mock_build_primary_skills.side_effect = Exception("Test error")

    builder = AgentCardBuilder(agent=mock_agent)

    # Act & Assert
    with pytest.raises(
        RuntimeError,
        match="Failed to build agent card for test_agent: Test error",
    ):
      await builder.build()


class TestHelperFunctions:
  """Test suite for helper functions."""

  def test_get_agent_type_llm_agent(self):
    """Test _get_agent_type for LlmAgent."""
    # Arrange
    mock_agent = Mock(spec=LlmAgent)

    # Act
    result = _get_agent_type(mock_agent)

    # Assert
    assert result == "llm"

  def test_get_agent_type_sequential_agent(self):
    """Test _get_agent_type for SequentialAgent."""
    # Arrange
    mock_agent = Mock(spec=SequentialAgent)

    # Act
    result = _get_agent_type(mock_agent)

    # Assert
    assert result == "sequential_workflow"

  def test_get_agent_type_parallel_agent(self):
    """Test _get_agent_type for ParallelAgent."""
    # Arrange
    mock_agent = Mock(spec=ParallelAgent)

    # Act
    result = _get_agent_type(mock_agent)

    # Assert
    assert result == "parallel_workflow"

  def test_get_agent_type_loop_agent(self):
    """Test _get_agent_type for LoopAgent."""
    # Arrange
    mock_agent = Mock(spec=LoopAgent)

    # Act
    result = _get_agent_type(mock_agent)

    # Assert
    assert result == "loop_workflow"

  def test_get_agent_type_custom_agent(self):
    """Test _get_agent_type for custom agent."""
    # Arrange
    mock_agent = Mock(spec=BaseAgent)

    # Act
    result = _get_agent_type(mock_agent)

    # Assert
    assert result == "custom_agent"

  def test_get_agent_skill_name_llm_agent(self):
    """Test _get_agent_skill_name for LlmAgent."""
    # Arrange
    mock_agent = Mock(spec=LlmAgent)

    # Act
    result = _get_agent_skill_name(mock_agent)

    # Assert
    assert result == "model"

  def test_get_agent_skill_name_workflow_agents(self):
    """Test _get_agent_skill_name for workflow agents."""
    # Arrange
    mock_sequential = Mock(spec=SequentialAgent)
    mock_parallel = Mock(spec=ParallelAgent)
    mock_loop = Mock(spec=LoopAgent)

    # Act & Assert
    assert _get_agent_skill_name(mock_sequential) == "workflow"
    assert _get_agent_skill_name(mock_parallel) == "workflow"
    assert _get_agent_skill_name(mock_loop) == "workflow"

  def test_get_agent_skill_name_custom_agent(self):
    """Test _get_agent_skill_name for custom agent."""
    # Arrange
    mock_agent = Mock(spec=BaseAgent)

    # Act
    result = _get_agent_skill_name(mock_agent)

    # Assert
    assert result == "custom"

  def test_replace_pronouns_basic(self):
    """Test _replace_pronouns with basic pronoun replacement."""
    # Arrange
    text = "You should do your work and it will be yours."

    # Act
    result = _replace_pronouns(text)

    # Assert
    assert result == "I should do my work and it will be mine."

  def test_replace_pronouns_case_insensitive(self):
    """Test _replace_pronouns with case insensitive matching."""
    # Arrange
    text = "YOU should do YOUR work and it will be YOURS."

    # Act
    result = _replace_pronouns(text)

    # Assert
    assert result == "I should do my work and it will be mine."

  def test_replace_pronouns_mixed_case(self):
    """Test _replace_pronouns with mixed case."""
    # Arrange
    text = "You should do Your work and it will be Yours."

    # Act
    result = _replace_pronouns(text)

    # Assert
    assert result == "I should do my work and it will be mine."

  def test_replace_pronouns_no_pronouns(self):
    """Test _replace_pronouns with no pronouns."""
    # Arrange
    text = "This is a test message without pronouns."

    # Act
    result = _replace_pronouns(text)

    # Assert
    assert result == text

  def test_replace_pronouns_partial_matches(self):
    """Test _replace_pronouns with partial matches that shouldn't be replaced."""
    # Arrange
    text = "youth, yourself, yourname"

    # Act
    result = _replace_pronouns(text)

    # Assert
    assert result == "youth, yourself, yourname"  # No changes

  def test_replace_pronouns_phrases(self):
    """Test _replace_pronouns with phrases that should be replaced."""
    # Arrange
    text = "You are a helpful chatbot"

    # Act
    result = _replace_pronouns(text)

    # Assert
    assert result == "I am a helpful chatbot"

  def test_get_default_description_llm_agent(self):
    """Test _get_default_description for LlmAgent."""
    # Arrange
    mock_agent = Mock(spec=LlmAgent)

    # Act
    result = _get_default_description(mock_agent)

    # Assert
    assert result == "An LLM-based agent"

  def test_get_default_description_sequential_agent(self):
    """Test _get_default_description for SequentialAgent."""
    # Arrange
    mock_agent = Mock(spec=SequentialAgent)

    # Act
    result = _get_default_description(mock_agent)

    # Assert
    assert result == "A sequential workflow agent"

  def test_get_default_description_parallel_agent(self):
    """Test _get_default_description for ParallelAgent."""
    # Arrange
    mock_agent = Mock(spec=ParallelAgent)

    # Act
    result = _get_default_description(mock_agent)

    # Assert
    assert result == "A parallel workflow agent"

  def test_get_default_description_loop_agent(self):
    """Test _get_default_description for LoopAgent."""
    # Arrange
    mock_agent = Mock(spec=LoopAgent)

    # Act
    result = _get_default_description(mock_agent)

    # Assert
    assert result == "A loop workflow agent"

  def test_get_default_description_custom_agent(self):
    """Test _get_default_description for custom agent."""
    # Arrange
    mock_agent = Mock(spec=BaseAgent)

    # Act
    result = _get_default_description(mock_agent)

    # Assert
    assert result == "A custom agent"

  def test_get_input_modes_llm_agent(self):
    """Test _get_input_modes for LlmAgent."""
    # Arrange
    mock_agent = Mock(spec=LlmAgent)

    # Act
    result = _get_input_modes(mock_agent)

    # Assert
    assert result is None  # Currently returns None for all cases

  def test_get_input_modes_non_llm_agent(self):
    """Test _get_input_modes for non-LlmAgent."""
    # Arrange
    mock_agent = Mock(spec=BaseAgent)

    # Act
    result = _get_input_modes(mock_agent)

    # Assert
    assert result is None

  def test_get_output_modes_llm_agent_with_config(self):
    """Test _get_output_modes for LlmAgent with response_modalities."""
    # Arrange
    mock_config = Mock()
    mock_config.response_modalities = ["text/plain", "application/json"]
    mock_agent = Mock(spec=LlmAgent)
    mock_agent.generate_content_config = mock_config

    # Act
    result = _get_output_modes(mock_agent)

    # Assert
    assert result == ["text/plain", "application/json"]

  def test_get_output_modes_llm_agent_without_config(self):
    """Test _get_output_modes for LlmAgent without config."""
    # Arrange
    mock_agent = Mock(spec=LlmAgent)
    mock_agent.generate_content_config = None

    # Act
    result = _get_output_modes(mock_agent)

    # Assert
    assert result is None

  def test_get_output_modes_llm_agent_without_response_modalities(self):
    """Test _get_output_modes for LlmAgent without response_modalities."""
    # Arrange
    mock_config = Mock()
    del mock_config.response_modalities
    mock_agent = Mock(spec=LlmAgent)
    mock_agent.generate_content_config = mock_config

    # Act
    result = _get_output_modes(mock_agent)

    # Assert
    assert result is None

  def test_get_output_modes_non_llm_agent(self):
    """Test _get_output_modes for non-LlmAgent."""
    # Arrange
    mock_agent = Mock(spec=BaseAgent)

    # Act
    result = _get_output_modes(mock_agent)

    # Assert
    assert result is None


class TestDescriptionBuildingFunctions:
  """Test suite for description building functions."""

  def test_build_agent_description_with_description(self):
    """Test _build_agent_description with agent description."""
    # Arrange
    mock_agent = Mock(spec=BaseAgent)
    mock_agent.description = "Test agent description"
    mock_agent.sub_agents = []

    # Act
    result = _build_agent_description(mock_agent)

    # Assert
    assert result == "Test agent description"

  def test_build_agent_description_without_description(self):
    """Test _build_agent_description without agent description."""
    # Arrange
    mock_agent = Mock(spec=BaseAgent)
    mock_agent.description = None
    mock_agent.sub_agents = []

    # Act
    result = _build_agent_description(mock_agent)

    # Assert
    assert result == "A custom agent"  # Default description

  def test_build_llm_agent_description_with_instructions(self):
    """Test _build_llm_agent_description_with_instructions with all components."""
    # Arrange
    mock_agent = Mock(spec=LlmAgent)
    mock_agent.description = "Test agent"
    mock_agent.instruction = "You should help users."
    mock_agent.global_instruction = "Your role is to assist."

    # Act
    result = _build_llm_agent_description_with_instructions(mock_agent)

    # Assert
    assert result == "Test agent I should help users. my role is to assist."

  def test_build_llm_agent_description_without_instructions(self):
    """Test _build_llm_agent_description_with_instructions without instructions."""
    # Arrange
    mock_agent = Mock(spec=LlmAgent)
    mock_agent.description = "Test agent"
    mock_agent.instruction = None
    mock_agent.global_instruction = None

    # Act
    result = _build_llm_agent_description_with_instructions(mock_agent)

    # Assert
    assert result == "Test agent"

  def test_build_llm_agent_description_without_description(self):
    """Test _build_llm_agent_description_with_instructions without description."""
    # Arrange
    mock_agent = Mock(spec=LlmAgent)
    mock_agent.description = None
    mock_agent.instruction = "You should help users."
    mock_agent.global_instruction = None

    # Act
    result = _build_llm_agent_description_with_instructions(mock_agent)

    # Assert
    assert result == "I should help users."

  def test_build_llm_agent_description_empty_all(self):
    """Test _build_llm_agent_description_with_instructions with all empty."""
    # Arrange
    mock_agent = Mock(spec=LlmAgent)
    mock_agent.description = None
    mock_agent.instruction = None
    mock_agent.global_instruction = None

    # Act
    result = _build_llm_agent_description_with_instructions(mock_agent)

    # Assert
    assert result == "An LLM-based agent"  # Default description

  def test_get_workflow_description_sequential_agent(self):
    """Test _get_workflow_description for SequentialAgent."""
    # Arrange
    mock_sub_agent1 = Mock(spec=BaseAgent)
    mock_sub_agent1.name = "agent1"
    mock_sub_agent1.description = "First agent"
    mock_sub_agent2 = Mock(spec=BaseAgent)
    mock_sub_agent2.name = "agent2"
    mock_sub_agent2.description = "Second agent"

    mock_agent = Mock(spec=SequentialAgent)
    mock_agent.sub_agents = [mock_sub_agent1, mock_sub_agent2]

    # Act
    result = _get_workflow_description(mock_agent)

    # Assert
    assert result is not None
    assert (
        result
        == "First, this agent will First agent Finally, this agent will Second"
        " agent."
    )

  def test_get_workflow_description_parallel_agent(self):
    """Test _get_workflow_description for ParallelAgent."""
    # Arrange
    mock_sub_agent1 = Mock(spec=BaseAgent)
    mock_sub_agent1.name = "agent1"
    mock_sub_agent1.description = "First agent"
    mock_sub_agent2 = Mock(spec=BaseAgent)
    mock_sub_agent2.name = "agent2"
    mock_sub_agent2.description = "Second agent"

    mock_agent = Mock(spec=ParallelAgent)
    mock_agent.sub_agents = [mock_sub_agent1, mock_sub_agent2]

    # Act
    result = _get_workflow_description(mock_agent)

    # Assert
    assert result is not None
    assert (
        result == "This agent will First agent and Second agent simultaneously."
    )

  def test_get_workflow_description_loop_agent(self):
    """Test _get_workflow_description for LoopAgent."""
    # Arrange
    mock_sub_agent1 = Mock(spec=BaseAgent)
    mock_sub_agent1.name = "agent1"
    mock_sub_agent1.description = "First agent"
    mock_sub_agent2 = Mock(spec=BaseAgent)
    mock_sub_agent2.name = "agent2"
    mock_sub_agent2.description = "Second agent"

    mock_agent = Mock(spec=LoopAgent)
    mock_agent.sub_agents = [mock_sub_agent1, mock_sub_agent2]
    mock_agent.max_iterations = 5

    # Act
    result = _get_workflow_description(mock_agent)

    # Assert
    assert (
        result
        == "This agent will First agent and Second agent in a loop (max 5"
        " iterations)."
    )

  def test_get_workflow_description_loop_agent_unlimited(self):
    """Test _get_workflow_description for LoopAgent with unlimited iterations."""
    # Arrange
    mock_sub_agent1 = Mock(spec=BaseAgent)
    mock_sub_agent1.name = "agent1"
    mock_sub_agent1.description = "First agent"

    mock_agent = Mock(spec=LoopAgent)
    mock_agent.sub_agents = [mock_sub_agent1]
    mock_agent.max_iterations = None

    # Act
    result = _get_workflow_description(mock_agent)

    # Assert
    assert (
        result
        == "This agent will First agent in a loop (max unlimited iterations)."
    )

  def test_get_workflow_description_no_sub_agents(self):
    """Test _get_workflow_description for agent without sub-agents."""
    # Arrange
    mock_agent = Mock(spec=SequentialAgent)
    mock_agent.sub_agents = []

    # Act
    result = _get_workflow_description(mock_agent)

    # Assert
    assert result is None

  def test_get_workflow_description_custom_agent(self):
    """Test _get_workflow_description for custom agent."""
    # Arrange
    mock_agent = Mock(spec=BaseAgent)
    mock_agent.sub_agents = [Mock(spec=BaseAgent)]

    # Act
    result = _get_workflow_description(mock_agent)

    # Assert
    assert result is None

  def test_build_sequential_description_single_agent(self):
    """Test _build_sequential_description with single sub-agent."""
    # Arrange
    mock_sub_agent = Mock(spec=BaseAgent)
    mock_sub_agent.name = "agent1"
    mock_sub_agent.description = "First agent"

    mock_agent = Mock(spec=SequentialAgent)
    mock_agent.sub_agents = [mock_sub_agent]

    # Act
    result = _build_sequential_description(mock_agent)

    # Assert
    assert result == "First, this agent will First agent."

  def test_build_sequential_description_multiple_agents(self):
    """Test _build_sequential_description with multiple sub-agents."""
    # Arrange
    mock_sub_agent1 = Mock(spec=BaseAgent)
    mock_sub_agent1.name = "agent1"
    mock_sub_agent1.description = "First agent"
    mock_sub_agent2 = Mock(spec=BaseAgent)
    mock_sub_agent2.name = "agent2"
    mock_sub_agent2.description = "Second agent"
    mock_sub_agent3 = Mock(spec=BaseAgent)
    mock_sub_agent3.name = "agent3"
    mock_sub_agent3.description = "Third agent"

    mock_agent = Mock(spec=SequentialAgent)
    mock_agent.sub_agents = [mock_sub_agent1, mock_sub_agent2, mock_sub_agent3]

    # Act
    result = _build_sequential_description(mock_agent)

    # Assert
    assert (
        result
        == "First, this agent will First agent Then, this agent will Second"
        " agent Finally, this agent will Third agent."
    )

  def test_build_sequential_description_without_descriptions(self):
    """Test _build_sequential_description with sub-agents without descriptions."""
    # Arrange
    mock_sub_agent1 = Mock(spec=BaseAgent)
    mock_sub_agent1.name = "agent1"
    mock_sub_agent1.description = None
    mock_sub_agent2 = Mock(spec=BaseAgent)
    mock_sub_agent2.name = "agent2"
    mock_sub_agent2.description = None

    mock_agent = Mock(spec=SequentialAgent)
    mock_agent.sub_agents = [mock_sub_agent1, mock_sub_agent2]

    # Act
    result = _build_sequential_description(mock_agent)

    # Assert
    assert (
        result
        == "First, this agent will execute the agent1 agent Finally, this agent"
        " will execute the agent2 agent."
    )

  def test_build_parallel_description_single_agent(self):
    """Test _build_parallel_description with single sub-agent."""
    # Arrange
    mock_sub_agent = Mock(spec=BaseAgent)
    mock_sub_agent.name = "agent1"
    mock_sub_agent.description = "First agent"

    mock_agent = Mock(spec=ParallelAgent)
    mock_agent.sub_agents = [mock_sub_agent]

    # Act
    result = _build_parallel_description(mock_agent)

    # Assert
    assert result == "This agent will First agent simultaneously."

  def test_build_parallel_description_multiple_agents(self):
    """Test _build_parallel_description with multiple sub-agents."""
    # Arrange
    mock_sub_agent1 = Mock(spec=BaseAgent)
    mock_sub_agent1.name = "agent1"
    mock_sub_agent1.description = "First agent"
    mock_sub_agent2 = Mock(spec=BaseAgent)
    mock_sub_agent2.name = "agent2"
    mock_sub_agent2.description = "Second agent"
    mock_sub_agent3 = Mock(spec=BaseAgent)
    mock_sub_agent3.name = "agent3"
    mock_sub_agent3.description = "Third agent"

    mock_agent = Mock(spec=ParallelAgent)
    mock_agent.sub_agents = [mock_sub_agent1, mock_sub_agent2, mock_sub_agent3]

    # Act
    result = _build_parallel_description(mock_agent)

    # Assert
    assert (
        result
        == "This agent will First agent , Second agent and Third agent"
        " simultaneously."
    )

  def test_build_loop_description_single_agent(self):
    """Test _build_loop_description with single sub-agent."""
    # Arrange
    mock_sub_agent = Mock(spec=BaseAgent)
    mock_sub_agent.name = "agent1"
    mock_sub_agent.description = "First agent"

    mock_agent = Mock(spec=LoopAgent)
    mock_agent.sub_agents = [mock_sub_agent]
    mock_agent.max_iterations = 3

    # Act
    result = _build_loop_description(mock_agent)

    # Assert
    assert result == "This agent will First agent in a loop (max 3 iterations)."

  def test_build_loop_description_multiple_agents(self):
    """Test _build_loop_description with multiple sub-agents."""
    # Arrange
    mock_sub_agent1 = Mock(spec=BaseAgent)
    mock_sub_agent1.name = "agent1"
    mock_sub_agent1.description = "First agent"
    mock_sub_agent2 = Mock(spec=BaseAgent)
    mock_sub_agent2.name = "agent2"
    mock_sub_agent2.description = "Second agent"

    mock_agent = Mock(spec=LoopAgent)
    mock_agent.sub_agents = [mock_sub_agent1, mock_sub_agent2]
    mock_agent.max_iterations = 10

    # Act
    result = _build_loop_description(mock_agent)

    # Assert
    assert (
        result
        == "This agent will First agent and Second agent in a loop (max 10"
        " iterations)."
    )

  def test_build_orchestration_skill_with_sub_agents(self):
    """Test _build_orchestration_skill with sub-agents."""
    # Arrange
    mock_sub_agent1 = Mock(spec=BaseAgent)
    mock_sub_agent1.name = "agent1"
    mock_sub_agent1.description = "First agent description"
    mock_sub_agent2 = Mock(spec=BaseAgent)
    mock_sub_agent2.name = "agent2"
    mock_sub_agent2.description = "Second agent description"

    mock_agent = Mock(spec=BaseAgent)
    mock_agent.name = "main_agent"
    mock_agent.sub_agents = [mock_sub_agent1, mock_sub_agent2]

    # Act
    result = _build_orchestration_skill(mock_agent, "sequential_workflow")

    # Assert
    assert result is not None
    assert result.id == "main_agent-sub-agents"
    assert result.name == "sub-agents"
    assert (
        result.description
        == "Orchestrates: agent1: First agent description; agent2: Second agent"
        " description"
    )
    assert result.tags == ["sequential_workflow", "orchestration"]

  def test_build_orchestration_skill_without_descriptions(self):
    """Test _build_orchestration_skill with sub-agents without descriptions."""
    # Arrange
    mock_sub_agent1 = Mock(spec=BaseAgent)
    mock_sub_agent1.name = "agent1"
    mock_sub_agent1.description = None
    mock_sub_agent2 = Mock(spec=BaseAgent)
    mock_sub_agent2.name = "agent2"
    mock_sub_agent2.description = None

    mock_agent = Mock(spec=BaseAgent)
    mock_agent.name = "main_agent"
    mock_agent.sub_agents = [mock_sub_agent1, mock_sub_agent2]

    # Act
    result = _build_orchestration_skill(mock_agent, "parallel_workflow")

    # Assert
    assert result is not None
    assert (
        result.description
        == "Orchestrates: agent1: No description; agent2: No description"
    )

  def test_build_orchestration_skill_no_sub_agents(self):
    """Test _build_orchestration_skill with no sub-agents."""
    # Arrange
    mock_agent = Mock(spec=BaseAgent)
    mock_agent.sub_agents = []

    # Act
    result = _build_orchestration_skill(mock_agent, "custom_agent")

    # Assert
    assert result is None


class TestExampleExtractionFunctions:
  """Test suite for example extraction functions."""

  def test_convert_example_tool_examples_with_model_dump(self):
    """Test _convert_example_tool_examples with examples that have model_dump."""
    # Arrange
    mock_input = Mock()
    mock_input.model_dump.return_value = {"text": "test input"}
    mock_output1 = Mock()
    mock_output1.model_dump.return_value = {"text": "test output 1"}
    mock_output2 = Mock()
    mock_output2.model_dump.return_value = {"text": "test output 2"}

    mock_example = Mock()
    mock_example.input = mock_input
    mock_example.output = [mock_output1, mock_output2]

    mock_tool = Mock(spec=ExampleTool)
    mock_tool.examples = [mock_example]

    # Act
    result = _convert_example_tool_examples(mock_tool)

    # Assert
    assert len(result) == 1
    assert result[0]["input"] == {"text": "test input"}
    assert result[0]["output"] == [
        {"text": "test output 1"},
        {"text": "test output 2"},
    ]

  def test_convert_example_tool_examples_without_model_dump(self):
    """Test _convert_example_tool_examples with examples without model_dump."""
    # Arrange
    mock_input = {"text": "test input"}
    mock_output1 = {"text": "test output 1"}
    mock_output2 = {"text": "test output 2"}

    mock_example = Mock()
    mock_example.input = mock_input
    mock_example.output = [mock_output1, mock_output2]

    mock_tool = Mock(spec=ExampleTool)
    mock_tool.examples = [mock_example]

    # Act
    result = _convert_example_tool_examples(mock_tool)

    # Assert
    assert len(result) == 1
    assert result[0]["input"] == {"text": "test input"}
    assert result[0]["output"] == [
        {"text": "test output 1"},
        {"text": "test output 2"},
    ]

  def test_convert_example_tool_examples_multiple_examples(self):
    """Test _convert_example_tool_examples with multiple examples."""
    # Arrange
    mock_example1 = Mock()
    mock_example1.input = {"text": "input 1"}
    mock_example1.output = [{"text": "output 1"}]

    mock_example2 = Mock()
    mock_example2.input = {"text": "input 2"}
    mock_example2.output = [{"text": "output 2"}]

    mock_tool = Mock(spec=ExampleTool)
    mock_tool.examples = [mock_example1, mock_example2]

    # Act
    result = _convert_example_tool_examples(mock_tool)

    # Assert
    assert len(result) == 2
    assert result[0]["input"] == {"text": "input 1"}
    assert result[0]["output"] == [{"text": "output 1"}]
    assert result[1]["input"] == {"text": "input 2"}
    assert result[1]["output"] == [{"text": "output 2"}]

  def test_convert_example_tool_examples_empty_list(self):
    """Test _convert_example_tool_examples with empty examples list."""
    # Arrange
    mock_tool = Mock(spec=ExampleTool)
    mock_tool.examples = []

    # Act
    result = _convert_example_tool_examples(mock_tool)

    # Assert
    assert result == []

  def test_extract_examples_from_instruction_with_examples(self):
    """Test _extract_examples_from_instruction with valid examples."""
    # Arrange
    instruction = (
        'Example Query: "What is the weather?" Example Response: "The weather'
        ' is sunny."'
    )

    # Act
    result = _extract_examples_from_instruction(instruction)

    # Assert
    # The function processes each pattern separately, so it won't find pairs
    # from different patterns. This test should return None.
    assert result is None

  def test_extract_examples_from_instruction_with_multiple_examples(self):
    """Test _extract_examples_from_instruction with multiple examples."""
    # Arrange
    instruction = """
    Example Query: "What is the weather?" Example Response: "The weather is sunny."
    Example Query: "What time is it?" Example Response: "It is 3 PM."
    """

    # Act
    result = _extract_examples_from_instruction(instruction)

    # Assert
    # The function finds matches but pairs them incorrectly due to how patterns are processed
    assert result is not None
    assert isinstance(result, list)
    assert len(result) == 2
    # The function pairs consecutive matches from the same pattern
    assert result[0]["input"] == {"text": "What is the weather?"}
    assert result[0]["output"] == [{"text": "What time is it?"}]
    assert result[1]["input"] == {"text": "The weather is sunny."}
    assert result[1]["output"] == [{"text": "It is 3 PM."}]

  def test_extract_examples_from_instruction_with_different_patterns(self):
    """Test _extract_examples_from_instruction with different example patterns."""
    # Arrange
    instruction = (
        'Example: "What is the weather?" Example Response: "The weather is'
        ' sunny."'
    )

    # Act
    result = _extract_examples_from_instruction(instruction)

    # Assert
    # The function processes each pattern separately, so it won't find pairs
    # from different patterns. This test should return None.
    assert result is None

  def test_extract_examples_from_instruction_case_insensitive(self):
    """Test _extract_examples_from_instruction with case insensitive matching."""
    # Arrange
    instruction = (
        'example query: "What is the weather?" example response: "The weather'
        ' is sunny."'
    )

    # Act
    result = _extract_examples_from_instruction(instruction)

    # Assert
    # The function processes each pattern separately, so it won't find pairs
    # from different patterns. This test should return None.
    assert result is None

  def test_extract_examples_from_instruction_no_examples(self):
    """Test _extract_examples_from_instruction with no examples."""
    # Arrange
    instruction = "This is a regular instruction without any examples."

    # Act
    result = _extract_examples_from_instruction(instruction)

    # Assert
    assert result is None

  def test_extract_examples_from_instruction_odd_number_of_matches(self):
    """Test _extract_examples_from_instruction with odd number of matches."""
    # Arrange
    instruction = (
        'Example Query: "What is the weather?" Example Response: "The weather'
        ' is sunny." Example Query: "What time is it?"'
    )

    # Act
    result = _extract_examples_from_instruction(instruction)

    # Assert
    # The function finds matches but only pairs complete pairs
    assert result is not None
    assert isinstance(result, list)
    assert len(result) == 1  # Only complete pairs should be included
    assert result[0]["input"] == {"text": "What is the weather?"}
    assert result[0]["output"] == [{"text": "What time is it?"}]
