from typing import Any, Literal

from pydantic import BaseModel, ValidationError, field_serializer


class ToolCall(BaseModel):
    id: str
    """The ID of the tool call."""
    name: str
    """The name of the function to call."""
    arguments: str
    """The arguments to call the function with, as generated by the model in JSON format."""
    type: Literal["function", "custom"] = "function"
    """The type of the tool call. Either 'function' or 'custom'."""

    @classmethod
    def from_tool_call(cls, tool_call: Any) -> "ToolCall":
        """Convert various tool call formats to ToolCall using Pydantic validation.

        This method attempts to validate the input against known tool call formats
        using Pydantic models, making it more robust than string-based type checking.

        Supports the following formats:
        - ChatCompletionMessageFunctionToolCall (function calls)
        - ChatCompletionMessageCustomToolCall (custom calls)
        - ResponseFunctionToolCall (response API format)
        - Dictionary representations of the above

        Args:
            tool_call: Tool call object in various formats (Pydantic models or dicts)

        Returns:
            ToolCall: Normalized tool call object with proper type classification

        Raises:
            TypeError: If the tool call format is not supported
        """
        from .openai.chat_completion import (
            ChatCompletionMessageCustomToolCall,
            ChatCompletionMessageFunctionToolCall,
        )
        from .openai.response import ResponseFunctionToolCall

        # Try to validate as ChatCompletionMessageFunctionToolCall
        try:
            # Convert to dict if it's a Pydantic model or has model_dump
            if hasattr(tool_call, "model_dump"):
                tool_call_dict = tool_call.model_dump()
            elif hasattr(tool_call, "dict"):
                tool_call_dict = tool_call.dict()
            else:
                tool_call_dict = tool_call

            validated_chat_call = ChatCompletionMessageFunctionToolCall.model_validate(
                tool_call_dict
            )
            return cls(
                id=validated_chat_call.id,
                name=validated_chat_call.function.name,
                arguments=validated_chat_call.function.arguments,
                type="function",
            )
        except (ValidationError, AttributeError, TypeError):
            pass

        # Try to validate as ChatCompletionMessageCustomToolCall
        try:
            # Convert to dict if it's a Pydantic model or has model_dump
            if hasattr(tool_call, "model_dump"):
                tool_call_dict = tool_call.model_dump()
            elif hasattr(tool_call, "dict"):
                tool_call_dict = tool_call.dict()
            else:
                tool_call_dict = tool_call

            validated_custom_call = ChatCompletionMessageCustomToolCall.model_validate(
                tool_call_dict
            )
            return cls(
                id=validated_custom_call.id,
                name=validated_custom_call.custom.name,
                arguments=validated_custom_call.custom.input,
                type="custom",
            )
        except (ValidationError, AttributeError, TypeError):
            pass

        # Try to validate as ResponseFunctionToolCall
        try:
            # Convert to dict if it's a Pydantic model or has model_dump
            if hasattr(tool_call, "model_dump"):
                tool_call_dict = tool_call.model_dump()
            elif hasattr(tool_call, "dict"):
                tool_call_dict = tool_call.dict()
            else:
                tool_call_dict = tool_call

            validated_response_call = ResponseFunctionToolCall.model_validate(
                tool_call_dict
            )
            return cls(
                id=validated_response_call.call_id,
                name=validated_response_call.name,
                arguments=validated_response_call.arguments,
                type="function",
            )
        except (ValidationError, AttributeError, TypeError):
            pass

        # If validation failed, try direct attribute access as fallback
        try:
            # Check for ChatCompletion format (has function attribute)
            if hasattr(tool_call, "function") and hasattr(tool_call, "id"):
                return cls(
                    id=tool_call.id,
                    name=tool_call.function.name,
                    arguments=tool_call.function.arguments,
                    type="function",
                )
            # Check for Custom format (has custom attribute)
            elif hasattr(tool_call, "custom") and hasattr(tool_call, "id"):
                return cls(
                    id=tool_call.id,
                    name=tool_call.custom.name,
                    arguments=tool_call.custom.input,
                    type="custom",
                )
            # Check for Response format (has call_id attribute)
            elif hasattr(tool_call, "call_id") and hasattr(tool_call, "name"):
                return cls(
                    id=tool_call.call_id,
                    name=tool_call.name,
                    arguments=tool_call.arguments,
                    type="function",
                )
        except AttributeError:
            pass

        raise TypeError(
            f"Unsupported tool call format. Expected ChatCompletionMessageFunctionToolCall, "
            f"ChatCompletionMessageCustomToolCall, or ResponseFunctionToolCall, got {type(tool_call)}"
        )


class ToolCallResult(BaseModel):
    id: str
    """The ID of the tool call."""
    result: Any
    """The result of the tool call."""

    @field_serializer("result")
    def convert_any_field_to_str(self, value: Any) -> str:
        """Convert `result` to string during serialization.

        Args:
            value: Current value of `any_field`.

        Returns:
            str: String representation of the field.
        """
        # simple case for now, can be extended to handle more complex cases
        return str(value)


API_FORMATS = Literal[
    "openai",  # old default, alias to openai-chatcompletion
    "openai-chatcompletion",  # chat completion
    "openai-response",
    "anthropic",
    "gemini",
]


def convert_tool_calls(tool_calls: list[Any]) -> list[ToolCall]:
    """Convert a list of tool calls into a list of ToolCall objects.

    Args:
        tool_calls (List[Any]): A list of tool call objects to convert.

    Returns:
        List[ToolCall]: A list of converted ToolCall objects.
    """
    return [ToolCall.from_tool_call(tool_call) for tool_call in tool_calls]


def recover_assistant_message(
    tool_calls: list[ToolCall],
    *,
    api_format: API_FORMATS = "openai",
) -> list[dict[str, Any]]:
    """Recover the assistant message from tool calls in various API formats.

    This function can handle mixed tool call types (function and custom) and will
    generate the appropriate message format based on each tool call's type.

    Args:
        tool_calls (List[ToolCall]): A list of ToolCall objects with mixed types.
        api_format (API_FORMATS, optional): The desired API format. Defaults to "openai".

    Returns:
        List[Dict[str, Any]]: The assistant message in the specified API format.
        For OpenAI format, returns a list with ChatCompletionMessage containing
        both function and custom tool calls as appropriate.

    Raises:
        NotImplementedError: If the API format is "anthropic" or "gemini".
        ValueError: If the API format is unsupported.
    """
    if api_format in ["openai", "openai-chatcompletion"]:
        from .openai.chat_completion import (
            ChatCompletionMessage,
            ChatCompletionMessageCustomToolCall,
            ChatCompletionMessageFunctionToolCall,
            Custom,
            Function,
        )

        # Build tool calls based on their type
        chat_tool_calls = []
        for tool_call in tool_calls:
            if not tool_call.name or not tool_call.arguments:
                continue

            if tool_call.type == "function":
                chat_tool_calls.append(
                    ChatCompletionMessageFunctionToolCall(
                        id=tool_call.id,
                        function=Function(
                            name=tool_call.name,
                            arguments=tool_call.arguments,
                        ),
                    )
                )
            elif tool_call.type == "custom":
                chat_tool_calls.append(
                    ChatCompletionMessageCustomToolCall(
                        id=tool_call.id,
                        custom=Custom(
                            name=tool_call.name,
                            input=tool_call.arguments,
                        ),
                    )
                )

        message = [ChatCompletionMessage(tool_calls=chat_tool_calls).model_dump()]

        return message

    elif api_format == "openai-response":
        from .openai.response import ResponseFunctionToolCall

        message = [
            ResponseFunctionToolCall(
                call_id=tool_call.id,
                name=tool_call.name,
                arguments=tool_call.arguments,
            ).model_dump()
            for tool_call in tool_calls
        ]

        return message

    elif api_format == "anthropic":
        raise NotImplementedError("Anthropic API format is not supported yet.")
    elif api_format == "gemini":
        raise NotImplementedError("Gemini API format is not supported yet.")
    else:
        raise ValueError(f"Unsupported API format: {api_format}")


def recover_tool_message(
    tool_responses: dict[str, str],
    *,
    api_format: API_FORMATS = "openai",
) -> list[dict[str, Any]]:
    """Recover the tool message from tool responses in various API formats.

    Args:
        tool_responses (Dict[str, str]): A dictionary of tool responses with call_ids as keys and results as values.
        api_format (API_FORMATS, optional): The desired API format. Defaults to "openai".

    Returns:
        List[Dict[str, Any]]: A list of tool messages in the specified API format.

    Raises:
        NotImplementedError: If the API format is "anthropic" or "gemini".
        ValueError: If the API format is unsupported.
    """
    messages = []
    for call_id, result in tool_responses.items():
        if api_format in ["openai", "openai-chatcompletion"]:
            from .openai.chat_completion import ChatCompetionMessageToolCallResult

            tool_message = ChatCompetionMessageToolCallResult(
                tool_call_id=call_id,
                content=str(result),
            )
            messages.append(tool_message.model_dump())
        elif api_format == "openai-response":
            from .openai.response import ResponseFunctionToolCallResult

            tool_message = ResponseFunctionToolCallResult(
                call_id=call_id,
                output=str(result),
            )
            messages.append(tool_message.model_dump())
        elif api_format == "anthropic":
            raise NotImplementedError("Anthropic API format is not supported yet")
        elif api_format == "gemini":
            raise NotImplementedError("Gemini API format is not supported yet")
        else:
            raise ValueError(f"Unsupported API format: {api_format}")

    return messages
