import logging
from typing import Optional

from pydantic import Field

from crewai.agents.agent_builder.base_agent import BaseAgent
from crewai.task import Task
from crewai.tools.base_tool import BaseTool
from crewai.utilities import I18N

logger = logging.getLogger(__name__)


class BaseAgentTool(BaseTool):
    """Base class for agent-related tools"""

    agents: list[BaseAgent] = Field(description="List of available agents")
    i18n: I18N = Field(
        default_factory=I18N, description="Internationalization settings"
    )

    def sanitize_agent_name(self, name: str) -> str:
        """
        Sanitize agent role name by normalizing whitespace and setting to lowercase.
        Converts all whitespace (including newlines) to single spaces and removes quotes.

        Args:
            name (str): The agent role name to sanitize

        Returns:
            str: The sanitized agent role name, with whitespace normalized,
                 converted to lowercase, and quotes removed
        """
        if not name:
            return ""
        # Normalize all whitespace (including newlines) to single spaces
        normalized = " ".join(name.split())
        # Remove quotes and convert to lowercase
        return normalized.replace('"', "").casefold()

    def _get_coworker(self, coworker: Optional[str], **kwargs) -> Optional[str]:
        coworker = coworker or kwargs.get("co_worker") or kwargs.get("coworker")
        if coworker:
            is_list = coworker.startswith("[") and coworker.endswith("]")
            if is_list:
                coworker = coworker[1:-1].split(",")[0]
        return coworker

    def _execute(
        self,
        agent_name: Optional[str],
        task: str,
        context: Optional[str] = None
    ) -> str:
        """
        Execute delegation to an agent with case-insensitive and whitespace-tolerant matching.

        Args:
            agent_name: Name/role of the agent to delegate to (case-insensitive)
            task: The specific question or task to delegate
            context: Optional additional context for the task execution

        Returns:
            str: The execution result from the delegated agent or an error message
                 if the agent cannot be found
        """
        try:
            if agent_name is None:
                agent_name = ""
                logger.debug("No agent name provided, using empty string")

            # It is important to remove the quotes from the agent name.
            # The reason we have to do this is because less-powerful LLM's
            # have difficulty producing valid JSON.
            # As a result, we end up with invalid JSON that is truncated like this:
            # {"task": "....", "coworker": "....
            # when it should look like this:
            # {"task": "....", "coworker": "...."}
            sanitized_name = self.sanitize_agent_name(agent_name)
            logger.debug(f"Sanitized agent name from '{agent_name}' to '{sanitized_name}'")

            available_agents = [agent.role for agent in self.agents]
            logger.debug(f"Available agents: {available_agents}")

            agent = [  # type: ignore # Incompatible types in assignment (expression has type "list[BaseAgent]", variable has type "str | None")
                available_agent
                for available_agent in self.agents
                if self.sanitize_agent_name(available_agent.role) == sanitized_name
            ]
            logger.debug(f"Found {len(agent)} matching agents for role '{sanitized_name}'")
        except (AttributeError, ValueError) as e:
            # Handle specific exceptions that might occur during role name processing
            return self.i18n.errors("agent_tool_unexisting_coworker").format(
                coworkers="\n".join(
                    [f"- {self.sanitize_agent_name(agent.role)}" for agent in self.agents]
                ),
                error=str(e)
            )

        if not agent:
            # No matching agent found after sanitization
            return self.i18n.errors("agent_tool_unexisting_coworker").format(
                coworkers="\n".join(
                    [f"- {self.sanitize_agent_name(agent.role)}" for agent in self.agents]
                ),
                error=f"No agent found with role '{sanitized_name}'"
            )

        agent = agent[0]
        try:
            task_with_assigned_agent = Task(
                description=task,
                agent=agent,
                expected_output=agent.i18n.slice("manager_request"),
                i18n=agent.i18n,
            )
            logger.debug(f"Created task for agent '{self.sanitize_agent_name(agent.role)}': {task}")
            return agent.execute_task(task_with_assigned_agent, context)
        except Exception as e:
            # Handle task creation or execution errors
            return self.i18n.errors("agent_tool_execution_error").format(
                agent_role=self.sanitize_agent_name(agent.role),
                error=str(e)
            )
