from typing import Dict, List, Optional

from .tool import Tool


class ToolBox:
    def __init__(self, tools: Optional[List[Tool]] = None):
        """
        Initialize the ToolBox as a container for tools available to the Architect.

        Args:
            tools (Optional[List[Tool]]): A list of Tool objects to add to the ToolBox
            
        Returns:
            None: Initializes the toolbox instance
        """
        self.tools: Dict[str, Tool] = {}
        if tools:
            self._validate_and_add_tools(tools)

    def add_tool(self, tool: Tool) -> None:
        """
        Adds a Tool object to the ToolBox.

        Args:
            tool (Tool): The Tool object to add to the ToolBox
            
        Returns:
            None: Adds the tool to the toolbox
            
        Raises:
            ValueError: If tool is missing name attribute or name already exists
        """
        if not hasattr(tool, "name"):
            raise ValueError("Tool must have a name attribute")
        if tool.name in self.tools:
            raise ValueError(f"Tool with name '{tool.name}' already exists")
        self.tools[tool.name] = tool

    def set_tool_list(self, tools: List[Tool]) -> None:
        """
        Sets the list of Tool objects to the ToolBox, removing any existing tools.

        Args:
            tools (List[Tool]): A list of Tool objects to set in the ToolBox
            
        Returns:
            None: Replaces all existing tools with the provided list
        """
        self.tools = {}
        self._validate_and_add_tools(tools)

    def find_by_name(self, name: str) -> Optional[Tool]:
        """
        Finds a Tool object by its name.

        Args:
            name (str): The name of the Tool object to find

        Returns:
            Optional[Tool]: The Tool object if found, otherwise None
        """
        return self.tools.get(name)

    def remove_tool(self, name: str) -> None:
        """
        Removes a Tool object from the ToolBox.

        Args:
            name (str): The name of the Tool object to remove
            
        Returns:
            None: Removes the tool from the toolbox if it exists
        """
        if name in self.tools:
            del self.tools[name]

    def open_tool(self, name: str) -> Optional[str]:
        """
        Creates a string representation of a Tool object formatted for consumption by a Large Language Model.

        Args:
            name (str): The name of the Tool object to open

        Returns:
            Optional[str]: A string representation of the Tool object, or None if not found
        """
        tool: Optional[Tool] = self.tools.get(name)
        if not tool:
            return None
        tool_details: str = f"TOOL NAME: {tool.name}\n"
        tool_details += f"Purpose: {tool.purpose}\n"
        tool_details += "Required Parameters:\n"
        for param_name, (data_type, description) in tool.required_parameters.items():
            tool_details += f"  {param_name} ({data_type}): {description}\n"
        tool_details += "Optional Parameters:\n"
        for param_name, (data_type, description) in tool.optional_parameters.items():
            tool_details += f"  {param_name} ({data_type}): {description}\n"
        tool_details += "\nOutput Descriptions:\n"
        for output_name, (data_type, description) in tool.output_descriptions.items():
            tool_details += f"  {output_name} ({data_type}): {description}\n"
        return tool_details

    def open_all_tools(self) -> str:
        """
        Opens each tool and appends the details to a single string.

        Args:
            None
            
        Returns:
            str: A string representation of all the Tool objects in the ToolBox
        """
        all_tool_details: str = "TOOLS:\n--------------------------------\n"
        for name in self.tools.keys():
            tool_details: Optional[str] = self.open_tool(name)
            if tool_details:
                all_tool_details += tool_details + "\n"
        return all_tool_details

    def _validate_and_add_tools(self, tools: List[Tool]) -> None:
        """
        Validates the tools and adds them to the ToolBox.

        Args:
            tools (List[Tool]): A list of Tool objects to add to the ToolBox
            
        Returns:
            None: Validates and adds tools to the toolbox
            
        Raises:
            ValueError: If tool validation fails or duplicate names exist
        """
        for tool in tools:
            if not hasattr(tool, "name"):
                raise ValueError("Tool must have a name attribute")
            if tool.name in self.tools:
                raise ValueError(
                    f"Tool with name '{tool.name}' already exists"
                )
            self.tools[tool.name] = tool
