import logging
import os
from typing import List, Dict, Any

from mcp.types import Tool

from .create_tools.openapi_extractor import OpenAPIExtractor
from .create_tools.utils import BuboBotAPIManager
from bubobot_mcp.src.config.config import Config


class DynamicToolHandler:
    """Handler for managing dynamically generated tools from OpenAPI schema"""
    
    def __init__(self, config: Config):
        """
        Initialize the dynamic tool handler with configuration.
        
        Args:
            config: Configuration object for the tools
        """
        self.config = config
        self.logger = logging.getLogger(self.__class__.__name__)
        
        # Initialize OpenAPI extractor and API manager
        self.openapi_extractor = OpenAPIExtractor(config)
        self.bubobot_api_manager = BuboBotAPIManager(config)
        
        # Store dynamic tools and their endpoint mappings
        self.dynamic_tools: Dict[str, Tool] = {}
        self.dynamic_tool_endpoints: Dict[str, str] = {}
        self._tools_loaded = False

    
    def _extract_path_parameters(self, endpoint_path: str) -> List[str]:
        """Extract path parameters from endpoint path (e.g., {id}, {group_id})"""
        import re
        # Find all path parameters in format {param_name}
        path_params = re.findall(r'\{[^}]+\}', endpoint_path)
        return path_params
    
    def _validate_and_filter_arguments(self, tool_name: str, arguments: Dict[str, Any], input_schema: Dict[str, Any]) -> Dict[str, Any]:
        """
        Validate and filter arguments according to tool's input schema.
        
        Args:
            tool_name: Name of the tool being called
            arguments: Arguments provided by user
            input_schema: Input schema definition for the tool
            
        Returns:
            Dict[str, Any]: Validated and filtered arguments
            
        Raises:
            ValueError: If required arguments are missing
        """
        if not input_schema:
            return arguments.copy()
        
        properties = input_schema.get('properties', {})
        required_fields = input_schema.get('required', [])
        
        # Check for required fields
        missing_required = []
        for field in required_fields:
            if field not in arguments:
                missing_required.append(field)
        
        if missing_required:
            if len(missing_required) == 1:
                raise ValueError(f"Required argument '{missing_required[0]}' is missing for tool '{tool_name}'")
            else:
                missing_str = "', '".join(missing_required)
                raise ValueError(f"Required arguments '{missing_str}' are missing for tool '{tool_name}'")
        
        # Filter arguments to only include those defined in schema
        validated_args = {}
        for field_name, field_value in arguments.items():
            if field_name in properties:
                validated_args[field_name] = field_value
        
        return validated_args

    async def _load_dynamic_tools(self):
        """Load tools dynamically from OpenAPI schema"""
        if self._tools_loaded:
            return
            
        try:
            # Fetch OpenAPI schema and extract tools
            await self.openapi_extractor.fetch_openapi_schema()
            get_endpoints = self.openapi_extractor.extract_get_apis()
            
            # Deduplicate endpoints by path to avoid processing same endpoint multiple times
            seen_paths = set()
            unique_endpoints = []
            for endpoint in get_endpoints:
                if endpoint.path not in seen_paths:
                    seen_paths.add(endpoint.path)
                    unique_endpoints.append(endpoint)
            
            self.logger.info(f"Found {len(get_endpoints)} total endpoints, {len(unique_endpoints)} unique paths")
            
            # Generate tools for unique endpoints only
            for endpoint in unique_endpoints:
                tool = self.openapi_extractor.generate_mcp_tool(endpoint)
                
                # Check for tool name collision
                if tool.name in self.dynamic_tools:
                    print(f"Warning: Tool name collision: {tool.name} for path {endpoint.path}")
                    continue
                    
                self.dynamic_tools[tool.name] = tool
                self.dynamic_tool_endpoints[tool.name] = endpoint.path
                print(f"Generated tool: {tool.name} -> {endpoint.path}")
            
            self._tools_loaded = True
            print(f"Loaded {len(self.dynamic_tools)} dynamic tools from OpenAPI schema")
            
        except Exception as e:
            print(f"Warning: Failed to load dynamic tools from OpenAPI: {e}")
            import traceback
            traceback.print_exc()
    
    async def list_tools(self) -> List[Tool]:
        """List all dynamically generated tools from OpenAPI"""
        await self._load_dynamic_tools()
        tool_list = list(self.dynamic_tools.values())
        return tool_list


    async def call_tool(self, name: str, arguments: Dict[str, Any]) -> Any:
        """Call a specific dynamic tool by name"""
        await self._load_dynamic_tools()
        
        if name not in self.dynamic_tools:
            raise ValueError(f"Unknown tool: {name}")
        
        try:
            # Get tool and its input schema
            tool = self.dynamic_tools[name]
            input_schema = tool.inputSchema
            
            # Validate and filter arguments according to input schema
            validated_args = self._validate_and_filter_arguments(name, arguments, input_schema)
            
            # Get endpoint path for this tool
            endpoint_path = self.dynamic_tool_endpoints.get(name)
            if not endpoint_path:
                raise ValueError(f"MCP Server error: No endpoint mapping found for tool: {name}")
            
            # Extract bearer_token from validated arguments
            bearer_token = os.environ.get('BEARER_TOKEN')
            if not bearer_token:
                raise ValueError("Bearer token is required for API calls, please set BEARER_TOKEN in environment variables")
            
            # Handle path parameters (e.g., {id}, {group_id}, etc.)
            final_endpoint = endpoint_path
            path_params = self._extract_path_parameters(endpoint_path)
            
            for param in path_params:
                param_key = param.strip('{}')  # Remove {} brackets
                if param_key in validated_args:
                    param_value = validated_args.pop(param_key)
                    final_endpoint = final_endpoint.replace(param, str(param_value))
                else:
                    raise ValueError(f"Required path parameter '{param_key}' not provided for endpoint {endpoint_path}")
            
            # Make API request using BuboBotAPIManager
            result = await self.bubobot_api_manager.make_request(
                bearer_token=bearer_token,
                endpoint=final_endpoint,
                body=validated_args,  # Remaining arguments become query parameters for GET
                method="GET"  # All dynamic tools are GET endpoints
            )
            
            return result
            
        except Exception as e:
            return {
                "error": f"Failed to call dynamic tool {name}: {str(e)}",
                "tool_name": name,
                "endpoint": self.dynamic_tool_endpoints.get(name, "unknown"),
                "final_endpoint": locals().get('final_endpoint', 'unknown'),
                "success": False
            }
        # return await ToolHandler(self.config).call_tool(name, arguments)

    async def is_dynamic_tool(self, tool_name: str) -> bool:
        """Check if a tool name corresponds to a dynamic tool"""
        await self._load_dynamic_tools()
        return tool_name in self.dynamic_tools
    
    async def get_tool_info(self, tool_name: str) -> Dict[str, Any]:
        """Get detailed information about a dynamic tool"""
        await self._load_dynamic_tools()
        
        if tool_name not in self.dynamic_tools:
            return {"error": f"Tool {tool_name} not found"}
        
        tool = self.dynamic_tools[tool_name]
        endpoint = self.dynamic_tool_endpoints[tool_name]
        path_params = self._extract_path_parameters(endpoint)
        
        return {
            "name": tool.name,
            "description": tool.description,
            "endpoint": endpoint,
            "path_parameters": [param.strip('{}') for param in path_params],
            "has_path_params": len(path_params) > 0,
            "input_schema": tool.inputSchema,
            "method": "GET",
            "is_dynamic": True
        }
    
    def get_tools_summary(self) -> Dict[str, Any]:
        """Get summary of all loaded dynamic tools"""
        return {
            "total_dynamic_tools": len(self.dynamic_tools),
            "tools_loaded": self._tools_loaded,
            "tool_names": list(self.dynamic_tools.keys()) if self._tools_loaded else [],
            "endpoints": list(self.dynamic_tool_endpoints.values()) if self._tools_loaded else []
        }
    
    async def get_tools_by_tag(self, tag_filter: str = None) -> List[Tool]:
        """Get tools filtered by tag"""
        await self._load_dynamic_tools()
        
        if not tag_filter:
            return list(self.dynamic_tools.values())
        
        # Get endpoints and filter by tag
        get_endpoints = self.openapi_extractor.extract_get_apis()
        filtered_tools = []
        
        for endpoint in get_endpoints:
            if tag_filter.lower() in [tag.lower() for tag in endpoint.tags]:
                tool = self.openapi_extractor.generate_mcp_tool(endpoint)
                if tool.name in self.dynamic_tools:
                    filtered_tools.append(self.dynamic_tools[tool.name])
        
        return filtered_tools


if __name__ == "__main__":
    import asyncio
    
    async def main():
        config = Config(
            base_config_path="./src/config/base_config.yml",
            env_file_path="./src/dev.local.env"
        )
        
        # Create handler - tools will be loaded lazily
        dynamic_tool_handler = DynamicToolHandler(config)
        tool_handler = ToolHandler(config)

        print(await tool_handler.list_tools())
        print('/'*50)
        print(await dynamic_tool_handler.list_tools())
        
        # print("🔧 Dynamic Tools:")
        # tools = await dynamic_tool_handler.list_tools()
        # print(f"Total tools: {len(tools)}")
        
        # print('/' * 50)
        # print("📊 Tools Summary:")
        # summary = dynamic_tool_handler.get_tools_summary()
        # for key, value in summary.items():
        #     print(f"  {key}: {value}")
        
        # print('/' * 50)
        # print("🔍 Example Tool Info:")
        # if tools:
        #     example_tool = tools[0]
        #     tool_info = await dynamic_tool_handler.get_tool_info(example_tool.name)
        #     for key, value in tool_info.items():
        #         print(f"  {key}: {value}")
    
    asyncio.run(main())
