import asyncio
import json
from typing import Dict, List, Any, Optional
from dataclasses import dataclass

from mcp.types import Tool
from bubobot_mcp.src.mcp_server.handlers.tools.create_tools.utils import BuboBotAPIManager
from bubobot_mcp.src.config.config import Config


@dataclass
class APIEndpoint:
    """Data class to represent an API endpoint"""
    path: str
    method: str
    summary: Optional[str]
    description: Optional[str]
    parameters: List[Dict[str, Any]]
    tags: List[str]


class OpenAPIExtractor:
    """Extract and analyze OpenAPI schema data"""
    
    def __init__(self, config: Config):
        self.config = config
        self.bubobot_api_manager = BuboBotAPIManager(config)
        self.openapi_schema: Optional[Dict[str, Any]] = None
    
    async def fetch_openapi_schema(self) -> Dict[str, Any]:
        """Fetch OpenAPI schema from the API"""
        try:
            schema = await self.bubobot_api_manager.make_request(
                bearer_token="",
                endpoint=self.config.openapi_schema_endpoint,
                body={},
                method="GET"
            )
            self.openapi_schema = schema
            return schema
        except Exception as e:
            print(f"Error fetching OpenAPI schema: {e}")
            return {}
    
    def extract_get_apis(self, schema: Optional[Dict[str, Any]] = None) -> List[APIEndpoint]:
        """Extract all GET method APIs from OpenAPI schema"""
        if schema is None:
            schema = self.openapi_schema
        
        if not schema or 'paths' not in schema:
            print("Invalid or empty OpenAPI schema")
            return []
        
        get_endpoints = []
        paths = schema.get('paths', {})
        
        for path, path_data in paths.items():
            if 'get' in path_data:
                get_operation = path_data['get']
                
                endpoint = APIEndpoint(
                    path=path,
                    method='GET',
                    summary=get_operation.get('summary'),
                    description=get_operation.get('description'),
                    parameters=get_operation.get('parameters', []),
                    tags=get_operation.get('tags', [])
                )
                
                get_endpoints.append(endpoint)
        
        return get_endpoints
    
    def generate_tool_input_schema(self, endpoint: APIEndpoint) -> Dict[str, Any]:
        """Generate MCP tool input schema from API endpoint"""
        properties = {}
        required = []
        
        # Process parameters
        for param in endpoint.parameters:
            param_name = param.get('name', '')
            param_schema = param.get('schema', {})
            param_required = param.get('required', False)
            
            # Add parameter to schema
            properties[param_name] = {
                "type": param_schema.get('type', 'string'),
                "description": param.get('description', f"Parameter {param_name}")
            }
            
            # Add default value if exists
            if 'default' in param_schema:
                properties[param_name]["default"] = param_schema['default']
            
            # Add enum if exists
            if 'enum' in param_schema:
                properties[param_name]["enum"] = param_schema['enum']
            
            # Add to required if needed
            if param_required:
                required.append(param_name)
        
        return {
            "type": "object",
            "properties": properties,
            "required": required
        }
    
    def generate_tool_name(self, endpoint: APIEndpoint) -> str:
        """Generate tool name from endpoint path"""
        # Remove leading /api/v1/ and replace / with _
        path = endpoint.path.replace('/api/v1/', '').replace('/', '_')
        
        # Handle path parameters like {id}
        path = path.replace('{', 'by_').replace('}', '')
        
        # Convert all dashes to underscores for consistency
        path = path.replace('-', '_')
        
        # Add get_ prefix
        return f"get_{path}"

    def generate_mcp_tool(self, endpoint: APIEndpoint) -> Tool:
        """Generate MCP Tool definition from API endpoint"""
        tool_name = self.generate_tool_name(endpoint)
        input_schema = self.generate_tool_input_schema(endpoint)
        description = endpoint.summary or endpoint.description or f"API call to {endpoint.path}"

        return Tool(
            name=tool_name,
            description=description,
            inputSchema=input_schema
        )
    
    def generate_tools_for_tag(self, endpoints: List[APIEndpoint], tag_filter: Optional[str] = None) -> List[Tool]:
        """Generate MCP Tool objects for endpoints with specified tag"""
        # Filter endpoints by tag if specified
        if tag_filter:
            filtered_endpoints = [ep for ep in endpoints if tag_filter.lower() in [tag.lower() for tag in ep.tags]]
        else:
            filtered_endpoints = endpoints
        
        # Generate tools
        tools = [self.generate_mcp_tool(ep) for ep in filtered_endpoints]
        return tools
    
    def export_tools_to_json(self, tools_by_tag: Dict[str, List[Tool]], filename: str = "tools_info.json") -> str:
        """Export tools information to JSON file for inspection"""
        import os
        from datetime import datetime
        
        # Convert Tool objects to serializable dict format
        export_data = {
            "metadata": {
                "generated_at": datetime.now().isoformat(),
                "total_tags": len(tools_by_tag),
                "total_tools": sum(len(tools) for tools in tools_by_tag.values()),
                "extraction_source": "OpenAPI /docs-json endpoint"
            },
            "tools_by_tag": {}
        }
        
        for tag, tools in tools_by_tag.items():
            export_data["tools_by_tag"][tag] = {
                "tag_name": tag,
                "tool_count": len(tools),
                "tools": []
            }
            
            for tool in tools:
                tool_info = {
                    "name": tool.name,
                    "description": tool.description,
                    "inputSchema": tool.inputSchema,
                    "parameter_count": len(tool.inputSchema.get('properties', {})),
                    "required_parameters": tool.inputSchema.get('required', []),
                    "optional_parameters": [
                        k for k in tool.inputSchema.get('properties', {}).keys() 
                        if k not in tool.inputSchema.get('required', [])
                    ]
                }
                export_data["tools_by_tag"][tag]["tools"].append(tool_info)
        
        # Get the directory of current file
        current_dir = os.path.dirname(os.path.abspath(__file__))
        file_path = os.path.join(current_dir, filename)
        
        # Save to JSON file
        with open(file_path, 'w', encoding='utf-8') as f:
            json.dump(export_data, f, indent=2, ensure_ascii=False)
        
        print(f"💾 Exported tools info to {file_path}")
        return file_path
    
    async def run_pipeline(self):
        """Complete pipeline to extract GET endpoints and generate tools"""
        print("🚀 Starting OpenAPI GET endpoints pipeline...")
        
        # Fetch schema
        print("📡 Fetching OpenAPI schema...")
        schema = await self.fetch_openapi_schema()
        
        if not schema:
            print("❌ Failed to fetch OpenAPI schema")
            return {}
        
        # Extract GET endpoints
        print("🔍 Extracting GET endpoints...")
        get_endpoints = self.extract_get_apis(schema)
        
        if not get_endpoints:
            print("❌ No GET endpoints found")
            return {}
        
        # Group by tags
        endpoints_by_tag = {}
        for endpoint in get_endpoints:
            for tag in endpoint.tags:
                if tag not in endpoints_by_tag:
                    endpoints_by_tag[tag] = []
                endpoints_by_tag[tag].append(endpoint)
        
        print(f"📊 Found {len(get_endpoints)} GET endpoints in {len(endpoints_by_tag)} tags")
        
        # Generate tools by tag
        tools_by_tag = {}
        for tag, endpoints in endpoints_by_tag.items():
            print(f"🔧 Generating tools for {tag} with {len(endpoints)} endpoints...")
            tools = self.generate_tools_for_tag(endpoints, tag)
            tools_by_tag[tag] = tools
            print(f"✅ Generated {len(tools)} tools for {tag}")
        
        # Export tools info to JSON file
        if tools_by_tag:
            print(f"\n💾 Exporting tools info...")
            self.export_tools_to_json(tools_by_tag)
        
        return tools_by_tag


async def main():
    """Run the complete pipeline"""
    config = Config(
        base_config_path="./src/config/base_config.yml",
        env_file_path="./src/dev.local.env"
    )
    
    extractor = OpenAPIExtractor(config)
    tools_by_tag = await extractor.run_pipeline()

    # print('/'*50)
    # print(tools_by_tag)
    
    total_tools = sum(len(tools) for tools in tools_by_tag.values())
    print(f"\n✅ Pipeline complete! Generated {total_tools} tools across {len(tools_by_tag)} tags")
    
    # Show generated tools
    for tag, tools in tools_by_tag.items():
        print(f"\n📋 {tag.title()} Tools:")
        for tool in tools:
            print(f"   - {tool.name}: {tool.description}")


if __name__ == "__main__":
    asyncio.run(main())
