# 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 ntpath
import os
from pathlib import Path
from pathlib import PureWindowsPath
import re
import sys
import tempfile
from textwrap import dedent

from google.adk.cli.utils import agent_loader as agent_loader_module
from google.adk.cli.utils.agent_loader import AgentLoader
from pydantic import ValidationError
import pytest


class TestAgentLoader:
  """Unit tests for AgentLoader focusing on interface behavior."""

  @pytest.fixture(autouse=True)
  def cleanup_sys_path(self):
    """Ensure sys.path is restored after each test."""
    original_path = sys.path.copy()
    original_env = os.environ.copy()
    yield
    sys.path[:] = original_path
    # Restore environment variables
    os.environ.clear()
    os.environ.update(original_env)

  def create_agent_structure(
      self, temp_dir: Path, agent_name: str, structure_type: str
  ):
    """Create different agent structures for testing.

    Args:
        temp_dir: The temporary directory to create the agent in
        agent_name: Name of the agent
        structure_type: One of 'module', 'package_with_root', 'package_with_agent_module'
    """
    if structure_type == "module":
      # Structure: agents_dir/agent_name.py
      agent_file = temp_dir / f"{agent_name}.py"
      agent_file.write_text(dedent(f"""
                import os
                from google.adk.agents.base_agent import BaseAgent
                from typing import Any

                class {agent_name.title()}Agent(BaseAgent):
                    agent_id: Any = None
                    config: Any = None

                    def __init__(self):
                        super().__init__(name="{agent_name}")
                        self.agent_id = id(self)
                        self.config = os.environ.get("AGENT_CONFIG", "default")

                root_agent = {agent_name.title()}Agent()


            """))

    elif structure_type == "package_with_root":
      # Structure: agents_dir/agent_name/__init__.py (with root_agent)
      agent_dir = temp_dir / agent_name
      agent_dir.mkdir()
      init_file = agent_dir / "__init__.py"
      init_file.write_text(dedent(f"""
                import os
                from google.adk.agents.base_agent import BaseAgent
                from typing import Any

                class {agent_name.title()}Agent(BaseAgent):
                    agent_id: Any = None
                    config: Any = None

                    def __init__(self):
                        super().__init__(name="{agent_name}")
                        self.agent_id = id(self)
                        self.config = os.environ.get("AGENT_CONFIG", "default")

                root_agent = {agent_name.title()}Agent()
            """))

    elif structure_type == "package_with_agent_module":
      # Structure: agents_dir/agent_name/agent.py
      agent_dir = temp_dir / agent_name
      agent_dir.mkdir()

      # Create __init__.py
      init_file = agent_dir / "__init__.py"
      init_file.write_text("")

      # Create agent.py with root_agent
      agent_file = agent_dir / "agent.py"
      agent_file.write_text(dedent(f"""
                import os
                from google.adk.agents.base_agent import BaseAgent
                from typing import Any

                class {agent_name.title()}Agent(BaseAgent):
                    agent_id: Any = None
                    config: Any = None

                    def __init__(self):
                        super().__init__(name="{agent_name}")
                        self.agent_id = id(self)
                        self.config = os.environ.get("AGENT_CONFIG", "default")

                root_agent = {agent_name.title()}Agent()
            """))

  def create_env_file(self, temp_dir: Path, agent_name: str, env_vars: dict):
    """Create a .env file for the agent."""
    env_file = temp_dir / agent_name / ".env"
    env_file.parent.mkdir(exist_ok=True)

    env_content = "\n".join(
        [f"{key}={value}" for key, value in env_vars.items()]
    )
    env_file.write_text(env_content)

  def test_load_agent_as_module(self):
    """Test loading an agent structured as a single module file."""
    with tempfile.TemporaryDirectory() as temp_dir:
      temp_path = Path(temp_dir)

      # Create agent as module
      self.create_agent_structure(temp_path, "module_agent", "module")

      # Load the agent
      loader = AgentLoader(str(temp_path))
      agent = loader.load_agent("module_agent")

      # Assert agent was loaded correctly
      assert agent.name == "module_agent"
      assert hasattr(agent, "agent_id")
      assert agent.config == "default"

  def test_load_agent_as_package_with_root_agent(self):
    """Test loading an agent structured as a package with root_agent in __init__.py."""
    with tempfile.TemporaryDirectory() as temp_dir:
      temp_path = Path(temp_dir)

      # Create agent as package
      self.create_agent_structure(
          temp_path, "package_agent", "package_with_root"
      )

      # Load the agent
      loader = AgentLoader(str(temp_path))
      agent = loader.load_agent("package_agent")

      # Assert agent was loaded correctly
      assert agent.name == "package_agent"
      assert hasattr(agent, "agent_id")

  def test_load_agent_as_package_with_agent_module(self):
    """Test loading an agent structured as a package with separate agent.py module."""
    with tempfile.TemporaryDirectory() as temp_dir:
      temp_path = Path(temp_dir)

      # Create agent as package with agent.py
      self.create_agent_structure(
          temp_path, "modular_agent", "package_with_agent_module"
      )

      # Load the agent
      loader = AgentLoader(str(temp_path))
      agent = loader.load_agent("modular_agent")

      # Assert agent was loaded correctly
      assert agent.name == "modular_agent"
      assert hasattr(agent, "agent_id")

  def test_agent_caching_returns_same_instance(self):
    """Test that loading the same agent twice returns the same instance."""
    with tempfile.TemporaryDirectory() as temp_dir:
      temp_path = Path(temp_dir)

      # Create agent
      self.create_agent_structure(temp_path, "cached_agent", "module")

      # Load the agent twice
      loader = AgentLoader(str(temp_path))
      agent1 = loader.load_agent("cached_agent")
      agent2 = loader.load_agent("cached_agent")

      # Assert same instance is returned
      assert agent1 is agent2
      assert agent1.agent_id == agent2.agent_id

  def test_env_loading_for_agent(self):
    """Test that .env file is loaded for the agent."""
    with tempfile.TemporaryDirectory() as temp_dir:
      temp_path = Path(temp_dir)

      # Create agent and .env file
      self.create_agent_structure(temp_path, "env_agent", "package_with_root")
      self.create_env_file(
          temp_path,
          "env_agent",
          {"AGENT_CONFIG": "production", "AGENT_SECRET": "test_secret_123"},
      )

      # Load the agent
      loader = AgentLoader(str(temp_path))
      agent = loader.load_agent("env_agent")

      # Assert environment variables were loaded
      assert agent.config == "production"
      assert os.environ.get("AGENT_SECRET") == "test_secret_123"

  def test_loading_order_preference(self):
    """Test that module/package is preferred over agent.py in a sub-package."""
    with tempfile.TemporaryDirectory() as temp_dir:
      temp_path = Path(temp_dir)
      agent_name = "order_test_agent"

      # Create structure 1: agents_dir/agent_name.py (expected to be loaded)
      agent_module_file = temp_path / f"{agent_name}.py"
      agent_module_file.write_text(dedent(f"""
                from google.adk.agents.base_agent import BaseAgent
                class ModuleAgent(BaseAgent):
                    def __init__(self):
                        super().__init__(name="{agent_name}_module_version")
                root_agent = ModuleAgent()
            """))

      # Create structure 2: agents_dir/agent_name/agent.py (should be ignored)
      agent_package_dir = temp_path / agent_name
      agent_package_dir.mkdir()
      agent_submodule_file = agent_package_dir / "agent.py"
      agent_submodule_file.write_text(dedent(f"""
                from google.adk.agents.base_agent import BaseAgent
                class SubmoduleAgent(BaseAgent):
                    def __init__(self):
                        super().__init__(name="{agent_name}_submodule_version")
                root_agent = SubmoduleAgent()
            """))

      loader = AgentLoader(str(temp_path))
      agent = loader.load_agent(agent_name)

      # Assert that the module version was loaded due to the new loading order
      assert agent.name == f"{agent_name}_module_version"

  def test_load_multiple_different_agents(self):
    """Test loading multiple different agents."""
    with tempfile.TemporaryDirectory() as temp_dir:
      temp_path = Path(temp_dir)

      # Create multiple agents with different structures
      self.create_agent_structure(temp_path, "agent_one", "module")
      self.create_agent_structure(temp_path, "agent_two", "package_with_root")
      self.create_agent_structure(
          temp_path, "agent_three", "package_with_agent_module"
      )

      # Load all agents
      loader = AgentLoader(str(temp_path))
      agent1 = loader.load_agent("agent_one")
      agent2 = loader.load_agent("agent_two")
      agent3 = loader.load_agent("agent_three")

      # Assert all agents were loaded correctly and are different instances
      assert agent1.name == "agent_one"
      assert agent2.name == "agent_two"
      assert agent3.name == "agent_three"
      assert agent1 is not agent2
      assert agent2 is not agent3
      assert agent1.agent_id != agent2.agent_id != agent3.agent_id

  def test_error_messages_use_os_sep_consistently(self):
    """Verify error messages use os.sep instead of hardcoded '/'."""
    del self
    with tempfile.TemporaryDirectory() as temp_dir:
      loader = AgentLoader(temp_dir)
      agent_name = "missing_agent"

      expected_path = os.path.join(temp_dir, agent_name)

      with pytest.raises(ValueError) as exc_info:
        loader.load_agent(agent_name)

      exc_info.match(re.escape(expected_path))
      exc_info.match(re.escape(f"{agent_name}{os.sep}root_agent.yaml"))
      exc_info.match(re.escape(f"<agents_dir>{os.sep}"))

  def test_agent_loader_with_mocked_windows_path(self, monkeypatch):
    """Mock Path() to simulate Windows behavior and catch regressions.

    REGRESSION TEST: Fails with rstrip('/'), passes with str(Path()).
    """
    del self
    windows_path = "C:\\Users\\dev\\agents\\"

    with monkeypatch.context() as m:
      m.setattr(
          agent_loader_module,
          "Path",
          lambda path_str: PureWindowsPath(path_str),
      )
      loader = AgentLoader(windows_path)

      expected = str(PureWindowsPath(windows_path))
      assert loader.agents_dir == expected
      assert not loader.agents_dir.endswith("\\")
      assert not loader.agents_dir.endswith("/")

  def test_agent_not_found_error(self):
    """Test that appropriate error is raised when agent is not found."""
    with tempfile.TemporaryDirectory() as temp_dir:
      loader = AgentLoader(temp_dir)
      agents_dir = temp_dir  # For use in the expected message string

      # Try to load nonexistent agent
      with pytest.raises(ValueError) as exc_info:
        loader.load_agent("nonexistent_agent")

      expected_msg_part_1 = "No root_agent found for 'nonexistent_agent'."
      expected_msg_part_2 = (
          "Searched in 'nonexistent_agent.agent.root_agent',"
          " 'nonexistent_agent.root_agent' and"
          " 'nonexistent_agent/root_agent.yaml'."
      )
      expected_msg_part_3 = (
          f"Ensure '{agents_dir}/nonexistent_agent' is structured correctly"
      )

      assert expected_msg_part_1 in str(exc_info.value)
      assert expected_msg_part_2 in str(exc_info.value)
      assert expected_msg_part_3 in str(exc_info.value)

  def test_agent_without_root_agent_error(self):
    """Test that appropriate error is raised when agent has no root_agent."""
    with tempfile.TemporaryDirectory() as temp_dir:
      temp_path = Path(temp_dir)

      # Create agent without root_agent
      agent_file = temp_path / "broken_agent.py"
      agent_file.write_text(dedent("""
                class BrokenAgent:
                    def __init__(self):
                        self.name = "broken"

                # Note: No root_agent defined
            """))

      loader = AgentLoader(str(temp_path))

      # Try to load agent without root_agent
      with pytest.raises(ValueError) as exc_info:
        loader.load_agent("broken_agent")

      assert "No root_agent found for 'broken_agent'" in str(exc_info.value)

  def test_agent_internal_module_not_found_error(self):
    """Test error when an agent tries to import a nonexistent module."""
    with tempfile.TemporaryDirectory() as temp_dir:
      temp_path = Path(temp_dir)
      agent_name = "importer_agent"

      # Create agent that imports a nonexistent module
      agent_file = temp_path / f"{agent_name}.py"
      agent_file.write_text(dedent(f"""
                from google.adk.agents.base_agent import BaseAgent
                import non_existent_module  # This will fail

                class {agent_name.title()}Agent(BaseAgent):
                    def __init__(self):
                        super().__init__(name="{agent_name}")

                root_agent = {agent_name.title()}Agent()
            """))

      loader = AgentLoader(str(temp_path))
      with pytest.raises(ModuleNotFoundError) as exc_info:
        loader.load_agent(agent_name)

      assert f"Fail to load '{agent_name}' module." in str(exc_info.value)
      assert "No module named 'non_existent_module'" in str(exc_info.value)

  def test_agent_internal_syntax_error(self):
    """Test other import errors within an agent's code (e.g., SyntaxError)."""
    with tempfile.TemporaryDirectory() as temp_dir:
      temp_path = Path(temp_dir)
      agent_name = "syntax_error_agent"

      # Create agent with a syntax error (which leads to ImportError)
      agent_file = temp_path / f"{agent_name}.py"
      agent_file.write_text(dedent(f"""
                from google.adk.agents.base_agent import BaseAgent

                # Invalid syntax
                this is not valid python code

                class {agent_name.title()}Agent(BaseAgent):
                    def __init__(self):
                        super().__init__(name="{agent_name}")

                root_agent = {agent_name.title()}Agent()
            """))

      loader = AgentLoader(str(temp_path))
      # SyntaxError is a subclass of Exception, and importlib might wrap it
      # The loader is expected to prepend its message and re-raise.
      with pytest.raises(
          SyntaxError
      ) as exc_info:  # Or potentially ImportError depending on Python version specifics with importlib
        loader.load_agent(agent_name)

      assert str(exc_info.value).startswith(
          f"Fail to load '{agent_name}' module."
      )
      # Check for part of the original SyntaxError message
      assert "invalid syntax" in str(exc_info.value).lower()

  def test_agent_internal_name_error(self):
    """Test other import errors within an agent's code (e.g., SyntaxError)."""
    with tempfile.TemporaryDirectory() as temp_dir:
      temp_path = Path(temp_dir)
      agent_name = "name_error_agent"

      # Create agent with a syntax error (which leads to ImportError)
      agent_file = temp_path / f"{agent_name}.py"
      agent_file.write_text(dedent(f"""
                from google.adk.agents.base_agent import BaseAgent

                # name is not defined
                print(non_existing_name)

                class {agent_name.title()}Agent(BaseAgent):
                    def __init__(self):
                        super().__init__(name="{agent_name}")

                root_agent = {agent_name.title()}Agent()
            """))

      loader = AgentLoader(str(temp_path))
      # SyntaxError is a subclass of Exception, and importlib might wrap it
      # The loader is expected to prepend its message and re-raise.
      with pytest.raises(
          NameError
      ) as exc_info:  # Or potentially ImportError depending on Python version specifics with importlib
        loader.load_agent(agent_name)

      assert str(exc_info.value).startswith(
          f"Fail to load '{agent_name}' module."
      )
      # Check for part of the original SyntaxError message
      assert "is not defined" in str(exc_info.value).lower()

  def test_sys_path_modification(self):
    """Test that agents_dir is added to sys.path correctly."""
    with tempfile.TemporaryDirectory() as temp_dir:
      temp_path = Path(temp_dir)

      # Create agent
      self.create_agent_structure(temp_path, "path_agent", "module")

      # Check sys.path before
      assert str(temp_path) not in sys.path

      loader = AgentLoader(str(temp_path))

      # Path should not be added yet - only added during load
      assert str(temp_path) not in sys.path

      # Load agent - this should add the path
      agent = loader.load_agent("path_agent")

      # Now assert path was added
      assert str(temp_path) in sys.path
      assert agent.name == "path_agent"

  def create_yaml_agent_structure(
      self, temp_dir: Path, agent_name: str, yaml_content: str
  ):
    """Create an agent structure with YAML configuration.

    Args:
        temp_dir: The temporary directory to create the agent in
        agent_name: Name of the agent
        yaml_content: YAML content for the root_agent.yaml file
    """
    agent_dir = temp_dir / agent_name
    agent_dir.mkdir()

    # Create root_agent.yaml file
    yaml_file = agent_dir / "root_agent.yaml"
    yaml_file.write_text(yaml_content)

  def test_load_agent_from_yaml_config(self):
    """Test loading an agent from YAML configuration."""
    with tempfile.TemporaryDirectory() as temp_dir:
      temp_path = Path(temp_dir)
      agent_name = "yaml_agent"

      # Create YAML configuration
      yaml_content = dedent("""
        agent_class: LlmAgent
        name: yaml_test_agent
        model: gemini-2.0-flash
        instruction: You are a test agent loaded from YAML configuration.
        description: A test agent created from YAML config
      """)

      self.create_yaml_agent_structure(temp_path, agent_name, yaml_content)

      # Load the agent
      loader = AgentLoader(str(temp_path))
      agent = loader.load_agent(agent_name)

      # Assert agent was loaded correctly
      assert agent.name == "yaml_test_agent"
      # Check if it's an LlmAgent before accessing model and instruction
      from google.adk.agents.llm_agent import LlmAgent

      if isinstance(agent, LlmAgent):
        assert agent.model == "gemini-2.0-flash"
        # Handle instruction which can be string or InstructionProvider
        instruction_text = str(agent.instruction)
        assert "test agent loaded from YAML" in instruction_text

  def test_yaml_agent_caching_returns_same_instance(self):
    """Test that loading the same YAML agent twice returns the same instance."""
    with tempfile.TemporaryDirectory() as temp_dir:
      temp_path = Path(temp_dir)
      agent_name = "cached_yaml_agent"

      # Create YAML configuration
      yaml_content = dedent("""
        agent_class: LlmAgent
        name: cached_yaml_test_agent
        model: gemini-2.0-flash
        instruction: You are a cached test agent.
      """)

      self.create_yaml_agent_structure(temp_path, agent_name, yaml_content)

      # Load the agent twice
      loader = AgentLoader(str(temp_path))
      agent1 = loader.load_agent(agent_name)
      agent2 = loader.load_agent(agent_name)

      # Assert same instance is returned
      assert agent1 is agent2
      assert agent1.name == agent2.name

  def test_yaml_agent_not_found_error(self):
    """Test that appropriate error is raised when YAML agent is not found."""
    with tempfile.TemporaryDirectory() as temp_dir:
      loader = AgentLoader(temp_dir)
      agents_dir = temp_dir  # For use in the expected message string

      # Try to load nonexistent YAML agent
      with pytest.raises(ValueError) as exc_info:
        loader.load_agent("nonexistent_yaml_agent")

      expected_msg_part_1 = "No root_agent found for 'nonexistent_yaml_agent'."
      expected_msg_part_2 = (
          "Searched in 'nonexistent_yaml_agent.agent.root_agent',"
          " 'nonexistent_yaml_agent.root_agent' and"
          " 'nonexistent_yaml_agent/root_agent.yaml'."
      )
      expected_msg_part_3 = (
          f"Ensure '{agents_dir}/nonexistent_yaml_agent' is structured"
          " correctly"
      )

      assert expected_msg_part_1 in str(exc_info.value)
      assert expected_msg_part_2 in str(exc_info.value)
      assert expected_msg_part_3 in str(exc_info.value)

  def test_yaml_agent_invalid_yaml_error(self):
    """Test that appropriate error is raised when YAML is invalid."""
    with tempfile.TemporaryDirectory() as temp_dir:
      temp_path = Path(temp_dir)
      agent_name = "invalid_yaml_agent"

      # Create invalid YAML content with wrong field name
      invalid_yaml_content = dedent("""
        not_exist_field: invalid_yaml_test_agent
        model: gemini-2.0-flash
        instruction: You are a test agent with invalid YAML
      """)

      self.create_yaml_agent_structure(
          temp_path, agent_name, invalid_yaml_content
      )

      loader = AgentLoader(str(temp_path))

      # Try to load agent with invalid YAML
      with pytest.raises(ValidationError) as exc_info:
        loader.load_agent(agent_name)

      # Should raise some form of YAML parsing error
      assert "Extra inputs are not permitted" in str(exc_info.value)

  def create_special_agent_structure(
      self, special_agents_dir: Path, agent_name: str, structure_type: str
  ):
    """Create special agent structures for testing.

    Args:
        special_agents_dir: The special agents directory to create the agent in
        agent_name: Name of the agent (without double underscore prefix)
        structure_type: One of 'module', 'package_with_agent_module'
    """
    if structure_type == "module":
      # Structure: special_agents_dir/agent_name.py
      agent_file = special_agents_dir / f"{agent_name}.py"
      agent_file.write_text(dedent(f"""
                import os
                from google.adk.agents.base_agent import BaseAgent
                from typing import Any

                class Special{agent_name.title()}Agent(BaseAgent):
                    agent_id: Any = None
                    config: Any = None

                    def __init__(self):
                        super().__init__(name="special_{agent_name}")
                        self.agent_id = id(self)
                        self.config = os.environ.get("AGENT_CONFIG", "special_default")

                root_agent = Special{agent_name.title()}Agent()
            """))

    elif structure_type == "package_with_agent_module":
      # Structure: special_agents_dir/agent_name/agent.py
      agent_dir = special_agents_dir / agent_name
      agent_dir.mkdir()

      # Create __init__.py
      init_file = agent_dir / "__init__.py"
      init_file.write_text("")

      # Create agent.py with root_agent
      agent_file = agent_dir / "agent.py"
      agent_file.write_text(dedent(f"""
                import os
                from google.adk.agents.base_agent import BaseAgent
                from typing import Any

                class Special{agent_name.title()}Agent(BaseAgent):
                    agent_id: Any = None
                    config: Any = None

                    def __init__(self):
                        super().__init__(name="special_{agent_name}")
                        self.agent_id = id(self)
                        self.config = os.environ.get("AGENT_CONFIG", "special_default")

                root_agent = Special{agent_name.title()}Agent()
            """))

  def test_load_special_agent_with_double_underscore(self):
    """Test loading a special agent with double underscore prefix."""
    with tempfile.TemporaryDirectory() as temp_dir:
      temp_path = Path(temp_dir)

      # Create special agents directory structure
      special_agents_dir = temp_path / "src" / "google" / "adk" / "assistants"
      special_agents_dir.mkdir(parents=True)

      # Create a special agent
      self.create_special_agent_structure(
          special_agents_dir, "helper", "package_with_agent_module"
      )

      # Mock the SPECIAL_AGENTS_DIR to point to our test directory
      from google.adk.cli.utils import agent_loader

      original_special_dir = agent_loader.SPECIAL_AGENTS_DIR

      try:
        agent_loader.SPECIAL_AGENTS_DIR = str(special_agents_dir)

        # Create a regular agents directory (can be empty for this test)
        regular_agents_dir = temp_path / "regular_agents"
        regular_agents_dir.mkdir()

        # Load the special agent
        loader = AgentLoader(str(regular_agents_dir))
        agent = loader.load_agent("__helper")

        # Assert agent was loaded correctly
        assert agent.name == "special_helper"
        assert hasattr(agent, "agent_id")
        assert agent.config == "special_default"

      finally:
        # Restore original SPECIAL_AGENTS_DIR
        agent_loader.SPECIAL_AGENTS_DIR = original_special_dir

  def test_special_agent_caching_returns_same_instance(self):
    """Test that loading the same special agent twice returns the same instance."""
    with tempfile.TemporaryDirectory() as temp_dir:
      temp_path = Path(temp_dir)

      # Create special agents directory structure
      special_agents_dir = temp_path / "src" / "google" / "adk" / "assistants"
      special_agents_dir.mkdir(parents=True)

      # Create a special agent
      self.create_special_agent_structure(
          special_agents_dir, "cached_helper", "module"
      )

      # Mock the SPECIAL_AGENTS_DIR to point to our test directory
      from google.adk.cli.utils import agent_loader

      original_special_dir = agent_loader.SPECIAL_AGENTS_DIR

      try:
        agent_loader.SPECIAL_AGENTS_DIR = str(special_agents_dir)

        # Create a regular agents directory
        regular_agents_dir = temp_path / "regular_agents"
        regular_agents_dir.mkdir()

        # Load the special agent twice
        loader = AgentLoader(str(regular_agents_dir))
        agent1 = loader.load_agent("__cached_helper")
        agent2 = loader.load_agent("__cached_helper")

        # Assert same instance is returned
        assert agent1 is agent2
        assert agent1.agent_id == agent2.agent_id
        assert agent1.name == "special_cached_helper"

      finally:
        # Restore original SPECIAL_AGENTS_DIR
        agent_loader.SPECIAL_AGENTS_DIR = original_special_dir

  def test_special_agent_not_found_error(self):
    """Test that appropriate error is raised when special agent is not found."""
    with tempfile.TemporaryDirectory() as temp_dir:
      temp_path = Path(temp_dir)

      # Create special agents directory (but empty)
      special_agents_dir = temp_path / "special_agents"
      special_agents_dir.mkdir()

      # Create regular agents directory
      regular_agents_dir = temp_path / "regular_agents"
      regular_agents_dir.mkdir()

      # Mock the SPECIAL_AGENTS_DIR to point to our test directory
      from google.adk.cli.utils import agent_loader

      original_special_dir = agent_loader.SPECIAL_AGENTS_DIR

      try:
        agent_loader.SPECIAL_AGENTS_DIR = str(special_agents_dir)

        loader = AgentLoader(str(regular_agents_dir))

        # Try to load nonexistent special agent
        with pytest.raises(ValueError) as exc_info:
          loader.load_agent("__nonexistent_special")

        expected_msg_part_1 = "No root_agent found for '__nonexistent_special'."
        expected_msg_part_2 = (
            "Searched in 'nonexistent_special.agent.root_agent',"
            " 'nonexistent_special.root_agent' and"
            " 'nonexistent_special/root_agent.yaml'."
        )
        expected_msg_part_3 = (
            f"Ensure '{special_agents_dir}/nonexistent_special' is structured"
            " correctly"
        )

        assert expected_msg_part_1 in str(exc_info.value)
        assert expected_msg_part_2 in str(exc_info.value)
        assert expected_msg_part_3 in str(exc_info.value)

      finally:
        # Restore original SPECIAL_AGENTS_DIR
        agent_loader.SPECIAL_AGENTS_DIR = original_special_dir

  def create_special_yaml_agent_structure(
      self, special_agents_dir: Path, agent_name: str, yaml_content: str
  ):
    """Create a special agent structure with YAML configuration.

    Args:
        special_agents_dir: The special agents directory to create the agent in
        agent_name: Name of the agent (without double underscore prefix)
        yaml_content: YAML content for the root_agent.yaml file
    """
    agent_dir = special_agents_dir / agent_name
    agent_dir.mkdir()

    # Create root_agent.yaml file
    yaml_file = agent_dir / "root_agent.yaml"
    yaml_file.write_text(yaml_content)

  def test_load_special_agent_from_yaml_config(self):
    """Test loading a special agent from YAML configuration."""
    with tempfile.TemporaryDirectory() as temp_dir:
      temp_path = Path(temp_dir)

      # Create special agents directory
      special_agents_dir = temp_path / "special_agents"
      special_agents_dir.mkdir()
      agent_name = "yaml_helper"

      # Create YAML configuration for special agent
      yaml_content = dedent("""
        agent_class: LlmAgent
        name: special_yaml_test_agent
        model: gemini-2.0-flash
        instruction: You are a special test agent loaded from YAML configuration.
        description: A special test agent created from YAML config
      """)

      self.create_special_yaml_agent_structure(
          special_agents_dir, agent_name, yaml_content
      )

      # Mock the SPECIAL_AGENTS_DIR to point to our test directory
      from google.adk.cli.utils import agent_loader

      original_special_dir = agent_loader.SPECIAL_AGENTS_DIR

      try:
        agent_loader.SPECIAL_AGENTS_DIR = str(special_agents_dir)

        # Create regular agents directory
        regular_agents_dir = temp_path / "regular_agents"
        regular_agents_dir.mkdir()

        # Load the special agent
        loader = AgentLoader(str(regular_agents_dir))
        agent = loader.load_agent("__yaml_helper")

        # Assert agent was loaded correctly
        assert agent.name == "special_yaml_test_agent"
        # Check if it's an LlmAgent before accessing model and instruction
        from google.adk.agents.llm_agent import LlmAgent

        if isinstance(agent, LlmAgent):
          assert agent.model == "gemini-2.0-flash"
          # Handle instruction which can be string or InstructionProvider
          instruction_text = str(agent.instruction)
          assert "special test agent loaded from YAML" in instruction_text

      finally:
        # Restore original SPECIAL_AGENTS_DIR
        agent_loader.SPECIAL_AGENTS_DIR = original_special_dir

  def test_yaml_config_agents_dir_parameter(self):
    """Test that _load_from_yaml_config respects the agents_dir parameter."""
    with tempfile.TemporaryDirectory() as temp_dir:
      temp_path = Path(temp_dir)

      # Create two different directories with the same agent name
      regular_agents_dir = temp_path / "regular_agents"
      regular_agents_dir.mkdir()
      custom_agents_dir = temp_path / "custom_agents"
      custom_agents_dir.mkdir()

      agent_name = "param_test_agent"

      # Create YAML agent in regular directory
      regular_yaml_content = dedent("""
        agent_class: LlmAgent
        name: regular_yaml_agent
        model: gemini-2.0-flash
        instruction: Regular agent from default directory.
      """)
      self.create_yaml_agent_structure(
          regular_agents_dir, agent_name, regular_yaml_content
      )

      # Create YAML agent in custom directory
      custom_yaml_content = dedent("""
        agent_class: LlmAgent
        name: custom_yaml_agent
        model: gemini-2.0-flash
        instruction: Custom agent from custom directory.
      """)
      self.create_yaml_agent_structure(
          custom_agents_dir, agent_name, custom_yaml_content
      )

      # Create loader pointing to regular directory
      loader = AgentLoader(str(regular_agents_dir))

      # Test 1: Call with regular agents_dir (should use self.agents_dir)
      default_agent = loader._load_from_yaml_config(
          agent_name, str(regular_agents_dir)
      )
      assert default_agent is not None
      assert default_agent.name == "regular_yaml_agent"

      # Test 2: Call with explicit custom agents_dir (should use custom directory)
      custom_agent = loader._load_from_yaml_config(
          agent_name, str(custom_agents_dir)
      )
      assert custom_agent is not None
      assert custom_agent.name == "custom_yaml_agent"

      # Test 3: Call with self.agents_dir explicitly (should be same as test 1)
      explicit_agent = loader._load_from_yaml_config(
          agent_name, loader.agents_dir
      )
      assert explicit_agent is not None
      assert explicit_agent.name == "regular_yaml_agent"

      # Verify they are different agents
      assert default_agent.name != custom_agent.name
      assert explicit_agent.name == default_agent.name
