"""
KgentikTools - Multi-framework tool resolver for Kgentik platform

This module provides seamless tool resolution from kgentik.yaml configuration,
supporting both local development and deployed environments across multiple
AI frameworks (LangGraph, CrewAI, etc.).
"""
import os
import yaml
from pathlib import Path
from typing import List, Dict, Any, Optional, Union
import importlib.util
import sys
import logging

logger = logging.getLogger(__name__)


class KgentikTools:
    """
    Multi-framework tool resolver for Kgentik platform.
    
    Automatically detects framework and loads tools from kgentik.yaml configuration.
    Works seamlessly in both local development and deployed environments.
    Supports LangGraph, CrewAI, and other AI frameworks.
    
    Example:
        >>> from kgentik.tools import KgentikTools
        >>> tools = KgentikTools()
        >>> tool_list = tools.get_tools()
    """
    
    def __init__(self, config_path: Optional[str] = None, framework: Optional[str] = None):
        """
        Initialize KgentikTools with optional config path and framework.
        
        Args:
            config_path: Path to kgentik.yaml file. If None, auto-detects.
            framework: Target framework ('langgraph', 'crewai'). If None, auto-detects.
        """
        self.config_path = config_path or self._find_config()
        self.framework = framework
        self._tools_cache: Dict[str, Any] = {}
        self._config_cache: Optional[Dict[str, Any]] = None
        
        if not self.config_path:
            raise FileNotFoundError(
                "No kgentik.yaml found. Please ensure you're in a Kgentik project directory "
                "or provide a config_path."
            )
        
        # Auto-detect framework if not specified
        if not self.framework:
            self.framework = self._detect_framework()
    
    def _find_config(self) -> Optional[str]:
        """Find kgentik.yaml in current or parent directories."""
        current = Path.cwd()
        for parent in [current] + list(current.parents):
            config = parent / "kgentik.yaml"
            if config.exists():
                return str(config)
        return None
    
    def _detect_framework(self) -> str:
        """Detect the framework from kgentik.yaml configuration."""
        config = self._load_config()
        framework = config.get("framework")
        
        if framework in ["langgraph", "crewai"]:
            return framework
        
        # Fallback: try to detect from legacy configuration
        if config.get("agent"):
            logger.warning("Legacy configuration detected. Assuming LangGraph framework.")
            return "langgraph"
        
        # Default to LangGraph for backward compatibility
        logger.warning("No framework specified. Defaulting to LangGraph.")
        return "langgraph"
    
    def _load_config(self) -> Dict[str, Any]:
        """Load and parse kgentik.yaml configuration."""
        if self._config_cache is not None:
            return self._config_cache
            
        try:
            with open(self.config_path, 'r') as f:
                self._config_cache = yaml.safe_load(f)
                return self._config_cache
        except Exception as e:
            raise ValueError(f"Failed to parse kgentik.yaml: {e}")
    
    def get_tools(self) -> List[Any]:
        """
        Load all tools from kgentik.yaml and adapt them for the target framework.
        
        Returns:
            List of tool functions/objects compatible with the target framework
        """
        config = self._load_config()
        tools = []
        
        for tool_def in config.get("tools", []):
            tool = self._import_tool(tool_def["name"])
            if tool:
                # Adapt tool for the target framework
                adapted_tool = self._adapt_tool_for_framework(tool, self.framework)
                tools.append(adapted_tool)
        
        return tools
    
    def get_framework(self) -> str:
        """Get the detected or specified framework."""
        return self.framework
    
    def get_config(self) -> Dict[str, Any]:
        """Get the loaded configuration."""
        return self._load_config()
    
    def _adapt_tool_for_framework(self, tool: Any, framework: str) -> Any:
        """
        Adapt a tool for the specified framework.
        
        Args:
            tool: The original tool object (should be StructuredTool)
            framework: Target framework ('langgraph', 'crewai')
            
        Returns:
            Framework-adapted tool
        """
        # Both LangGraph and CrewAI accept LangChain StructuredTool directly
        # No conversion needed!
        return tool
    
    
    def _import_tool(self, name: str) -> Any:
        """
        Import tool from tools/ or .kgentik/tools/
        
        Args:
            name: Tool name (e.g., 'sqrt', 'email-sender')
            
        Returns:
            The tool object or None if not found
        """
        module_name = name.replace('-', '_')
        
        # Try tools/{name}.py first
        try:
            module = importlib.import_module(f'tools.{module_name}')
            return self._extract_tool(module)
        except ImportError:
            pass
        
        # Try .kgentik/tools/{name}.py
        try:
            config_dir = Path(self.config_path).parent
            kgentik_tools_path = config_dir / '.kgentik'
            if kgentik_tools_path.exists():
                sys.path.insert(0, str(kgentik_tools_path))
                try:
                    module = importlib.import_module(f'tools.{module_name}')
                    return self._extract_tool(module)
                finally:
                    sys.path.pop(0)
        except ImportError:
            pass
        
        print(f"Warning: Tool '{name}' not found")
        return None
    
    def _extract_tool(self, module) -> Any:
        """
        Extract @tool decorated function from module
        
        Args:
            module: The imported Python module
            
        Returns:
            The StructuredTool object created by @tool decorator
        """
        # Look for StructuredTool objects first (the actual tools created by @tool decorator)
        for attr_name in dir(module):
            if attr_name.startswith('_'):
                continue
            attr = getattr(module, attr_name)
            
            # Check if it's a LangChain StructuredTool (has 'name' and 'invoke' attributes)
            if hasattr(attr, 'name') and hasattr(attr, 'invoke') and not callable(attr):
                return attr
        
        # Fallback: look for explicitly named tool exports
        if hasattr(module, 'tool') and hasattr(module.tool, 'name'):
            return module.tool
                
        # Last resort: check for function with @tool decorator
        for attr_name in dir(module):
            if attr_name.startswith('_'):
                continue
            attr = getattr(module, attr_name)
            if callable(attr) and hasattr(attr, '__wrapped__'):
                return attr
        
        logger.warning(f"No tool found in module {module.__name__}")
        return None
    
    def __iter__(self):
        """Make it iterable."""
        return iter(self.get_tools())
    
    def __len__(self):
        """Support len()."""
        return len(self.get_tools())
    
    def __repr__(self):
        """String representation."""
        return f"KgentikTools(config_path='{self.config_path}', framework='{self.framework}')"


def get_framework_info() -> Dict[str, Any]:
    """
    Get information about supported frameworks and their capabilities.
    
    Returns:
        Dictionary with framework information
    """
    return {
        "supported_frameworks": ["langgraph", "crewai"],
        "framework_info": {
            "langgraph": {
                "name": "LangGraph",
                "description": "Build stateful, multi-actor applications with LangGraph",
                "patterns": ["react", "mas-supervisor", "mas-swarm", "custom"],
                "tool_compatibility": "native"
            },
            "crewai": {
                "name": "CrewAI",
                "description": "Orchestrate role-playing AI agents with CrewAI",
                "patterns": ["single-agent", "crew", "flow", "custom"],
                "tool_compatibility": "native"
            }
        }
    }


def validate_config(config_path: Optional[str] = None) -> Dict[str, Any]:
    """
    Validate a kgentik.yaml configuration file.
    
    Args:
        config_path: Path to kgentik.yaml file. If None, auto-detects.
        
    Returns:
        Validation result with errors and warnings
    """
    try:
        tools = KgentikTools(config_path)
        config = tools.get_config()
        
        errors = []
        warnings = []
        
        # Check required fields
        if not config.get("name"):
            errors.append("Missing required field: name")
        if not config.get("description"):
            errors.append("Missing required field: description")
        if not config.get("framework"):
            errors.append("Missing required field: framework")
        
        # Validate framework
        framework = config.get("framework")
        supported_frameworks = get_framework_info()["supported_frameworks"]
        if framework not in supported_frameworks:
            errors.append(f"Unsupported framework: {framework}. Supported: {', '.join(supported_frameworks)}")
        
        # Framework-specific validation
        if framework == "langgraph":
            if not config.get("langgraph"):
                errors.append("LangGraph framework requires 'langgraph' configuration")
        elif framework == "crewai":
            if not config.get("crewai"):
                errors.append("CrewAI framework requires 'crewai' configuration")
        
        # Validate tools
        tool_count = len(tools.get_tools())
        if tool_count == 0:
            warnings.append("No tools configured")
        
        return {
            "valid": len(errors) == 0,
            "errors": errors,
            "warnings": warnings,
            "framework": framework,
            "tool_count": tool_count
        }
        
    except Exception as e:
        return {
            "valid": False,
            "errors": [f"Configuration error: {str(e)}"],
            "warnings": [],
            "framework": None,
            "tool_count": 0
        }


# TODO: Future extensions
# - CLI tools (kgentik deploy, kgentik test, kgentik init)
# - Testing utilities for agents
# - Deployment helpers
# - Advanced framework adapters
# - Tool validation and type checking
# - Hot-reloading for development
# - Agent composition utilities (KgentikAgents class)
