from __future__ import annotations

from app.llm.adapters.messages import (
    to_anthropic_messages,
    to_gemini_contents,
    to_openai_messages,
)
from app.llm.adapters.tools import (
    normalize_tools,
    parse_anthropic_tool_use,
    parse_gemini_function_calls,
    parse_openai_tool_calls,
    to_anthropic_tools,
    to_gemini_tools,
    to_openai_tools,
)
from app.llm.types import ToolSpec

SAMPLE_TOOL = ToolSpec(
    name="read_file",
    description="Read a workspace file",
    parameters={
        "type": "object",
        "properties": {"path": {"type": "string"}},
        "required": ["path"],
    },
)

SAMPLE_MESSAGES = [
    {"role": "system", "content": "You are helpful."},
    {"role": "user", "content": "Read README.md"},
    {"role": "assistant", "content": "I'll read that file."},
]


def test_to_openai_tools_round_trip():
    openai_tools = to_openai_tools([SAMPLE_TOOL])
    assert openai_tools == [
        {
            "type": "function",
            "function": {
                "name": "read_file",
                "description": "Read a workspace file",
                "parameters": SAMPLE_TOOL.parameters,
            },
        }
    ]

    normalized = normalize_tools(openai_tools)
    assert len(normalized) == 1
    assert normalized[0].name == SAMPLE_TOOL.name
    assert normalized[0].description == SAMPLE_TOOL.description
    assert normalized[0].parameters == SAMPLE_TOOL.parameters


def test_to_anthropic_tools_round_trip():
    anthropic_tools = to_anthropic_tools([SAMPLE_TOOL])
    assert anthropic_tools == [
        {
            "name": "read_file",
            "description": "Read a workspace file",
            "input_schema": SAMPLE_TOOL.parameters,
        }
    ]

    normalized = normalize_tools(anthropic_tools)
    assert len(normalized) == 1
    assert normalized[0].name == SAMPLE_TOOL.name
    assert normalized[0].parameters == SAMPLE_TOOL.parameters


def test_to_gemini_tools_round_trip():
    gemini_tools = to_gemini_tools([SAMPLE_TOOL])
    assert gemini_tools == [
        {
            "function_declarations": [
                {
                    "name": "read_file",
                    "description": "Read a workspace file",
                    "parameters": SAMPLE_TOOL.parameters,
                }
            ]
        }
    ]

    declarations = gemini_tools[0]["function_declarations"]
    normalized = normalize_tools(declarations)
    assert len(normalized) == 1
    assert normalized[0].name == SAMPLE_TOOL.name
    assert normalized[0].parameters == SAMPLE_TOOL.parameters


def test_to_openai_messages():
    assert to_openai_messages(SAMPLE_MESSAGES) == SAMPLE_MESSAGES
    assert to_openai_messages(
        [
            *SAMPLE_MESSAGES,
            {"role": "tool", "content": "ignored"},
            {"role": "user", "content": "   "},
        ]
    ) == SAMPLE_MESSAGES


def test_to_anthropic_messages_splits_system():
    system, messages = to_anthropic_messages(SAMPLE_MESSAGES)

    assert system == "You are helpful."
    assert messages == [
        {"role": "user", "content": "Read README.md"},
        {"role": "assistant", "content": "I'll read that file."},
    ]


def test_to_gemini_contents_maps_roles():
    system, contents = to_gemini_contents(SAMPLE_MESSAGES)

    assert system == "You are helpful."
    assert contents == [
        {"role": "user", "parts": [{"text": "Read README.md"}]},
        {"role": "model", "parts": [{"text": "I'll read that file."}]},
    ]


def test_parse_openai_tool_calls():
    raw = {
        "choices": [
            {
                "message": {
                    "tool_calls": [
                        {
                            "id": "call-1",
                            "function": {
                                "name": "read_file",
                                "arguments": '{"path": "README.md"}',
                            },
                        }
                    ]
                }
            }
        ]
    }

    calls = parse_openai_tool_calls(raw)
    assert len(calls) == 1
    assert calls[0].id == "call-1"
    assert calls[0].name == "read_file"
    assert calls[0].arguments == {"path": "README.md"}


def test_parse_anthropic_tool_use():
    blocks = [
        {"type": "text", "text": "Reading…"},
        {
            "type": "tool_use",
            "id": "toolu_1",
            "name": "read_file",
            "input": {"path": "README.md"},
        },
    ]

    calls = parse_anthropic_tool_use(blocks)
    assert len(calls) == 1
    assert calls[0].id == "toolu_1"
    assert calls[0].name == "read_file"
    assert calls[0].arguments == {"path": "README.md"}


def test_parse_gemini_function_calls():
    parts = [
        {"text": "Reading…"},
        {"function_call": {"name": "read_file", "args": {"path": "README.md"}}},
    ]

    calls = parse_gemini_function_calls(parts)
    assert len(calls) == 1
    assert calls[0].name == "read_file"
    assert calls[0].arguments == {"path": "README.md"}
    assert calls[0].id  # synthetic id when Gemini omits one
