using Test
using YiemAgent
using YiemAgent.agentCore
using YiemAgent.type
using JSON

# Import the function from the private module scope
import YiemAgent.agentCore: _extractToolCalls

@testset "_extractToolCalls" begin

    # ------------------------------------------------------------------ #
    # Format 1: response["message"]["tool_calls"] (LMStudio.jl style)     #
    # ------------------------------------------------------------------ #

    @testset "single tool call via message format" begin
        response = Dict{String,Any}(
            "finish_reason" => "tool_calls",
            "index" => 0,
            "message" => Dict{String,Any}(
                "role" => "assistant",
                "content" => "",
                "reasoning_content" => "Let me check the weather.",
                "tool_calls" => Any[
                    Dict{String,Any}(
                        "type" => "function",
                        "function" => Dict{String,Any}(
                            "name" => "getWeather",
                            "arguments" => "{\"city\":\"Bangkok, Thailand\"}",
                        ),
                        "id" => "tc_001",
                    )
                ],
            ),
        )
        has_toolcalls, tc_list, assistant_msg = _extractToolCalls(response)
        @test has_toolcalls == true
        @test length(tc_list) == 1
        @test tc_list[1].name == "getWeather"
        @test tc_list[1].id == "tc_001"
        @test tc_list[1].type == "function"
        @test tc_list[1].arguments["city"] == "Bangkok, Thailand"
        @test assistant_msg isa assistantMessage
        @test assistant_msg.role == "assistant"
        @test assistant_msg.stopReason == "tool_calls"
        @test length(assistant_msg.content) == 1
        @test assistant_msg.content[1] isa reasoningContent
        @test assistant_msg.content[1].text == "Let me check the weather."
    end

    @testset "multiple tool calls via message format" begin
        response = Dict{String,Any}(
            "message" => Dict{String,Any}(
                "role" => "assistant",
                "content" => "",
                "tool_calls" => Any[
                    Dict{String,Any}(
                        "type" => "function",
                        "function" => Dict{String,Any}(
                            "name" => "getWeather",
                            "arguments" => "{\"city\":\"Tokyo, Japan\"}",
                        ),
                        "id" => "tc_001",
                    ),
                    Dict{String,Any}(
                        "type" => "function",
                        "function" => Dict{String,Any}(
                            "name" => "getTime",
                            "arguments" => "{\"timezone\":\"Asia/Tokyo\"}",
                        ),
                        "id" => "tc_002",
                    ),
                ],
            ),
        )
        has_toolcalls, tc_list, assistant_msg = _extractToolCalls(response)
        @test has_toolcalls == true
        @test length(tc_list) == 2
        @test tc_list[1].name == "getWeather"
        @test tc_list[1].arguments["city"] == "Tokyo, Japan"
        @test tc_list[2].name == "getTime"
        @test tc_list[2].arguments["timezone"] == "Asia/Tokyo"
        @test assistant_msg.role == "assistant"
    end

    @testset "tool call with empty arguments string" begin
        response = Dict{String,Any}(
            "message" => Dict{String,Any}(
                "role" => "assistant",
                "tool_calls" => Any[
                    Dict{String,Any}(
                        "type" => "function",
                        "function" => Dict{String,Any}(
                            "name" => "listTools",
                            "arguments" => "{}",
                        ),
                        "id" => "tc_empty",
                    ),
                ],
            ),
        )
        has_toolcalls, tc_list, assistant_msg = _extractToolCalls(response)
        @test has_toolcalls == true
        @test length(tc_list) == 1
        @test tc_list[1].name == "listTools"
        @test tc_list[1].arguments == Dict{String,Any}()
        @test assistant_msg.stopReason == "end_turn"
    end

    @testset "tool call with missing id falls back to uuid" begin
        response = Dict{String,Any}(
            "message" => Dict{String,Any}(
                "role" => "assistant",
                "tool_calls" => Any[
                    Dict{String,Any}(
                        "type" => "function",
                        "function" => Dict{String,Any}(
                            "name" => "getTime",
                            "arguments" => "{\"city\":\"NYC\"}",
                        ),
                    ),
                ],
            ),
        )
        has_toolcalls, tc_list, assistant_msg = _extractToolCalls(response)
        @test has_toolcalls == true
        @test length(tc_list) == 1
        @test !isempty(tc_list[1].id)
        @test tc_list[1].name == "getTime"
    end

    @testset "tool call with non-string arguments (pre-parsed dict)" begin
        response = Dict{String,Any}(
            "message" => Dict{String,Any}(
                "role" => "assistant",
                "tool_calls" => Any[
                    Dict{String,Any}(
                        "type" => "function",
                        "function" => Dict{String,Any}(
                            "name" => "getWeather",
                            "arguments" => Dict{String,Any}("city" => "London", "units" => "fahrenheit"),
                        ),
                        "id" => "tc_parsed",
                    ),
                ],
            ),
        )
        has_toolcalls, tc_list, assistant_msg = _extractToolCalls(response)
        @test has_toolcalls == true
        @test length(tc_list) == 1
        @test tc_list[1].arguments["city"] == "London"
        @test tc_list[1].arguments["units"] == "fahrenheit"
    end

    @testset "tool call with api/provider/model/usage metadata" begin
        response = Dict{String,Any}(
            "api" => "openai",
            "provider" => "anthropic",
            "model" => "claude-3-opus",
            "message" => Dict{String,Any}(
                "role" => "assistant",
                "tool_calls" => Any[
                    Dict{String,Any}(
                        "type" => "function",
                        "function" => Dict{String,Any}(
                            "name" => "getTime",
                            "arguments" => "{}",
                        ),
                        "id" => "tc_meta",
                    ),
                ],
            ),
        )
        has_toolcalls, tc_list, assistant_msg = _extractToolCalls(response)
        @test has_toolcalls == true
        @test assistant_msg.api == "openai"
        @test assistant_msg.provider == "anthropic"
        @test assistant_msg.model == "claude-3-opus"
    end

    # --------------------------------------------------------------- #
    # Format 2: response.content blocks (OpenAI API style)            #
    # --------------------------------------------------------------- #

    @testset "content blocks with tool_calls" begin
        response = Dict{String,Any}(
            "content" => Any[
                Dict{String,Any}("type" => "text", "text" => "Let me check."),
                Dict{String,Any}(
                    "type" => "tool_calls",
                    "tool_calls" => Any[
                        Dict{String,Any}(
                            "type" => "function",
                            "function" => Dict{String,Any}(
                                "name" => "getWeather",
                                "arguments" => "{\"city\":\"Paris\"}",
                            ),
                            "id" => "tc_block_1",
                        ),
                    ],
                ),
            ],
        )
        has_toolcalls, tc_list, assistant_msg = _extractToolCalls(response)
        @test has_toolcalls == true
        @test length(tc_list) == 1
        @test tc_list[1].name == "getWeather"
        @test tc_list[1].arguments["city"] == "Paris"
        # text block before tool_calls should be included in content
        @test length(assistant_msg.content) == 1
        @test assistant_msg.content[1].text == "Let me check."
    end

    @testset "content blocks with tool_call (single-call format)" begin
        response = Dict{String,Any}(
            "content" => Any[
                Dict{String,Any}(
                    "type" => "tool_call",
                    "id" => "tc_single",
                    "name" => "getTime",
                    "arguments" => Dict{String,Any}("timezone" => "Europe/London"),
                ),
            ],
        )
        has_toolcalls, tc_list, assistant_msg = _extractToolCalls(response)
        @test has_toolcalls == true
        @test length(tc_list) == 1
        @test tc_list[1].name == "getTime"
        @test tc_list[1].id == "tc_single"
        @test tc_list[1].arguments["timezone"] == "Europe/London"
    end

    @testset "content blocks with reasoning and text" begin
        response = Dict{String,Any}(
            "content" => Any[
                Dict{String,Any}("type" => "reasoning", "text" => "Thinking..."),
                Dict{String,Any}("type" => "text", "text" => "Here's the answer."),
            ],
        )
        has_toolcalls, tc_list, assistant_msg = _extractToolCalls(response)
        @test has_toolcalls == false
        @test length(tc_list) == 0
        @test length(assistant_msg.content) == 2
        @test assistant_msg.content[1] isa reasoningContent
        @test assistant_msg.content[1].text == "Thinking..."
        @test assistant_msg.content[2] isa textContent
        @test assistant_msg.content[2].text == "Here's the answer."
    end

    @testset "content blocks with text and tool_call (tool_call not in content)" begin
        response = Dict{String,Any}(
            "content" => Any[
                Dict{String,Any}("type" => "text", "text" => "Sure, I'll check."),
                Dict{String,Any}(
                    "type" => "tool_call",
                    "id" => "tc_mix",
                    "name" => "getWeather",
                    "arguments" => Dict{String,Any}("city" => "London"),
                ),
            ],
        )
        has_toolcalls, tc_list, assistant_msg = _extractToolCalls(response)
        @test has_toolcalls == true
        @test length(tc_list) == 1
        @test tc_list[1].name == "getWeather"
        # text block included, tool_call block excluded from content
        @test length(assistant_msg.content) == 1
        @test assistant_msg.content[1].text == "Sure, I'll check."
    end

    # ------------------------------------------------------------------ #
    # assistantMessage construction                                       #
    # ------------------------------------------------------------------ #

    @testset "assistantMessage with error_message and errorMessage fallback" begin
        response = Dict{String,Any}(
            "error_message" => "rate limit",
            "content" => Any[Dict{String,Any}("type" => "text", "text" => "fail")],
        )
        has_toolcalls, tc_list, assistant_msg = _extractToolCalls(response)
        @test has_toolcalls == false
        @test assistant_msg.errorMessage == "rate limit"
    end

    @testset "assistantMessage with usage tracking" begin
        response = Dict{String,Any}(
            "content" => Any[Dict{String,Any}("type" => "text", "text" => "hi")],
            "usage" => llmUsage(100, 50),
        )
        has_toolcalls, tc_list, assistant_msg = _extractToolCalls(response)
        @test assistant_msg.usage.inputTokens == 100
        @test assistant_msg.usage.outputTokens == 50
    end

    @testset "assistantMessage with invalid usage defaults to zero" begin
        response = Dict{String,Any}(
            "content" => Any[Dict{String,Any}("type" => "text", "text" => "hi")],
            "usage" => "invalid",
        )
        has_toolcalls, tc_list, assistant_msg = _extractToolCalls(response)
        @test assistant_msg.usage.inputTokens == 0
        @test assistant_msg.usage.outputTokens == 0
    end

    @testset "reasoning_content as textContent" begin
        response = Dict{String,Any}(
            "reasoning_content" => textContent("internal thought"),
            "content" => Any[Dict{String,Any}("type" => "text", "text" => "output")],
        )
        has_toolcalls, tc_list, assistant_msg = _extractToolCalls(response)
        @test length(assistant_msg.content) == 2
        @test assistant_msg.content[1] isa reasoningContent
        @test assistant_msg.content[1].text == "internal thought"
        @test assistant_msg.content[2] isa textContent
        @test assistant_msg.content[2].text == "output"
    end

    # ------------------------------------------------------------------ #
    # No tool call cases                                                  #
    # ------------------------------------------------------------------ #

    @testset "no tool calls found" begin
        response = Dict{String,Any}(
            "content" => Any[
                Dict{String,Any}("type" => "text", "text" => "Hello world."),
            ],
        )
        has_toolcalls, tc_list, assistant_msg = _extractToolCalls(response)
        @test has_toolcalls == false
        @test length(tc_list) == 0
        @test assistant_msg.stopReason == "end_turn"
    end

    @testset "empty message" begin
        response = Dict{String,Any}()
        has_toolcalls, tc_list, assistant_msg = _extractToolCalls(response)
        @test has_toolcalls == false
        @test length(tc_list) == 0
        @test assistant_msg.role == "assistant"
        @test assistant_msg.stopReason == "end_turn"
        @test length(assistant_msg.content) == 0
    end

    @testset "message with empty tool_calls array" begin
        response = Dict{String,Any}(
            "message" => Dict{String,Any}(
                "role" => "assistant",
                "tool_calls" => Any[],
            ),
        )
        has_toolcalls, tc_list, assistant_msg = _extractToolCalls(response)
        @test has_toolcalls == false
        @test length(tc_list) == 0
        @test assistant_msg.role == "assistant"
    end

    @testset "message field is not a Dict" begin
        response = Dict{String,Any}(
            "message" => "not a dict",
            "content" => Any[Dict{String,Any}("type" => "text", "text" => "fallback")],
        )
        has_toolcalls, tc_list, assistant_msg = _extractToolCalls(response)
        @test has_toolcalls == false
        @test length(tc_list) == 0
        @test length(assistant_msg.content) == 1
    end

    @testset "Format 1 takes priority over Format 2" begin
        response = Dict{String,Any}(
            "message" => Dict{String,Any}(
                "role" => "assistant",
                "tool_calls" => Any[
                    Dict{String,Any}(
                        "type" => "function",
                        "function" => Dict{String,Any}(
                            "name" => "getWeather",
                            "arguments" => "{\"city\":\"Format1\"}",
                        ),
                        "id" => "tc_fmt1",
                    ),
                ],
            ),
            "content" => Any[
                Dict{String,Any}(
                    "type" => "tool_call",
                    "id" => "tc_fmt2",
                    "name" => "getTime",
                    "arguments" => Dict{String,Any}("city" => "Format2"),
                ),
            ],
        )
        has_toolcalls, tc_list, assistant_msg = _extractToolCalls(response)
        @test has_toolcalls == true
        @test length(tc_list) == 1
        @test tc_list[1].name == "getWeather"
    end

    # ------------------------------------------------------------------ #
    # Edge cases                                                          #
    # ------------------------------------------------------------------ #

    @testset "tool call with null arguments" begin
        response = Dict{String,Any}(
            "message" => Dict{String,Any}(
                "role" => "assistant",
                "tool_calls" => Any[
                    Dict{String,Any}(
                        "type" => "function",
                        "function" => Dict{String,Any}(
                            "name" => "getTime",
                            "arguments" => nothing,
                        ),
                        "id" => "tc_null",
                    ),
                ],
            ),
        )
        has_toolcalls, tc_list, assistant_msg = _extractToolCalls(response)
        @test has_toolcalls == true
        @test length(tc_list) == 1
        @test tc_list[1].name == "getTime"
    end

    @testset "tool call with missing function key" begin
        response = Dict{String,Any}(
            "message" => Dict{String,Any}(
                "role" => "assistant",
                "tool_calls" => Any[
                    Dict{String,Any}(
                        "type" => "function",
                        "id" => "tc_nofunc",
                    ),
                ],
            ),
        )
        has_toolcalls, tc_list, assistant_msg = _extractToolCalls(response)
        @test has_toolcalls == true
        @test length(tc_list) == 1
        @test tc_list[1].name == ""
    end

    @testset "tool call with missing name in function block" begin
        response = Dict{String,Any}(
            "message" => Dict{String,Any}(
                "role" => "assistant",
                "tool_calls" => Any[
                    Dict{String,Any}(
                        "type" => "function",
                        "function" => Dict{String,Any}("arguments" => "{}"),
                        "id" => "tc_noname",
                    ),
                ],
            ),
        )
        has_toolcalls, tc_list, assistant_msg = _extractToolCalls(response)
        @test has_toolcalls == true
        @test length(tc_list) == 1
        @test tc_list[1].name == ""
    end

    @testset "message format with JSON.Object (JSON.parse result)" begin
        json_str = JSON.json(Dict(
            "message" => Dict(
                "role" => "assistant",
                "tool_calls" => [
                    Dict(
                        "type" => "function",
                        "function" => Dict("name" => "getWeather", "arguments" => "{\"city\":\"Test\"}"),
                        "id" => "tc_jsonobj",
                    ),
                ],
            ),
        ))
        parsed = JSON.parse(json_str)
        has_toolcalls, tc_list, assistant_msg = _extractToolCalls(parsed)
        @test has_toolcalls == true
        @test length(tc_list) == 1
        @test tc_list[1].name == "getWeather"
        @test tc_list[1].arguments["city"] == "Test"
    end

    @testset "tool_calls block with mixed content types (text + tool_calls)" begin
        response = Dict{String,Any}(
            "content" => Any[
                Dict{String,Any}("type" => "text", "text" => "I'll check both."),
                Dict{String,Any}(
                    "type" => "tool_calls",
                    "tool_calls" => Any[
                        Dict{String,Any}(
                            "type" => "function",
                            "function" => Dict{String,Any}(
                                "name" => "getWeather",
                                "arguments" => "{\"city\":\"London\"}",
                            ),
                            "id" => "tc_mix1",
                        ),
                        Dict{String,Any}(
                            "type" => "function",
                            "function" => Dict{String,Any}(
                                "name" => "getTime",
                                "arguments" => "{\"timezone\":\"UTC\"}",
                            ),
                            "id" => "tc_mix2",
                        ),
                    ],
                ),
            ],
        )
        has_toolcalls, tc_list, assistant_msg = _extractToolCalls(response)
        @test has_toolcalls == true
        @test length(tc_list) == 2
        @test tc_list[1].name == "getWeather"
        @test tc_list[2].name == "getTime"
        @test length(assistant_msg.content) == 1
        @test assistant_msg.content[1].text == "I'll check both."
    end

    @testset "tool_call block without arguments field" begin
        response = Dict{String,Any}(
            "content" => Any[
                Dict{String,Any}(
                    "type" => "tool_call",
                    "id" => "tc_noargs",
                    "name" => "getTime",
                ),
            ],
        )
        has_toolcalls, tc_list, assistant_msg = _extractToolCalls(response)
        @test has_toolcalls == true
        @test length(tc_list) == 1
        @test tc_list[1].arguments == Dict{String,Any}()
    end

    @testset "tool_calls block with empty tool_calls array" begin
        response = Dict{String,Any}(
            "content" => Any[
                Dict{String,Any}(
                    "type" => "tool_calls",
                    "tool_calls" => Any[],
                ),
            ],
        )
        has_toolcalls, tc_list, assistant_msg = _extractToolCalls(response)
        @test has_toolcalls == false
        @test length(tc_list) == 0
    end

    @testset "tool_calls block with non-AbstractDict elements" begin
        response = Dict{String,Any}(
            "content" => Any[
                Dict{String,Any}(
                    "type" => "tool_calls",
                    "tool_calls" => Any["not a dict", 42, nothing],
                ),
            ],
        )
        has_toolcalls, tc_list, assistant_msg = _extractToolCalls(response)
        @test has_toolcalls == false
        @test length(tc_list) == 0
    end

    @testset "content field is not a Vector" begin
        response = Dict{String,Any}(
            "content" => "not a vector",
        )
        has_toolcalls, tc_list, assistant_msg = _extractToolCalls(response)
        @test has_toolcalls == false
        @test length(tc_list) == 0
        @test length(assistant_msg.content) == 0
    end

    @testset "Dict-based response with all metadata fields" begin
        response = Dict{String,Any}(
            "api" => "openai",
            "provider" => "anthropic",
            "model" => "claude-3-sonnet",
            "content" => Any[
                Dict{String,Any}(
                    "type" => "tool_call",
                    "id" => "tc_meta",
                    "name" => "getTime",
                    "arguments" => Dict{String,Any}("city" => "Seoul"),
                ),
            ],
        )
        has_toolcalls, tc_list, assistant_msg = _extractToolCalls(response)
        @test has_toolcalls == true
        @test length(tc_list) == 1
        @test tc_list[1].name == "getTime"
        @test tc_list[1].arguments["city"] == "Seoul"
        @test assistant_msg.api == "openai"
        @test assistant_msg.provider == "anthropic"
        @test assistant_msg.model == "claude-3-sonnet"
    end

    @testset "default role is assistant" begin
        response = Dict{String,Any}(
            "content" => Any[Dict{String,Any}("type" => "text", "text" => "no role specified")],
        )
        has_toolcalls, tc_list, assistant_msg = _extractToolCalls(response)
        @test assistant_msg.role == "assistant"
    end

    @testset "tool call with custom role in message format" begin
        response = Dict{String,Any}(
            "message" => Dict{String,Any}(
                "role" => "custom_role",
                "tool_calls" => Any[
                    Dict{String,Any}(
                        "type" => "function",
                        "function" => Dict{String,Any}(
                            "name" => "getWeather",
                            "arguments" => "{}",
                        ),
                        "id" => "tc_role",
                    ),
                ],
            ),
        )
        has_toolcalls, tc_list, assistant_msg = _extractToolCalls(response)
        @test assistant_msg.role == "custom_role"
    end

    @testset "image content block handling" begin
        response = Dict{String,Any}(
            "content" => Any[
                Dict{String,Any}(
                    "type" => "image_url",
                    "image_url" => Dict("url" => "data:image/png;base64,abc123"),
                ),
                Dict{String,Any}("type" => "text", "text" => "What is this?"),
            ],
        )
        has_toolcalls, tc_list, assistant_msg = _extractToolCalls(response)
        @test has_toolcalls == false
        @test length(assistant_msg.content) == 2
        @test assistant_msg.content[1] isa textContent
        @test assistant_msg.content[1].text == ""
        @test assistant_msg.content[2].text == "What is this?"
    end

end
