#include <gtest/gtest.h>

#include <simpledbus/base/Connection.h>
#include <simpledbus/base/Message.h>

#include <chrono>

using namespace SimpleDBus;

class MessageTest : public ::testing::Test {
  protected:
    void SetUp() override {
        conn = new Connection(DBUS_BUS_SESSION);
        conn->init();
    }

    void TearDown() override {
        conn->uninit();
        delete conn;
        conn = nullptr;
    }

    Connection* conn;
};

TEST_F(MessageTest, SendReceiveBooleanTrue) {
    Message msg = Message::create_method_call("simpledbus.tester.python", "/", "simpledbus.tester.message",
                                              "SendReceiveBoolean");

    msg.append_argument(Holder::create<bool>(true), DBUS_TYPE_BOOLEAN_AS_STRING);

    Message reply = conn->send_with_reply_and_block(msg);
    Holder h_reply = reply.extract();

    EXPECT_EQ(h_reply.type(), Holder::Type::BOOLEAN);
    EXPECT_EQ(h_reply.get<bool>(), true);
}

TEST_F(MessageTest, SendReceiveBooleanFalse) {
    Message msg = Message::create_method_call("simpledbus.tester.python", "/", "simpledbus.tester.message",
                                              "SendReceiveBoolean");

    msg.append_argument(Holder::create<bool>(false), DBUS_TYPE_BOOLEAN_AS_STRING);

    Message reply = conn->send_with_reply_and_block(msg);
    Holder h_reply = reply.extract();

    EXPECT_EQ(h_reply.type(), Holder::Type::BOOLEAN);
    EXPECT_EQ(h_reply.get<bool>(), false);
}

TEST_F(MessageTest, SendReceiveByte) {
    Message msg = Message::create_method_call("simpledbus.tester.python", "/", "simpledbus.tester.message",
                                              "SendReceiveByte");

    uint8_t value = 0x42;
    msg.append_argument(Holder::create<uint8_t>(value), DBUS_TYPE_BYTE_AS_STRING);

    Message reply = conn->send_with_reply_and_block(msg);
    Holder h_reply = reply.extract();

    EXPECT_EQ(h_reply.type(), Holder::Type::BYTE);
    EXPECT_EQ(h_reply.get<uint8_t>(), value);
}

TEST_F(MessageTest, SendReceiveInt16) {
    Message msg = Message::create_method_call("simpledbus.tester.python", "/", "simpledbus.tester.message",
                                              "SendReceiveInt16");

    int16_t value = 0x1234;
    msg.append_argument(Holder::create<int16_t>(value), DBUS_TYPE_INT16_AS_STRING);

    Message reply = conn->send_with_reply_and_block(msg);
    Holder h_reply = reply.extract();

    EXPECT_EQ(h_reply.type(), Holder::Type::INT16);
    EXPECT_EQ(h_reply.get<int16_t>(), value);
}

TEST_F(MessageTest, SendReceiveUint16) {
    Message msg = Message::create_method_call("simpledbus.tester.python", "/", "simpledbus.tester.message",
                                              "SendReceiveUint16");

    uint16_t value = 0x1234;
    msg.append_argument(Holder::create<uint16_t>(value), DBUS_TYPE_UINT16_AS_STRING);

    Message reply = conn->send_with_reply_and_block(msg);
    Holder h_reply = reply.extract();

    EXPECT_EQ(h_reply.type(), Holder::Type::UINT16);
    EXPECT_EQ(h_reply.get<uint16_t>(), value);
}

TEST_F(MessageTest, SendReceiveInt32) {
    Message msg = Message::create_method_call("simpledbus.tester.python", "/", "simpledbus.tester.message",
                                              "SendReceiveInt32");

    int32_t value = 0x12345678;
    msg.append_argument(Holder::create<int32_t>(value), DBUS_TYPE_INT32_AS_STRING);

    Message reply = conn->send_with_reply_and_block(msg);
    Holder h_reply = reply.extract();

    EXPECT_EQ(h_reply.type(), Holder::Type::INT32);
    EXPECT_EQ(h_reply.get<int32_t>(), value);
}

TEST_F(MessageTest, SendReceiveUint32) {
    Message msg = Message::create_method_call("simpledbus.tester.python", "/", "simpledbus.tester.message",
                                              "SendReceiveUint32");

    uint32_t value = 0x12345678;
    msg.append_argument(Holder::create<uint32_t>(value), DBUS_TYPE_UINT32_AS_STRING);

    Message reply = conn->send_with_reply_and_block(msg);
    Holder h_reply = reply.extract();

    EXPECT_EQ(h_reply.type(), Holder::Type::UINT32);
    EXPECT_EQ(h_reply.get<uint32_t>(), value);
}

TEST_F(MessageTest, SendReceiveInt64) {
    Message msg = Message::create_method_call("simpledbus.tester.python", "/", "simpledbus.tester.message",
                                              "SendReceiveInt64");

    int64_t value = 0x1234567812345678;
    msg.append_argument(Holder::create<int64_t>(value), DBUS_TYPE_INT64_AS_STRING);

    Message reply = conn->send_with_reply_and_block(msg);
    Holder h_reply = reply.extract();

    EXPECT_EQ(h_reply.type(), Holder::Type::INT64);
    EXPECT_EQ(h_reply.get<int64_t>(), value);
}

TEST_F(MessageTest, SendReceiveUint64) {
    Message msg = Message::create_method_call("simpledbus.tester.python", "/", "simpledbus.tester.message",
                                              "SendReceiveUint64");

    uint64_t value = 0x1234567812345678;
    msg.append_argument(Holder::create<uint64_t>(value), DBUS_TYPE_UINT64_AS_STRING);

    Message reply = conn->send_with_reply_and_block(msg);
    Holder h_reply = reply.extract();

    EXPECT_EQ(h_reply.type(), Holder::Type::UINT64);
    EXPECT_EQ(h_reply.get<uint64_t>(), value);
}

TEST_F(MessageTest, SendReceiveArrayInt32) {
    Message msg = Message::create_method_call("simpledbus.tester.python", "/", "simpledbus.tester.message",
                                              "SendReceiveArrayInt32");

    Holder h_msg = Holder::create<std::vector<Holder>>();
    h_msg.array_append(Holder::create<int32_t>(0x12345678));
    h_msg.array_append(Holder::create<int32_t>(0x87654321));
    msg.append_argument(h_msg, "ai");

    Message reply = conn->send_with_reply_and_block(msg);
    Holder h_reply = reply.extract();

    EXPECT_EQ(h_reply.type(), Holder::Type::ARRAY);

    std::vector<Holder> array = h_reply.get<std::vector<Holder>>();

    ASSERT_EQ(array.size(), 2);

    EXPECT_EQ(array[0].type(), Holder::Type::INT32);
    EXPECT_EQ(array[0].get<int32_t>(), 0x12345678);

    EXPECT_EQ(array[1].type(), Holder::Type::INT32);
    EXPECT_EQ(array[1].get<int32_t>(), 0x87654321);
}

TEST_F(MessageTest, SendReceiveArrayString) {
    Message msg = Message::create_method_call("simpledbus.tester.python", "/", "simpledbus.tester.message",
                                              "SendReceiveArrayString");

    Holder h_msg = Holder::create<std::vector<Holder>>();
    h_msg.array_append(Holder::create<std::string>("Hello"));
    h_msg.array_append(Holder::create<std::string>("World"));
    msg.append_argument(h_msg, "as");

    Message reply = conn->send_with_reply_and_block(msg);
    Holder h_reply = reply.extract();

    EXPECT_EQ(h_reply.type(), Holder::Type::ARRAY);

    std::vector<Holder> array = h_reply.get<std::vector<Holder>>();

    ASSERT_EQ(array.size(), 2);

    EXPECT_EQ(array[0].type(), Holder::Type::STRING);
    EXPECT_EQ(array[0].get<std::string>(), "Hello");

    EXPECT_EQ(array[1].type(), Holder::Type::STRING);
    EXPECT_EQ(array[1].get<std::string>(), "World");
}

TEST_F(MessageTest, SendReceiveDictInt32) {
    Message msg = Message::create_method_call("simpledbus.tester.python", "/", "simpledbus.tester.message",
                                              "SendReceiveDictInt32");

    Holder h_msg = Holder::create<std::map<std::string, Holder>>();
    h_msg.dict_append(Holder::INT32, static_cast<int32_t>(0x12345678), Holder::create<int32_t>(0x87654321));
    h_msg.dict_append(Holder::INT32, static_cast<int32_t>(0x87654321), Holder::create<std::string>("Hello"));
    msg.append_argument(h_msg, "a{iv}");

    Message reply = conn->send_with_reply_and_block(msg);
    Holder h_reply = reply.extract();

    EXPECT_EQ(h_reply.type(), Holder::Type::DICT);

    std::map<int32_t, Holder> dict = h_reply.get<std::map<int32_t, Holder>>();

    ASSERT_EQ(dict.size(), 2);
    ASSERT_EQ(dict.count(0x12345678), 1);
    ASSERT_EQ(dict.count(0x87654321), 1);

    EXPECT_EQ(dict[0x12345678].type(), Holder::Type::INT32);
    EXPECT_EQ(dict[0x12345678].get<int32_t>(), 0x87654321);

    EXPECT_EQ(dict[0x87654321].type(), Holder::Type::STRING);
    EXPECT_EQ(dict[0x87654321].get<std::string>(), "Hello");
}

TEST_F(MessageTest, SendReceiveDictString) {
    Message msg = Message::create_method_call("simpledbus.tester.python", "/", "simpledbus.tester.message",
                                              "SendReceiveDictString");

    Holder h_msg = Holder::create<std::map<std::string, Holder>>();
    h_msg.dict_append(Holder::STRING, "key1", Holder::create<int32_t>(0x87654321));
    h_msg.dict_append(Holder::STRING, "key2", Holder::create<std::string>("Hello"));
    msg.append_argument(h_msg, "a{sv}");

    Message reply = conn->send_with_reply_and_block(msg);
    Holder h_reply = reply.extract();

    EXPECT_EQ(h_reply.type(), Holder::Type::DICT);

    std::map<std::string, Holder> dict = h_reply.get<std::map<std::string, Holder>>();

    ASSERT_EQ(dict.size(), 2);
    ASSERT_EQ(dict.count("key1"), 1);
    ASSERT_EQ(dict.count("key2"), 1);

    EXPECT_EQ(dict["key1"].type(), Holder::Type::INT32);
    EXPECT_EQ(dict["key1"].get<int32_t>(), 0x87654321);

    EXPECT_EQ(dict["key2"].type(), Holder::Type::STRING);
    EXPECT_EQ(dict["key2"].get<std::string>(), "Hello");
}

/*
TODO: Uncomment these tests when we have a way to test the message forwarding logic.

TEST_F(MessageTest, ReceiveMethodCallSuccess) {
    Message msg = Message::create_method_call("simpledbus.tester.python", "/", "simpledbus.tester.message",
                                              "TriggerMethodCall");

    msg.append_argument(Holder::create_string(conn->unique_name()), DBUS_TYPE_STRING_AS_STRING);
    msg.append_argument(Holder::create_string("/my/custom/path"), DBUS_TYPE_STRING_AS_STRING);
    msg.append_argument(Holder::create_string("my.interface"), DBUS_TYPE_STRING_AS_STRING);
    msg.append_argument(Holder::create_string("MyMethod"), DBUS_TYPE_STRING_AS_STRING);
    msg.append_argument(Holder::create_string("Hello World"), DBUS_TYPE_STRING_AS_STRING);
    conn->send_with_reply_and_block(msg);

    // Wait for the method call to be received
    bool method_called = false;
    std::chrono::time_point<std::chrono::steady_clock> end = std::chrono::steady_clock::now() + std::chrono::seconds(1);
    while (std::chrono::steady_clock::now() < end) {
        conn->read_write();
        auto method_call = conn->pop_message();
        if (method_call.is_valid()) {
            EXPECT_EQ(method_call.get_path(), "/my/custom/path");
            EXPECT_EQ(method_call.get_interface(), "my.interface");
            EXPECT_EQ(method_call.get_member(), "MyMethod");

            Holder arguments = method_call.extract();
            EXPECT_EQ(arguments.type(), Holder::Type::STRING);
            EXPECT_EQ(arguments.get_string(), "Hello World");

            Message reply = Message::create_method_return(method_call);
            reply.append_argument(Holder::create_string("Nice to meet you"), DBUS_TYPE_STRING_AS_STRING);
            conn->send(reply);
            method_called = true;
            break;
        }
    }
    EXPECT_TRUE(method_called);
}

TEST_F(MessageTest, ReceiveMethodCallFailure) {
    Message msg = Message::create_method_call("simpledbus.tester.python", "/", "simpledbus.tester.message",
                                              "TriggerMethodCall");

    msg.append_argument(Holder::create_string(conn->unique_name()), DBUS_TYPE_STRING_AS_STRING);
    msg.append_argument(Holder::create_string("/my/custom/path"), DBUS_TYPE_STRING_AS_STRING);
    msg.append_argument(Holder::create_string("my.interface"), DBUS_TYPE_STRING_AS_STRING);
    msg.append_argument(Holder::create_string("MyMethod"), DBUS_TYPE_STRING_AS_STRING);
    msg.append_argument(Holder::create_string("Hello World"), DBUS_TYPE_STRING_AS_STRING);
    conn->send_with_reply_and_block(msg);

    // Wait for the method call to be received
    bool method_called = false;
    std::chrono::time_point<std::chrono::steady_clock> end = std::chrono::steady_clock::now() + std::chrono::seconds(1);
    while (std::chrono::steady_clock::now() < end) {
        conn->read_write();
        auto method_call = conn->pop_message();
        if (method_call.is_valid()) {
            EXPECT_EQ(method_call.get_path(), "/my/custom/path");
            EXPECT_EQ(method_call.get_interface(), "my.interface");
            EXPECT_EQ(method_call.get_member(), "MyMethod");

            Holder arguments = method_call.extract();
            EXPECT_EQ(arguments.type(), Holder::Type::STRING);
            EXPECT_EQ(arguments.get_string(), "Hello World");

            Message reply = Message::create_error(method_call, "simpledbus.error.unknown", "Unknown error");
            conn->send(reply);
            method_called = true;
            break;
        }
    }
    EXPECT_TRUE(method_called);
}
*/

TEST_F(MessageTest, CopyConstructor) {
    // Create an original message
    Message original = Message::create_method_call("org.example.Bus", "/org/example/Path", "org.example.Interface",
                                                   "ExampleMethod");
    original.append_argument(Holder::create<std::string>("Test argument"), "s");

    // Test copy constructor
    Message copy_constructed(original);
    EXPECT_TRUE(copy_constructed.is_valid());
    EXPECT_NE(copy_constructed.get_unique_id(), original.get_unique_id());
    EXPECT_EQ(copy_constructed.get_path(), original.get_path());
    EXPECT_EQ(copy_constructed.get_interface(), original.get_interface());
    EXPECT_EQ(copy_constructed.get_member(), original.get_member());

    // Verify that the original message is still valid and unchanged
    EXPECT_TRUE(original.is_valid());
    EXPECT_EQ(original.get_path(), "/org/example/Path");
    EXPECT_EQ(original.get_interface(), "org.example.Interface");
    EXPECT_EQ(original.get_member(), "ExampleMethod");
}

TEST_F(MessageTest, CopyAssignment) {
    // Create an original message
    Message original = Message::create_method_call("org.example.Bus", "/org/example/Path", "org.example.Interface",
                                                   "ExampleMethod");
    original.append_argument(Holder::create<std::string>("Test argument"), "s");

    // Test copy assignment
    Message copy_assigned;
    copy_assigned = original;
    EXPECT_TRUE(copy_assigned.is_valid());
    EXPECT_NE(copy_assigned.get_unique_id(), original.get_unique_id());
    EXPECT_EQ(copy_assigned.get_path(), original.get_path());
    EXPECT_EQ(copy_assigned.get_interface(), original.get_interface());
    EXPECT_EQ(copy_assigned.get_member(), original.get_member());

    // Verify that the original message is still valid and unchanged
    EXPECT_TRUE(original.is_valid());
    EXPECT_EQ(original.get_path(), "/org/example/Path");
    EXPECT_EQ(original.get_interface(), "org.example.Interface");
    EXPECT_EQ(original.get_member(), "ExampleMethod");
}

TEST_F(MessageTest, MoveConstructor) {
    // Create an original message
    Message original = Message::create_method_call("org.example.Bus", "/org/example/Path", "org.example.Interface",
                                                   "ExampleMethod");
    original.append_argument(Holder::create<std::string>("Test argument"), "s");

    // Test move constructor
    Message move_constructed(std::move(original));
    EXPECT_TRUE(move_constructed.is_valid());
    EXPECT_FALSE(original.is_valid());  // Original should be invalidated
    EXPECT_EQ(move_constructed.get_path(), "/org/example/Path");
    EXPECT_EQ(move_constructed.get_interface(), "org.example.Interface");
    EXPECT_EQ(move_constructed.get_member(), "ExampleMethod");
}

TEST_F(MessageTest, MoveAssignment) {
    // Create an original message
    Message original = Message::create_method_call("org.example.Bus", "/org/example/Path", "org.example.Interface",
                                                   "ExampleMethod");
    original.append_argument(Holder::create<std::string>("Test argument"), "s");

    // Test move assignment
    Message move_assigned;
    move_assigned = std::move(original);
    EXPECT_TRUE(move_assigned.is_valid());
    EXPECT_FALSE(original.is_valid());  // Original should be invalidated
    EXPECT_EQ(move_assigned.get_path(), "/org/example/Path");
    EXPECT_EQ(move_assigned.get_interface(), "org.example.Interface");
    EXPECT_EQ(move_assigned.get_member(), "ExampleMethod");
}