"""
Decorator for registering tools in the Agent Tool Registry.

Provides @atr.register() decorator to turn Python functions into discoverable tools.
"""

import inspect
from typing import Callable, Optional, List, Any, get_type_hints, get_origin, get_args, Union

from .schema import (
    ToolSpec,
    ToolMetadata,
    ParameterSpec,
    ParameterType,
    SideEffect,
    CostLevel,
)
from .registry import Registry


def _python_type_to_parameter_type(python_type: Any) -> ParameterType:
    """Convert Python type annotation to ParameterType.
    
    Args:
        python_type: Python type annotation
        
    Returns:
        Corresponding ParameterType
    """
    # Handle None type
    if python_type is type(None):
        return ParameterType.STRING
    
    # Get the origin to check for generic types
    origin = get_origin(python_type)
    
    # Only unwrap Optional/Union types - don't unwrap List, Dict, etc.
    # Check if it's a Union (which includes Optional)
    if origin is Union:
        args = get_args(python_type)
        if args:
            # For Optional[X] or Union[X, None], get X
            non_none_types = [arg for arg in args if arg is not type(None)]
            if non_none_types:
                # Use the first non-None type and recurse
                return _python_type_to_parameter_type(non_none_types[0])
    
    # Now check the origin for generic types like List[str], Dict[str, int]
    if origin is list:
        return ParameterType.ARRAY
    elif origin is dict:
        return ParameterType.OBJECT
    
    # Map basic Python types to ParameterType
    if python_type == str:
        return ParameterType.STRING
    elif python_type == int:
        return ParameterType.INTEGER
    elif python_type == float:
        return ParameterType.NUMBER
    elif python_type == bool:
        return ParameterType.BOOLEAN
    elif python_type == list:
        return ParameterType.ARRAY
    elif python_type == dict:
        return ParameterType.OBJECT
    else:
        # Default to string for unknown types
        return ParameterType.STRING


def _extract_parameters_from_function(func: Callable) -> List[ParameterSpec]:
    """Extract parameter specifications from function signature.
    
    Args:
        func: The function to analyze
        
    Returns:
        List of ParameterSpec objects
        
    Raises:
        ValueError: If function has parameters without type hints
    """
    sig = inspect.signature(func)
    type_hints = get_type_hints(func)
    parameters = []
    
    for param_name, param in sig.parameters.items():
        # Skip self/cls parameters
        if param_name in ('self', 'cls'):
            continue
            
        # Require type hints - no magic arguments!
        if param_name not in type_hints:
            raise ValueError(
                f"Parameter '{param_name}' in function '{func.__name__}' must have a type hint. "
                "No magic arguments allowed!"
            )
        
        python_type = type_hints[param_name]
        param_type = _python_type_to_parameter_type(python_type)
        
        # Determine if required (has no default value)
        required = param.default == inspect.Parameter.empty
        default = None if required else param.default
        
        # Extract description from docstring if available
        description = f"Parameter {param_name}"
        docstring = inspect.getdoc(func)
        if docstring:
            # Simple extraction - look for "param_name: description" pattern
            for line in docstring.split('\n'):
                if param_name in line and ':' in line:
                    parts = line.split(':', 1)
                    if len(parts) == 2:
                        description = parts[1].strip()
                        break
        
        param_spec = ParameterSpec(
            name=param_name,
            type=param_type,
            description=description,
            required=required,
            default=default,
        )
        parameters.append(param_spec)
    
    return parameters


def _extract_return_spec_from_function(func: Callable) -> Optional[ParameterSpec]:
    """Extract return value specification from function.
    
    Args:
        func: The function to analyze
        
    Returns:
        ParameterSpec for return value, or None if no return type
    """
    type_hints = get_type_hints(func)
    
    if 'return' not in type_hints or type_hints['return'] == type(None):
        return None
    
    return_type = type_hints['return']
    param_type = _python_type_to_parameter_type(return_type)
    
    return ParameterSpec(
        name="return_value",
        type=param_type,
        description="Function return value",
        required=True,
    )


class register:
    """Decorator to register a function as a tool in the ATR.
    
    Usage:
        @atr.register(name="scraper", cost="low", tags=["web", "scraping"])
        def scrape_website(url: str, timeout: int = 30) -> str:
            '''Scrape content from a website.
            
            Args:
                url: The URL to scrape
                timeout: Timeout in seconds
            '''
            # Implementation here
            pass
    
    This decorator:
    1. Extracts function signature and converts to ToolSpec
    2. Validates all parameters have type hints (no magic arguments!)
    3. Registers the tool in the global registry
    4. Returns the original function unchanged (doesn't wrap it)
    """

    def __init__(
        self,
        name: Optional[str] = None,
        description: Optional[str] = None,
        version: str = "1.0.0",
        author: Optional[str] = None,
        cost: str = "free",
        side_effects: Optional[List[str]] = None,
        tags: Optional[List[str]] = None,
        registry: Optional[Registry] = None,
    ):
        """Initialize the register decorator.
        
        Args:
            name: Tool name (defaults to function name)
            description: Tool description (defaults to function docstring)
            version: Tool version
            author: Tool author
            cost: Cost level (free, low, medium, high)
            side_effects: List of side effects
            tags: Searchable tags
            registry: Registry instance (uses global if not provided)
        """
        self.name = name
        self.description = description
        self.version = version
        self.author = author
        self.cost = cost
        self.side_effects = side_effects or ["none"]
        self.tags = tags or []
        self.registry = registry

    def __call__(self, func: Callable) -> Callable:
        """Register the function and return it unchanged.
        
        Args:
            func: The function to register
            
        Returns:
            The original function (not wrapped)
        """
        # Get or import global registry
        if self.registry is None:
            from . import _global_registry
            self.registry = _global_registry
        
        # Extract metadata
        tool_name = self.name or func.__name__
        tool_description = self.description or inspect.getdoc(func) or f"Tool: {tool_name}"
        
        # Parse cost level
        try:
            cost_level = CostLevel(self.cost.lower())
        except ValueError:
            cost_level = CostLevel.FREE
        
        # Parse side effects
        parsed_side_effects = []
        for se in self.side_effects:
            try:
                parsed_side_effects.append(SideEffect(se.lower()))
            except ValueError:
                parsed_side_effects.append(SideEffect.NONE)
        
        # Create metadata
        metadata = ToolMetadata(
            name=tool_name,
            description=tool_description,
            version=self.version,
            author=self.author,
            cost=cost_level,
            side_effects=parsed_side_effects,
            tags=self.tags,
        )
        
        # Extract parameters from function signature
        parameters = _extract_parameters_from_function(func)
        
        # Extract return specification
        returns = _extract_return_spec_from_function(func)
        
        # Create tool spec
        tool_spec = ToolSpec(
            metadata=metadata,
            parameters=parameters,
            returns=returns,
        )
        
        # Register the tool (store the callable but don't execute it)
        self.registry.register_tool(tool_spec, callable_func=func)
        
        # Return the original function unchanged - we don't wrap it
        # The registry stores it, but execution happens elsewhere
        return func
