import inspect
import os
from collections.abc import Callable
from typing import Any

from anthropic import Anthropic

from tests.fixtures.agents.prompt import SYSTEM_PROMPT
from tests.fixtures.datasource import datasource


def convert_functions_to_tools(functions: list[Callable]):
    tools = []
    for func in functions:
        sig = inspect.signature(func)

        properties = {}
        required = []

        for param_name, param in sig.parameters.items():
            if param_name in ("self", "cls"):
                continue

            param_type = param.annotation
            json_type = _python_type_to_json_type(param_type)

            properties[param_name] = {"type": json_type}

            if param.default == inspect.Parameter.empty:
                required.append(param_name)

        tools.append(
            {
                "name": func.__name__,
                "description": func.__doc__ or f"Function {func.__name__}",
                "input_schema": {
                    "type": "object",
                    "properties": properties,
                    "required": required,
                },
            }
        )

    return tools


def _python_type_to_json_type(python_type):
    type_mapping = {
        str: "string",
        int: "integer",
        float: "number",
        bool: "boolean",
        list: "array",
        dict: "object",
    }

    origin = getattr(python_type, "__origin__", None)
    if origin is not None:
        return type_mapping.get(origin, "string")

    return type_mapping.get(python_type, "string")


class AnthropicAgent:
    def __init__(self):
        self.client = Anthropic(
            api_key=os.getenv("ANTHROPIC_API_KEY"),
        )

    def ask(self, question: str) -> list[Any]:
        response = self.client.messages.create(
            max_tokens=500,
            model="claude-sonnet-4-0",
            messages=[
                {
                    "role": "user",
                    "content": question,
                },
            ],
            tools=convert_functions_to_tools(datasource),
            system=SYSTEM_PROMPT,
        )

        return response.content

    async def stream(self, question: str):
        """
        Stream responses from Anthropic with tool call support.

        Args:
            question: The question to ask

        Yields:
            Text chunks from the streaming response
        """
        messages = [
            {
                "role": "user",
                "content": question,
            },
        ]

        max_iterations = 3
        for _ in range(max_iterations):
            with self.client.messages.stream(
                max_tokens=500,
                model="claude-sonnet-4-0",
                messages=messages,
                tools=convert_functions_to_tools(datasource),
                system=SYSTEM_PROMPT,
            ) as stream:
                tool_use_blocks = []
                accumulated_text = []

                for event in stream:
                    if (
                        event.type == "content_block_delta"
                        and hasattr(event.delta, "text")
                        and event.delta.text is not None
                    ):
                        accumulated_text.append(event.delta.text)
                        yield event.delta.text
                    elif (
                        event.type == "content_block_start"
                        and hasattr(event.content_block, "type")
                        and event.content_block.type == "tool_use"
                    ):
                        tool_use_blocks.append(event.content_block)
                    elif (
                        event.type == "content_block_delta"
                        and hasattr(event.delta, "type")
                        and event.delta.type == "tool_use"
                        and hasattr(event.delta, "partial_json")
                    ):
                        pass

                final_message = stream.get_final_message()
                if final_message and hasattr(final_message, "content"):
                    tool_results = []

                    for block in final_message.content:
                        if hasattr(block, "type") and block.type == "tool_use":
                            tool_name = block.name
                            tool_input = block.input

                            func = None
                            for f in datasource:
                                if f.__name__ == tool_name:
                                    func = f
                                    break

                            if func:
                                try:
                                    if isinstance(tool_input, dict):
                                        result = func(**tool_input)
                                    else:
                                        result = func(tool_input)
                                    tool_results.append(
                                        {
                                            "type": "tool_result",
                                            "tool_use_id": block.id,
                                            "content": str(result),
                                        }
                                    )
                                except Exception as e:
                                    error_msg = repr(e)
                                    tool_results.append(
                                        {
                                            "type": "tool_result",
                                            "tool_use_id": block.id,
                                            "content": f"Error: {error_msg}",
                                        }
                                    )

                    if tool_results:
                        messages.append(
                            {
                                "role": "assistant",
                                "content": final_message.content,
                            }
                        )
                        messages.append({"role": "user", "content": tool_results})
                        continue

                break
