"""
Tool Manager - Central management for all Agent tools

This module provides the ToolManager class which manages all tools available
to the Agent, handles tool registration, validation, and execution.
"""

import logging
import time
from typing import Dict, Any, List, Optional

from openfund_agent.tools.base import (
    BaseTool,
    ToolInfo,
    ToolExecutionResult,
    ParameterValidation,
    ToolNotFoundError,
)
from openfund_agent.tools.data_tool import DataTool
from openfund_agent.tools.strategy_tool import StrategyExecutionToolWrapper
from openfund_agent.tools.feedback_tool import PerformanceFeedbackTool
from openfund_agent.tools.code_tool import CodeModificationTool
from openfund_agent.tools.decision_tool import DecisionMakingTool


logger = logging.getLogger(__name__)


class ToolManager:
    """
    Tool Manager - Central hub for managing all Agent tools

    This class provides a unified interface for:
    - Registering and discovering tools
    - Validating tool calls
    - Executing tools with proper error handling
    - Listing available tools and their capabilities
    """

    def __init__(self, config: Dict[str, Any]):
        """
        Initialize the Tool Manager

        Args:
            config: Configuration dictionary containing:
                - exchange_config: Exchange API configuration
                - cache_ttl: Cache time-to-live in minutes
                - maker_config: Openfund-maker configuration
                - taker_config: Openfund-taker configuration
                - exchange_client: Exchange client instance
                - claude_client: Claude API client
                - workspace_path: Path to workspace root
                - git_manager: Git manager instance
                - model_version: Claude model version (optional)
        """
        self.config = config
        self.tools: Dict[str, BaseTool] = {}
        self.execution_history: List[Dict[str, Any]] = []

        logger.info("Initializing ToolManager")
        self._register_tools()
        logger.info(f"ToolManager initialized with {len(self.tools)} tools")

    def _register_tools(self) -> None:
        """
        Register all available tools

        This method instantiates and registers all tool implementations.
        Tools are registered with their canonical names for easy access.
        """
        try:
            # Register Data Collection Tool
            if "exchange_config" in self.config:
                self.tools["data_collection"] = DataTool(
                    exchange_config=self.config["exchange_config"],
                    cache_ttl=self.config.get("cache_ttl", 60),
                )
                logger.info("Registered DataTool")

            # Register Strategy Execution Tool
            if all(k in self.config for k in ["maker_config", "taker_config", "exchange_client"]):
                self.tools["strategy_execution"] = StrategyExecutionToolWrapper(
                    maker_config=self.config["maker_config"],
                    taker_config=self.config["taker_config"],
                    exchange_client=self.config["exchange_client"],
                )
                logger.info("Registered StrategyExecutionToolWrapper")

            # Register Performance Feedback Tool
            from openfund_agent.feedback.analyzer import PerformanceAnalyzer

            analyzer = PerformanceAnalyzer(
                notification_service=self.config.get("notification_service")
            )
            self.tools["performance_feedback"] = PerformanceFeedbackTool(analyzer=analyzer)
            logger.info("Registered PerformanceFeedbackTool")

            # Register Code Modification Tool
            if all(k in self.config for k in ["claude_client", "workspace_path", "git_manager"]):
                self.tools["code_modification"] = CodeModificationTool(
                    claude_client=self.config["claude_client"],
                    workspace_path=self.config["workspace_path"],
                    git_manager=self.config["git_manager"],
                    model_version=self.config.get("model_version", "claude-3-5-sonnet-20241022"),
                )
                logger.info("Registered CodeModificationTool")

            # Register Decision Making Tool
            if "decision_engine" in self.config:
                self.tools["decision_making"] = DecisionMakingTool(
                    engine=self.config["decision_engine"]
                )
                logger.info("Registered DecisionMakingTool")

        except Exception as e:
            logger.error(f"Error during tool registration: {str(e)}")
            raise

    def get_tool(self, tool_name: str) -> BaseTool:
        """
        Get a tool by name

        Args:
            tool_name: Name of the tool to retrieve

        Returns:
            BaseTool instance

        Raises:
            ToolNotFoundError: If the tool is not registered
        """
        if tool_name not in self.tools:
            available_tools = ", ".join(self.tools.keys())
            raise ToolNotFoundError(
                f"Tool '{tool_name}' not found. Available tools: {available_tools}"
            )

        return self.tools[tool_name]

    def list_tools(self) -> List[ToolInfo]:
        """
        List all registered tools with their metadata

        Returns:
            List of ToolInfo objects containing tool name, description, and parameters
        """
        tool_infos = []

        for name, tool in self.tools.items():
            tool_info = tool.get_info()
            tool_infos.append(tool_info)

        logger.debug(f"Listed {len(tool_infos)} tools")
        return tool_infos

    def validate_tool_call(self, tool_name: str, parameters: Dict[str, Any]) -> ParameterValidation:
        """
        Validate parameters for a tool call

        This method checks:
        - Tool exists
        - Required parameters are present
        - Parameter types are correct
        - Enum values are valid

        Args:
            tool_name: Name of the tool
            parameters: Parameters to validate

        Returns:
            ParameterValidation object with validation results

        Raises:
            ToolNotFoundError: If the tool is not registered
        """
        logger.debug(f"Validating tool call: {tool_name} with parameters: {parameters}")

        # Get the tool (raises ToolNotFoundError if not found)
        tool = self.get_tool(tool_name)

        # Validate parameters using the tool's validation logic
        validation_result = tool.validate_parameters(parameters)

        if not validation_result.valid:
            logger.warning(
                f"Tool call validation failed for {tool_name}: {validation_result.errors}"
            )
        else:
            logger.debug(f"Tool call validation passed for {tool_name}")

        return validation_result

    def execute_tool(self, tool_name: str, parameters: Dict[str, Any]) -> ToolExecutionResult:
        """
        Execute a tool with given parameters

        This method:
        1. Validates the tool call
        2. Executes the tool
        3. Handles errors gracefully
        4. Records execution history
        5. Returns structured results

        Args:
            tool_name: Name of the tool to execute
            parameters: Tool parameters

        Returns:
            ToolExecutionResult with execution status and data
        """
        start_time = time.time()

        logger.info(f"Executing tool: {tool_name}")
        logger.debug(f"Tool parameters: {parameters}")

        try:
            # Validate parameters
            validation = self.validate_tool_call(tool_name, parameters)

            if not validation.valid:
                execution_time = time.time() - start_time
                result = ToolExecutionResult(
                    success=False,
                    error=f"Parameter validation failed: {', '.join(validation.errors)}",
                    data=None,
                    execution_time=execution_time,
                    metadata={"tool_name": tool_name, "validation_errors": validation.errors},
                )

                self._record_execution(tool_name, parameters, result)
                return result

            # Get the tool
            tool = self.get_tool(tool_name)

            # Execute the tool
            try:
                data = tool.execute(parameters)
                execution_time = time.time() - start_time

                result = ToolExecutionResult(
                    success=True,
                    error=None,
                    data=data,
                    execution_time=execution_time,
                    metadata={"tool_name": tool_name},
                )

                logger.info(f"Tool {tool_name} executed successfully in {execution_time:.2f}s")

            except Exception as e:
                execution_time = time.time() - start_time
                error_msg = f"Tool execution failed: {str(e)}"
                logger.error(f"{error_msg} (tool: {tool_name})", exc_info=True)

                result = ToolExecutionResult(
                    success=False,
                    error=error_msg,
                    data=None,
                    execution_time=execution_time,
                    metadata={"tool_name": tool_name, "exception_type": type(e).__name__},
                )

        except ToolNotFoundError as e:
            execution_time = time.time() - start_time
            logger.error(str(e))

            result = ToolExecutionResult(
                success=False,
                error=str(e),
                data=None,
                execution_time=execution_time,
                metadata={"tool_name": tool_name},
            )

        except Exception as e:
            execution_time = time.time() - start_time
            error_msg = f"Unexpected error during tool execution: {str(e)}"
            logger.error(error_msg, exc_info=True)

            result = ToolExecutionResult(
                success=False,
                error=error_msg,
                data=None,
                execution_time=execution_time,
                metadata={"tool_name": tool_name, "exception_type": type(e).__name__},
            )

        # Record execution
        self._record_execution(tool_name, parameters, result)

        return result

    def _record_execution(
        self, tool_name: str, parameters: Dict[str, Any], result: ToolExecutionResult
    ) -> None:
        """
        Record tool execution in history

        Args:
            tool_name: Name of the executed tool
            parameters: Parameters used
            result: Execution result
        """
        execution_record = {
            "tool_name": tool_name,
            "parameters": parameters,
            "success": result.success,
            "error": result.error,
            "execution_time": result.execution_time,
            "timestamp": time.time(),
        }

        self.execution_history.append(execution_record)

        # Keep only last 1000 executions to prevent memory issues
        if len(self.execution_history) > 1000:
            self.execution_history = self.execution_history[-1000:]

    def get_execution_history(
        self, limit: Optional[int] = None, tool_name: Optional[str] = None
    ) -> List[Dict[str, Any]]:
        """
        Get tool execution history

        Args:
            limit: Maximum number of records to return (None for all)
            tool_name: Filter by tool name (None for all tools)

        Returns:
            List of execution records
        """
        history = self.execution_history

        # Filter by tool name if specified
        if tool_name:
            history = [record for record in history if record["tool_name"] == tool_name]

        # Apply limit if specified
        if limit:
            history = history[-limit:]

        return history

    def get_tool_statistics(self) -> Dict[str, Any]:
        """
        Get statistics about tool usage

        Returns:
            Dictionary with statistics:
            - total_executions: Total number of tool executions
            - successful_executions: Number of successful executions
            - failed_executions: Number of failed executions
            - tool_usage: Execution count per tool
            - average_execution_time: Average execution time per tool
        """
        total = len(self.execution_history)
        successful = sum(1 for record in self.execution_history if record["success"])
        failed = total - successful

        # Calculate per-tool statistics
        tool_usage: Dict[str, int] = {}
        tool_execution_times: Dict[str, List[float]] = {}

        for record in self.execution_history:
            tool_name = record["tool_name"]

            # Count usage
            tool_usage[tool_name] = tool_usage.get(tool_name, 0) + 1

            # Collect execution times
            if tool_name not in tool_execution_times:
                tool_execution_times[tool_name] = []
            if record["execution_time"]:
                tool_execution_times[tool_name].append(record["execution_time"])

        # Calculate average execution times
        avg_execution_times = {
            tool: sum(times) / len(times) if times else 0.0
            for tool, times in tool_execution_times.items()
        }

        return {
            "total_executions": total,
            "successful_executions": successful,
            "failed_executions": failed,
            "success_rate": successful / total if total > 0 else 0.0,
            "tool_usage": tool_usage,
            "average_execution_time": avg_execution_times,
        }

    def clear_execution_history(self) -> None:
        """
        Clear all execution history

        This is useful for testing or resetting the manager state.
        """
        self.execution_history.clear()
        logger.info("Execution history cleared")

    def has_tool(self, tool_name: str) -> bool:
        """
        Check if a tool is registered

        Args:
            tool_name: Name of the tool to check

        Returns:
            True if tool is registered, False otherwise
        """
        return tool_name in self.tools

    def get_registered_tool_names(self) -> List[str]:
        """
        Get list of all registered tool names

        Returns:
            List of tool names
        """
        return list(self.tools.keys())
