BareGit
#include <agent_smith/message.hpp>
#include <gtest/gtest.h>

namespace agent_smith
{
namespace
{

TEST(MessageTest, SerializesAllMessageKinds)
{
    EXPECT_EQ(
        toOpenAIJson(Message(SystemMessage{"instructions"})),
        nlohmann::json({{"role", "system"}, {"content", "instructions"}}));
    EXPECT_EQ(toOpenAIJson(Message(UserMessage{"hello"})),
              nlohmann::json({{"role", "user"}, {"content", "hello"}}));

    ToolMessage tool{
        .tool_call_id = "call_1",
        .name = "weather",
        .content = "sunny",
        .is_error = true,
    };
    EXPECT_EQ(toOpenAIJson(Message(tool)),
              nlohmann::json({{"role", "tool"},
                              {"tool_call_id", "call_1"},
                              {"name", "weather"},
                              {"content", "sunny"}}));
}

TEST(MessageTest, SerializesAssistantToolCalls)
{
    AssistantMessage assistant{
        .content = "",
        .tool_calls =
            {
                ToolCall{
                    .id = "call_1",
                    .name = "weather",
                    .arguments = {{"city", "Portland"}},
                },
            },
    };

    const nlohmann::json json = toOpenAIJson(Message(assistant));

    EXPECT_EQ(json.at("role"), "assistant");
    EXPECT_EQ(json.at("content"), "");
    ASSERT_TRUE(json.contains("tool_calls"));
    ASSERT_EQ(json.at("tool_calls").size(), 1U);
    EXPECT_EQ(json.at("tool_calls")[0],
              nlohmann::json({{"id", "call_1"},
                              {"type", "function"},
                              {"function",
                               {{"name", "weather"},
                                {"arguments", "{\"city\":\"Portland\"}"}}}}));
}

TEST(MessageTest, ParsesAssistantMessageWithToolCallsAndUsage)
{
    const nlohmann::json json{
        {"role", "assistant"},
        {"content", nullptr},
        {"tool_calls",
         {{{"id", "call_1"},
           {"type", "function"},
           {"function",
            {{"name", "weather"}, {"arguments", "{\"city\":\"Portland\"}"}}}}}},
        {"usage",
         {{"prompt_tokens", 10},
          {"completion_tokens", 4},
          {"total_tokens", 14}}},
    };

    auto parsed = assistantMessageFromOpenAIJson(json);

    ASSERT_TRUE(parsed.has_value());
    const auto* message = std::get_if<AssistantMessage>(&*parsed);
    ASSERT_NE(message, nullptr);
    EXPECT_EQ(message->content, "");
    ASSERT_EQ(message->tool_calls.size(), 1U);
    EXPECT_EQ(message->tool_calls[0].id, "call_1");
    EXPECT_EQ(message->tool_calls[0].name, "weather");
    EXPECT_EQ(message->tool_calls[0].arguments,
              nlohmann::json({{"city", "Portland"}}));
    ASSERT_TRUE(message->usage.has_value());
    EXPECT_EQ(message->usage->prompt, 10U);
    EXPECT_EQ(message->usage->completion, 4U);
    EXPECT_EQ(message->usage->total, 14U);
}

TEST(MessageTest, RejectsMalformedAssistantToolArguments)
{
    const nlohmann::json json{
        {"role", "assistant"},
        {"tool_calls",
         {{{"id", "call_1"},
           {"type", "function"},
           {"function", {{"name", "weather"}, {"arguments", "not JSON"}}}}}},
    };

    auto parsed = assistantMessageFromOpenAIJson(json);

    ASSERT_FALSE(parsed.has_value());
    const ParseError* error = parsed.error().as<ParseError>();
    ASSERT_NE(error, nullptr);
    EXPECT_EQ(error->msg, "expected tool arguments JSON object");
}

TEST(MessageTest, RejectsUnexpectedAssistantContent)
{
    const nlohmann::json json{
        {"role", "assistant"},
        {"content", {"unexpected", "object"}},
    };

    auto parsed = assistantMessageFromOpenAIJson(json);

    ASSERT_FALSE(parsed.has_value());
    EXPECT_NE(parsed.error().as<ParseError>(), nullptr);
}

} // namespace
} // namespace agent_smith