import re
from typing import List, Literal, Optional, Type, Union

from pydantic import BaseModel


def normalize_tool_name(name: str) -> str:
    """Normalize tool name to snake_case format and remove dots and spaces.
    Also handles OpenAPI-style duplicate names like 'add_add_get' by converting to 'add_get'.

    Args:
        name: Original tool name in various formats (including CamelCase, UpperCamelCase, or containing spaces)

    Returns:
        str: Normalized name in snake_case without dots or spaces
    """
    # First check for OpenAPI-style duplicate names (e.g. "add_add_get")
    openapi_pattern = r"^([a-zA-Z0-9]+)_\1_([a-zA-Z0-9]+)$"
    match = re.match(openapi_pattern, name)
    if match:
        return f"{match.group(1)}_{match.group(2)}"

    # Replace all special chars (., -, @, etc.) with single underscore
    name = re.sub(r"[.\-@]+", "_", name)

    # Remove spaces and collapse multiple spaces into a single space
    name = re.sub(r"\s+", " ", name).strip()

    # Replace spaces with underscores
    name = name.replace(" ", "_")

    # Convert CamelCase and UpperCamelCase to snake_case
    # Handles all cases including:
    # XMLParser -> xml_parser
    # getUserIDFromDB -> get_user_id_from_db
    # HTTPRequest -> http_request
    name = re.sub(r"(?<!^)(?=[A-Z][a-z])|(?<=[a-z0-9])(?=[A-Z])", "_", name).lower()

    # Collapse multiple underscores into single underscore
    return re.sub(r"_+", "_", name)


def get_all_static_methods(
    cls_or_instance: Union[Type, object],
    skip_list: Optional[List[str]] = None,
    include_list: Optional[List[str]] = None,
) -> List[str]:
    """Returns a list of all valid public static methods of a class or its instance.

    Args:
        cls_or_instance (Union[Type, object]): The class type or instance from which
            static methods will be retrieved.
        skip_list (Optional[List[str]]): A list of method names to explicitly skip.
        include_list (Optional[List[str]]): A list of method names to explicitly include.

    Returns:
        List[str]: A list of names of all valid public static methods in the class.

    Example:
        >>> class Example:
        ...     @staticmethod
        ...     def static_method_one():
        ...         pass
        ...
        ...     @staticmethod
        ...     def static_method_two():
        ...         pass
        ...
        >>> get_all_static_methods(Example, skip_list=["static_method_two"])
        ['static_method_one']
        >>> get_all_static_methods(Example, include_list=["static_method_two", "static_method_three"])
        ['static_method_two']
    """
    cls = (
        cls_or_instance
        if isinstance(cls_or_instance, type)
        else cls_or_instance.__class__
    )
    skip_list = skip_list or []
    include_list = include_list or []

    static_methods = []
    for name, attr in cls.__dict__.items():
        if isinstance(attr, staticmethod):
            # Check if the method is public (does not start with '_')
            if not name.startswith("_"):
                # Include the method only if it's in include_list, or if include_list is empty
                if (not include_list or name in include_list) and name not in skip_list:
                    static_methods.append(name)

    # Ensure all returned names are valid in the instance or type
    valid_methods = [name for name in static_methods if hasattr(cls_or_instance, name)]
    return valid_methods


class Function(BaseModel):
    arguments: str
    """
    The arguments to call the function with, as generated by the model in JSON
    format. Note that the model does not always generate valid JSON, and may
    hallucinate parameters not defined by your function schema. Validate the
    arguments in your code before calling your function.
    """

    name: str
    """The name of the function to call."""


class ChatCompletionMessageToolCall(BaseModel):
    id: str
    """The ID of the tool call."""

    function: Function
    """The function that the model called."""

    type: Literal["function"]
    """The type of the tool. Currently, only `function` is supported."""
