"""
Core module providing the autoarg decorator/wrapper functionality.
"""

import argparse
import sys
from typing import Any, Callable, List, Optional, Union

from .parser import (
    create_argument_name,
    extract_parameter_info,
    get_argparse_type,
    parse_docstring,
)


def autoarg(func_or_funcs: Union[Callable, List[Callable]]) -> Callable:
    """
    Wrap a callable or list of callables with automatic argparse CLI generation.
    
    For a single callable, creates a CLI that parses arguments for that function.
    For multiple callables, creates subcommands for each function.
    
    Args:
        func_or_funcs: A single callable or list of callables to wrap
        
    Returns:
        A callable that when invoked will parse command-line arguments and
        execute the appropriate function
    """
    # Determine if single function or multiple
    if isinstance(func_or_funcs, list):
        return _create_multi_command_wrapper(func_or_funcs)
    else:
        return _create_single_command_wrapper(func_or_funcs)


def _create_single_command_wrapper(func: Callable) -> Callable:
    """
    Create a wrapper for a single callable with argparse.
    
    Args:
        func: The callable to wrap
        
    Returns:
        Wrapped callable that parses CLI arguments
    """
    # Parse function metadata outside wrapper to trigger warnings at wrap time
    description, param_help = parse_docstring(func)
    params_info = extract_parameter_info(func)
    
    def wrapper(argv: Optional[List[str]] = None) -> Any:
        
        # Create argument parser
        parser = argparse.ArgumentParser(
            description=description or f"CLI for {func.__name__}",
            prog=func.__name__
        )
        
        # Add arguments
        for param_name, info in params_info.items():
            _add_argument_to_parser(parser, param_name, info, param_help)
        
        # Parse arguments
        args = parser.parse_args(argv)
        
        # Call function with parsed arguments
        kwargs = vars(args)
        return func(**kwargs)
    
    return wrapper


def _create_multi_command_wrapper(funcs: List[Callable]) -> Callable:
    """
    Create a wrapper for multiple callables with subcommands.
    
    Args:
        funcs: List of callables to create subcommands for
        
    Returns:
        Wrapped callable that parses CLI arguments and routes to subcommands
    """
    # Parse all function metadata outside wrapper to trigger warnings at wrap time
    funcs_metadata = []
    for func in funcs:
        description, param_help = parse_docstring(func)
        params_info = extract_parameter_info(func)
        funcs_metadata.append((func, description, param_help, params_info))
    
    def wrapper(argv: Optional[List[str]] = None) -> Any:
        # Create main parser
        parser = argparse.ArgumentParser(
            description="Multi-command CLI",
            prog="cli"
        )
        subparsers = parser.add_subparsers(dest='command', required=True)
        
        # Create subparser for each function
        func_map = {}
        for func, description, param_help, params_info in funcs_metadata:
            
            # Create subparser
            subparser = subparsers.add_parser(
                func.__name__,
                help=description or f"Execute {func.__name__}",
                description=description or f"CLI for {func.__name__}"
            )
            
            # Add arguments to subparser
            for param_name, info in params_info.items():
                _add_argument_to_parser(subparser, param_name, info, param_help)
            
            func_map[func.__name__] = func
        
        # Parse arguments
        args = parser.parse_args(argv)
        
        # Get the selected function
        selected_func = func_map[args.command]
        
        # Remove 'command' from kwargs and call function
        kwargs = vars(args)
        del kwargs['command']
        
        return selected_func(**kwargs)
    
    return wrapper


def _add_argument_to_parser(
    parser: argparse.ArgumentParser,
    param_name: str,
    param_info: dict,
    param_help: dict
) -> None:
    """
    Add a single argument to an argparse parser.
    
    Args:
        parser: The ArgumentParser to add the argument to
        param_name: Name of the parameter
        param_info: Dictionary with parameter metadata (type, default, etc.)
        param_help: Dictionary mapping parameter names to help text
    """
    arg_name = create_argument_name(param_name)
    param_type = param_info['type']
    has_default = param_info['has_default']
    default_value = param_info['default']
    help_text = param_help.get(param_name, '')
    
    # Get argparse type and additional kwargs
    argparse_type, type_kwargs = get_argparse_type(param_type)
    
    # Build argument kwargs
    arg_kwargs = {
        'help': help_text,
        **type_kwargs
    }
    
    # Handle boolean types specially
    if argparse_type == bool:
        if has_default:
            if default_value:
                # Default is True, create --no-param flag to set False
                arg_name = '--no-' + param_name.replace('_', '-')
                arg_kwargs['action'] = 'store_false'
                arg_kwargs['dest'] = param_name
            else:
                # Default is False, so flag should set to True
                arg_kwargs['action'] = 'store_true'
                arg_kwargs['dest'] = param_name
        else:
            # No default, use store_true
            arg_kwargs['action'] = 'store_true'
            arg_kwargs['dest'] = param_name
        
        # Remove type from kwargs for boolean actions
        if 'type' in arg_kwargs:
            del arg_kwargs['type']
    else:
        # Non-boolean type
        arg_kwargs['type'] = argparse_type
        
        if has_default:
            arg_kwargs['default'] = default_value
            if default_value is not None:
                arg_kwargs['help'] += f' (default: {default_value})'
        else:
            # Required argument
            arg_kwargs['required'] = True
    
    parser.add_argument(arg_name, **arg_kwargs)
