/*
 *  Copyright (C) GridGain Systems. All Rights Reserved.
 *  _________        _____ __________________        _____
 *  __  ____/___________(_)______  /__  ____/______ ____(_)_______
 *  _  / __  __  ___/__  / _  __  / _  / __  _  __ `/__  / __  __ \
 *  / /_/ /  _  /    _  /  / /_/ /  / /_/ /  / /_/ / _  /  _  / / /
 *  \____/   /_/     /_/   \_,__/   \____/   \__,_/  /_/   /_/ /_/
 */

#include "all_fields_type.h"
#include "ignite_runner_suite.h"

#include "tests/test-common/test_utils.h"

#include "ignite/client/ignite_client.h"
#include "ignite/client/ignite_client_configuration.h"

#include <gtest/gtest.h>

#include <chrono>

using namespace ignite;

/**
 * Test key type mapping (@see ignite_runner_suite::TABLE_1).
 */
struct test_key_type {
    test_key_type() = default;

    explicit test_key_type(std::int64_t key)
        : key(key) {}

    std::int64_t key{0};
};

/**
 * Test value type mapping (@see ignite_runner_suite::TABLE_1).
 */
struct test_value_type {
    test_value_type() = default;

    explicit test_value_type(std::string val)
        : val(std::move(val)) {}

    std::string val;
};

namespace ignite {

template<>
ignite_tuple convert_to_tuple(test_key_type &&value) {
    ignite_tuple tuple;

    tuple.set("key", value.key);

    return tuple;
}

template<>
test_key_type convert_from_tuple(ignite_tuple &&value) {
    test_key_type res;

    res.key = value.get<std::int64_t>("key");

    return res;
}

template<>
ignite_tuple convert_to_tuple(test_value_type &&value) {
    ignite_tuple tuple;

    tuple.set("val", value.val);

    return tuple;
}

template<>
test_value_type convert_from_tuple(ignite_tuple &&value) {
    test_value_type res;

    res.val = value.get<std::string>("val");

    return res;
}

} // namespace ignite

/**
 * Test suite.
 */
class key_value_view_test : public ignite_runner_suite {
protected:
    void SetUp() override {
        ignite_client_configuration cfg{get_node_addrs()};
        cfg.set_logger(get_logger());

        m_client = ignite_client::start(cfg, std::chrono::seconds(30));
        auto table = m_client.get_tables().get_table(TABLE_1);

        kv_view = table->get_key_value_view<test_key_type, test_value_type>();
    }

    void TearDown() override {
        std::vector<test_key_type> work_range;
        work_range.reserve(100);
        for (int i = -50; i < 50; ++i)
            work_range.emplace_back(i);

        kv_view.remove_all(nullptr, work_range);
    }

    /** Ignite client. */
    ignite_client m_client;

    /** Key-Value view. */
    key_value_view<test_key_type, test_value_type> kv_view;
};

TEST_F(key_value_view_test, put_get) {
    auto key = test_key_type(1);
    auto val = test_value_type("foo");

    kv_view.put(nullptr, key, val);
    auto res = kv_view.get(nullptr, key);

    ASSERT_TRUE(res.has_value());
    EXPECT_EQ("foo", res->val);
}

TEST_F(key_value_view_test, put_get_async) {
    auto key = test_key_type(1);
    auto val = test_value_type("foo");

    auto all_done = std::make_shared<std::promise<std::optional<test_value_type>>>();

    kv_view.put_async(nullptr, key, val, [&](ignite_result<void> &&res) {
        if (!check_and_set_operation_error(*all_done, res))
            return;

        kv_view.get_async(nullptr, key, [&](auto res) { result_set_promise(*all_done, std::move(res)); });
    });

    auto res = all_done->get_future().get();
    ASSERT_TRUE(res.has_value());
    EXPECT_EQ("foo", res->val);
}

TEST_F(key_value_view_test, put_overrides_value) {
    auto key = test_key_type(1);
    auto val = test_value_type("foo");

    kv_view.put(nullptr, key, val);
    auto res = kv_view.get(nullptr, key);

    ASSERT_TRUE(res.has_value());
    EXPECT_EQ("foo", res->val);

    val.val = "bar";
    kv_view.put(nullptr, key, val);
    res = kv_view.get(nullptr, key);

    ASSERT_TRUE(res.has_value());
    EXPECT_EQ("bar", res->val);
}

TEST_F(key_value_view_test, get_all_empty) {
    auto res = kv_view.get_all(nullptr, {});
    EXPECT_TRUE(res.empty());
}

TEST_F(key_value_view_test, get_all_nonexisting) {
    auto res = kv_view.get_all(nullptr, {test_key_type(-42)});

    ASSERT_FALSE(res.empty());

    EXPECT_EQ(res.size(), 1);
    EXPECT_EQ(res.front(), std::nullopt);
}

TEST_F(key_value_view_test, put_all_empty_no_throw) {
    kv_view.put_all(nullptr, {});
}

TEST_F(key_value_view_test, put_all_get_all) {
    static constexpr std::int64_t records_num = 10;

    std::vector<std::pair<test_key_type, test_value_type>> records;
    records.reserve(records_num);
    for (std::int64_t i = 1; i < 1 + records_num; ++i)
        records.emplace_back(i, "Val" + std::to_string(i));

    std::vector<test_key_type> keys;
    for (std::int64_t i = 9; i < 13; ++i)
        keys.emplace_back(i);

    kv_view.put_all(nullptr, records);
    auto res = kv_view.get_all(nullptr, keys);

    ASSERT_EQ(res.size(), keys.size());

    for (std::size_t i = 0; i < keys.size(); ++i) {
        auto key = keys[i];
        auto val = res[i];

        if (key.key <= records_num) {
            ASSERT_TRUE(val.has_value()) << "Key = " << key.key;
            EXPECT_EQ("Val" + std::to_string(key.key), val->val);
        } else {
            ASSERT_FALSE(val.has_value()) << "Key = " << key.key << ", Res = " << val->val;
        }
    }
}

TEST_F(key_value_view_test, put_all_get_all_async) {
    static constexpr std::int64_t records_num = 10;

    std::vector<std::pair<test_key_type, test_value_type>> records;
    records.reserve(records_num);
    for (std::int64_t i = 1; i < 1 + records_num; ++i)
        records.emplace_back(i, "Val" + std::to_string(i));

    std::vector<test_key_type> keys;
    for (std::int64_t i = 9; i < 13; ++i)
        keys.emplace_back(i);

    auto all_done = std::make_shared<std::promise<std::vector<std::optional<test_value_type>>>>();

    kv_view.put_all_async(nullptr, records, [&](ignite_result<void> &&res) {
        if (!check_and_set_operation_error(*all_done, res))
            return;

        kv_view.get_all_async(nullptr, keys, [&](auto res) { result_set_promise(*all_done, std::move(res)); });
    });

    auto res = all_done->get_future().get();

    ASSERT_EQ(res.size(), keys.size());

    for (std::size_t i = 0; i < keys.size(); ++i) {
        auto key = keys[i];
        auto val = res[i];

        if (key.key <= records_num) {
            ASSERT_TRUE(val.has_value()) << "Key = " << key.key;
            EXPECT_EQ("Val" + std::to_string(key.key), val->val);
        } else {
            ASSERT_FALSE(val.has_value()) << "Key = " << key.key << ", Res = " << val->val;
        }
    }
}

TEST_F(key_value_view_test, get_and_put_new_record) {
    auto key = test_key_type(42);
    auto val = test_value_type("foo");
    auto res = kv_view.get_and_put(nullptr, key, val);

    ASSERT_FALSE(res.has_value());
}

TEST_F(key_value_view_test, get_and_put_existing_record) {
    auto key = test_key_type(42);
    auto val1 = test_value_type("foo");
    auto res = kv_view.get_and_put(nullptr, key, val1);

    ASSERT_FALSE(res.has_value());

    auto val2 = test_value_type("bar");
    res = kv_view.get_and_put(nullptr, key, val2);

    ASSERT_TRUE(res.has_value());
    EXPECT_EQ("foo", res->val);

    res = kv_view.get(nullptr, key);

    ASSERT_TRUE(res.has_value());
    EXPECT_EQ("bar", res->val);
}

TEST_F(key_value_view_test, get_and_put_existing_record_async) {
    auto key = test_key_type(42);
    auto val1 = test_value_type("foo");
    auto val2 = test_value_type("bar");

    auto first = std::make_shared<std::promise<std::optional<test_value_type>>>();

    kv_view.get_and_put_async(nullptr, key, val1, [&](auto res) {
        if (!check_and_set_operation_error(*first, res))
            return;

        if (res.value().has_value())
            first->set_exception(std::make_exception_ptr(ignite_error("Expected nullopt on first insertion")));

        kv_view.get_and_put_async(nullptr, key, val2, [&](auto res) { result_set_promise(*first, std::move(res)); });
    });

    auto res = first->get_future().get();
    ASSERT_TRUE(res.has_value());
    EXPECT_EQ(val1.val, res->val);

    auto second = std::make_shared<std::promise<std::optional<test_value_type>>>();
    kv_view.get_async(nullptr, key, [&](auto res) { result_set_promise(*second, std::move(res)); });

    res = second->get_future().get();
    ASSERT_TRUE(res.has_value());
    EXPECT_EQ(val2.val, res->val);
}

TEST_F(key_value_view_test, insert_new_record) {
    auto key = test_key_type(42);
    auto val = test_value_type("foo");
    auto res = kv_view.put_if_absent(nullptr, key, val);

    ASSERT_TRUE(res);
}

TEST_F(key_value_view_test, put_if_absent_existing_record) {
    auto key = test_key_type(42);
    auto val1 = test_value_type("foo");
    auto put_res = kv_view.put_if_absent(nullptr, key, val1);
    ASSERT_TRUE(put_res);

    auto val2 = test_value_type("bar");
    put_res = kv_view.put_if_absent(nullptr, key, val2);
    ASSERT_FALSE(put_res);

    auto res = kv_view.get(nullptr, key);
    ASSERT_TRUE(res.has_value());
    EXPECT_EQ("foo", res->val);
}

TEST_F(key_value_view_test, put_if_absent_existing_record_async) {
    auto key = test_key_type(42);
    auto val1 = test_value_type("foo");
    auto val2 = test_value_type("bar");

    auto all_done = std::make_shared<std::promise<std::optional<test_value_type>>>();

    kv_view.put_if_absent_async(nullptr, key, val1, [&](ignite_result<bool> &&res) {
        if (!check_and_set_operation_error(*all_done, res))
            return;

        if (!res.value())
            all_done->set_exception(std::make_exception_ptr(ignite_error("Expected true on first insertion")));

        kv_view.put_if_absent_async(nullptr, key, val2, [&](ignite_result<bool> &&res) {
            if (!check_and_set_operation_error(*all_done, res))
                return;

            if (res.value())
                all_done->set_exception(std::make_exception_ptr(ignite_error("Expected false on second insertion")));

            kv_view.get_async(nullptr, key, [&](auto res) { result_set_promise(*all_done, std::move(res)); });
        });
    });

    auto res = all_done->get_future().get();
    ASSERT_TRUE(res.has_value());
    EXPECT_EQ(val1.val, res->val);
}

TEST_F(key_value_view_test, replace_nonexisting) {
    auto key = test_key_type(42);
    auto val = test_value_type("foo");

    auto res1 = kv_view.replace(nullptr, key, val);
    ASSERT_FALSE(res1);

    auto res2 = kv_view.get(nullptr, key);
    ASSERT_FALSE(res2.has_value());
}

TEST_F(key_value_view_test, replace_existing) {
    auto key = test_key_type(42);
    auto val1 = test_value_type("foo");

    auto res = kv_view.put_if_absent(nullptr, key, val1);
    ASSERT_TRUE(res);

    auto val2 = test_value_type("bar");
    res = kv_view.replace(nullptr, key, val2);
    ASSERT_TRUE(res);

    auto res2 = kv_view.get(nullptr, key);
    ASSERT_TRUE(res2.has_value());
    EXPECT_EQ("bar", res2->val);
}

TEST_F(key_value_view_test, replace_existing_async) {
    auto key = test_key_type(42);
    auto val1 = test_value_type("foo");
    auto val2 = test_value_type("bar");

    auto all_done = std::make_shared<std::promise<std::optional<test_value_type>>>();

    kv_view.put_if_absent_async(nullptr, key, val1, [&](ignite_result<bool> &&res) {
        if (!check_and_set_operation_error(*all_done, res))
            return;

        if (!res.value())
            all_done->set_exception(std::make_exception_ptr(ignite_error("Expected true on insertion")));

        kv_view.replace_async(nullptr, key, val2, [&](ignite_result<bool> &&res) {
            if (!check_and_set_operation_error(*all_done, res))
                return;

            if (!res.value())
                all_done->set_exception(std::make_exception_ptr(ignite_error("Expected true on replace")));

            kv_view.get_async(nullptr, key, [&](auto res) { result_set_promise(*all_done, std::move(res)); });
        });
    });

    auto res = all_done->get_future().get();
    ASSERT_TRUE(res.has_value());
    EXPECT_EQ(val2.val, res->val);
}

TEST_F(key_value_view_test, replace_exact_nonexisting) {
    auto res1 = kv_view.replace(nullptr, test_key_type(42), test_value_type("foo"), test_value_type("bar"));
    ASSERT_FALSE(res1);

    auto res2 = kv_view.get(nullptr, test_key_type(42));
    ASSERT_FALSE(res2.has_value());
}

TEST_F(key_value_view_test, replace_exact_existing_wrong) {
    auto res1 = kv_view.put_if_absent(nullptr, test_key_type(42), test_value_type("foo"));
    ASSERT_TRUE(res1);

    res1 = kv_view.replace(nullptr, test_key_type(42), test_value_type("bar"), test_value_type("baz"));
    ASSERT_FALSE(res1);

    auto res2 = kv_view.get(nullptr, test_key_type(42));
    ASSERT_TRUE(res2.has_value());
    EXPECT_EQ("foo", res2->val);
}

TEST_F(key_value_view_test, replace_exact_existing_right) {
    auto res1 = kv_view.put_if_absent(nullptr, test_key_type(42), test_value_type("foo"));
    ASSERT_TRUE(res1);

    res1 = kv_view.replace(nullptr, test_key_type(42), test_value_type("foo"), test_value_type("baz"));
    ASSERT_TRUE(res1);

    auto res2 = kv_view.get(nullptr, test_key_type(42));
    ASSERT_TRUE(res2.has_value());
    EXPECT_EQ("baz", res2->val);
}

TEST_F(key_value_view_test, replace_exact_existing_right_async) {
    auto key = test_key_type(42);
    auto val1 = test_value_type("foo");
    auto val2 = test_value_type("bar");

    auto all_done = std::make_shared<std::promise<std::optional<test_value_type>>>();

    kv_view.put_if_absent_async(nullptr, key, val1, [&](ignite_result<bool> &&res) {
        if (!check_and_set_operation_error(*all_done, res))
            return;

        if (!res.value())
            all_done->set_exception(std::make_exception_ptr(ignite_error("Expected true on insertion")));

        kv_view.replace_async(nullptr, key, val1, val2, [&](ignite_result<bool> &&res) {
            if (!check_and_set_operation_error(*all_done, res))
                return;

            if (!res.value())
                all_done->set_exception(std::make_exception_ptr(ignite_error("Expected true on replace")));

            kv_view.get_async(nullptr, key, [&](auto res) { result_set_promise(*all_done, std::move(res)); });
        });
    });

    auto res = all_done->get_future().get();
    ASSERT_TRUE(res.has_value());
    EXPECT_EQ(val2.val, res->val);
}

TEST_F(key_value_view_test, get_and_replace_nonexisting) {
    auto key = test_key_type(42);
    auto val = test_value_type("foo");
    auto res1 = kv_view.get_and_replace(nullptr, key, val);
    ASSERT_FALSE(res1.has_value());

    auto res2 = kv_view.get(nullptr, key);
    ASSERT_FALSE(res2.has_value());
}

TEST_F(key_value_view_test, get_and_replace_existing) {
    auto key = test_key_type(42);
    auto val1 = test_value_type("foo");
    auto res1 = kv_view.put_if_absent(nullptr, key, val1);
    ASSERT_TRUE(res1);

    auto val2 = test_value_type("bar");
    auto res2 = kv_view.get_and_replace(nullptr, key, val2);

    ASSERT_TRUE(res2.has_value());
    EXPECT_EQ("foo", res2->val);

    res2 = kv_view.get(nullptr, test_key_type(42));
    ASSERT_TRUE(res2.has_value());
    EXPECT_EQ("bar", res2->val);
}

TEST_F(key_value_view_test, get_and_replace_existing_async) {
    auto key = test_key_type(42);
    auto val1 = test_value_type("foo");
    auto val2 = test_value_type("bar");

    auto all_done = std::make_shared<std::promise<std::optional<test_value_type>>>();

    kv_view.put_if_absent_async(nullptr, key, val1, [&](ignite_result<bool> &&res) {
        if (!check_and_set_operation_error(*all_done, res))
            return;

        if (!res.value())
            all_done->set_exception(std::make_exception_ptr(ignite_error("Expected true on insertion")));

        kv_view.get_and_replace_async(
            nullptr, key, val2, [&](auto res) { result_set_promise(*all_done, std::move(res)); });
    });

    auto res = all_done->get_future().get();
    ASSERT_TRUE(res.has_value());
    EXPECT_EQ(val1.val, res->val);
}

TEST_F(key_value_view_test, remove_nonexisting) {
    auto res1 = kv_view.remove(nullptr, test_key_type(1));
    ASSERT_FALSE(res1);

    auto res2 = kv_view.get(nullptr, test_key_type(1));
    ASSERT_FALSE(res2.has_value());
}

TEST_F(key_value_view_test, remove_existing) {
    auto res = kv_view.put_if_absent(nullptr, test_key_type(1), test_value_type("foo"));
    ASSERT_TRUE(res);

    res = kv_view.remove(nullptr, test_key_type(1));
    ASSERT_TRUE(res);

    auto res2 = kv_view.get(nullptr, test_key_type(1));
    ASSERT_FALSE(res2.has_value());
}

TEST_F(key_value_view_test, remove_existing_async) {
    auto all_done = std::make_shared<std::promise<bool>>();

    kv_view.put_if_absent_async(nullptr, test_key_type(42), test_value_type("foo"), [&](ignite_result<bool> &&res) {
        if (!check_and_set_operation_error(*all_done, res))
            return;

        if (!res.value())
            all_done->set_exception(std::make_exception_ptr(ignite_error("Expected true on insertion")));

        kv_view.remove_async(
            nullptr, test_key_type(42), [&](auto res) { result_set_promise(*all_done, std::move(res)); });
    });

    auto res = all_done->get_future().get();
    ASSERT_TRUE(res);
}

TEST_F(key_value_view_test, remove_exact_nonexisting) {
    auto res = kv_view.remove(nullptr, test_key_type(1), test_value_type("foo"));
    ASSERT_FALSE(res);
}

TEST_F(key_value_view_test, remove_exact_existing) {
    auto res = kv_view.put_if_absent(nullptr, test_key_type(1), test_value_type("foo"));
    ASSERT_TRUE(res);

    res = kv_view.remove(nullptr, test_key_type(1), test_value_type("bar"));
    ASSERT_FALSE(res);

    res = kv_view.remove(nullptr, test_key_type(1), test_value_type("foo"));
    ASSERT_TRUE(res);

    auto res2 = kv_view.get(nullptr, test_key_type(1));
    ASSERT_FALSE(res2.has_value());
}

TEST_F(key_value_view_test, remove_exact_existing_async) {
    auto key = test_key_type(42);
    auto val = test_value_type("foo");

    auto all_done = std::make_shared<std::promise<bool>>();

    auto res = kv_view.put_if_absent(nullptr, key, val);
    ASSERT_TRUE(res);

    kv_view.remove_async(nullptr, key, test_value_type("bar"), [&](auto res) {
        if (!check_and_set_operation_error(*all_done, res))
            return;

        if (res.value())
            all_done->set_exception(std::make_exception_ptr(ignite_error("Expected false on second remove")));

        kv_view.remove_async(nullptr, key, val, [&](auto res) { result_set_promise(*all_done, std::move(res)); });
    });

    auto res2 = all_done->get_future().get();
    ASSERT_TRUE(res2);
}

TEST_F(key_value_view_test, get_and_remove_nonexisting) {
    auto res1 = kv_view.get_and_remove(nullptr, test_key_type(42));
    ASSERT_FALSE(res1.has_value());

    auto res2 = kv_view.get(nullptr, test_key_type(42));
    ASSERT_FALSE(res2.has_value());
}

TEST_F(key_value_view_test, get_and_remove_existing) {
    auto res1 = kv_view.put_if_absent(nullptr, test_key_type(42), test_value_type("foo"));
    ASSERT_TRUE(res1);

    auto res2 = kv_view.get_and_remove(nullptr, test_key_type(42));

    ASSERT_TRUE(res2.has_value());
    EXPECT_EQ("foo", res2->val);

    res2 = kv_view.get(nullptr, test_key_type(42));
    ASSERT_FALSE(res2.has_value());
}

TEST_F(key_value_view_test, get_and_remove_existing_async) {
    auto key = test_key_type(42);
    auto val1 = test_value_type("foo");

    auto all_done = std::make_shared<std::promise<std::optional<test_value_type>>>();

    kv_view.put_if_absent_async(nullptr, key, val1, [&](ignite_result<bool> &&res) {
        if (!check_and_set_operation_error(*all_done, res))
            return;

        if (!res.value())
            all_done->set_exception(std::make_exception_ptr(ignite_error("Expected true on insertion")));

        kv_view.get_and_remove_async(nullptr, key, [&](auto res) { result_set_promise(*all_done, std::move(res)); });
    });

    auto res = all_done->get_future().get();
    ASSERT_TRUE(res.has_value());
    EXPECT_EQ(val1.val, res->val);
}

TEST_F(key_value_view_test, contains) {
    auto res = kv_view.contains(nullptr, test_key_type(1));
    ASSERT_FALSE(res);

    res = kv_view.put_if_absent(nullptr, test_key_type(1), test_value_type("foo"));
    ASSERT_TRUE(res);

    res = kv_view.contains(nullptr, test_key_type(1));
    ASSERT_TRUE(res);
}

TEST_F(key_value_view_test, contains_async) {
    auto all_done = std::make_shared<std::promise<bool>>();

    kv_view.contains_async(nullptr, test_key_type(1), [&](ignite_result<bool> &&res) {
        if (!check_and_set_operation_error(*all_done, res))
            return;

        if (res.value())
            all_done->set_exception(std::make_exception_ptr(ignite_error("Expected to not contain")));

        kv_view.put_if_absent_async(nullptr, test_key_type(1), test_value_type("foo"), [&](ignite_result<bool> &&res) {
            if (!check_and_set_operation_error(*all_done, res))
                return;

            if (!res.value())
                all_done->set_exception(std::make_exception_ptr(ignite_error("Expected true on insert")));

            kv_view.contains_async(nullptr, test_key_type(1),
                [&](ignite_result<bool> &&res) { result_set_promise(*all_done, std::move(res)); });
        });
    });

    auto res = all_done->get_future().get();
    ASSERT_TRUE(res);
}

TEST_F(key_value_view_test, remove_all_nonexisting_keys_return_all) {
    std::vector<test_key_type> non_existing = {test_key_type(1), test_key_type(2)};
    auto res = kv_view.remove_all(nullptr, non_existing);

    EXPECT_EQ(res.size(), 2);
}

TEST_F(key_value_view_test, remove_all_only_existing) {
    std::vector<std::pair<test_key_type, test_value_type>> to_insert = {
        {test_key_type(1), test_value_type("foo")}, {test_key_type(2), test_value_type("bar")}};
    kv_view.put_all(nullptr, to_insert);

    auto res = kv_view.remove_all(nullptr, {test_key_type(1), test_key_type(2)});

    EXPECT_TRUE(res.empty());
}

TEST_F(key_value_view_test, remove_all_overlapped) {
    static constexpr std::size_t records_num = 10;

    std::vector<std::pair<test_key_type, test_value_type>> to_insert;
    to_insert.reserve(records_num);
    for (std::int64_t i = 1; i < 1 + std::int64_t(records_num); ++i)
        to_insert.emplace_back(i, "Val" + std::to_string(i));

    kv_view.put_all(nullptr, to_insert);

    std::vector<test_key_type> to_remove;
    for (std::int64_t i = 9; i < 13; ++i)
        to_remove.emplace_back(i);

    auto res = kv_view.remove_all(nullptr, to_remove);

    EXPECT_EQ(res.size(), 2);
}

TEST_F(key_value_view_test, remove_all_empty) {
    auto res = kv_view.remove_all(nullptr, std::vector<test_key_type>{});
    EXPECT_TRUE(res.empty());
}

TEST_F(key_value_view_test, remove_all_exact_nonexisting) {
    auto res = kv_view.remove_all(
        nullptr, {{test_key_type(1), test_value_type("foo")}, {test_key_type(2), test_value_type("bar")}});

    ASSERT_EQ(2, res.size());
}

TEST_F(key_value_view_test, remove_all_exact_overlapped) {
    kv_view.put_all(nullptr, {{test_key_type(1), test_value_type("foo")}, {test_key_type(2), test_value_type("bar")}});

    auto res = kv_view.remove_all(
        nullptr, {{test_key_type(1), test_value_type("baz")}, {test_key_type(2), test_value_type("bar")}});

    EXPECT_EQ(res.size(), 1);
    EXPECT_EQ(1, res.front().key);

    auto tuple2 = kv_view.get(nullptr, test_key_type(2));

    ASSERT_FALSE(tuple2.has_value());
}

TEST_F(key_value_view_test, remove_all_exact_overlapped_async) {
    auto all_done = std::make_shared<std::promise<std::vector<test_key_type>>>();

    kv_view.put_all_async(nullptr,
        {{test_key_type(1), test_value_type("foo")}, {test_key_type(2), test_value_type("bar")}}, [&](auto res) {
            if (!check_and_set_operation_error(*all_done, res))
                return;

            kv_view.remove_all_async(nullptr,
                {{test_key_type(1), test_value_type("baz")}, {test_key_type(2), test_value_type("bar")}},
                [&](auto res) { result_set_promise(*all_done, std::move(res)); });
        });

    auto res = all_done->get_future().get();
    EXPECT_EQ(res.size(), 1);
    EXPECT_EQ(1, res.front().key);
}

TEST_F(key_value_view_test, remove_all_exact_empty) {
    auto res = kv_view.remove_all(nullptr, std::vector<std::pair<test_key_type, test_value_type>>{});
    EXPECT_TRUE(res.empty());
}

TEST_F(key_value_view_test, types_test) {
    auto table = m_client.get_tables().get_table(TABLE_NAME_ALL_COLUMNS);
    auto kv_view = table->get_key_value_view<test_key_type, all_fields_type>();

    all_fields_type inserted;
    inserted.m_key = 42;
    inserted.m_str = "test";
    inserted.m_int8 = 1;
    inserted.m_int16 = 2;
    inserted.m_int32 = 3;
    inserted.m_int64 = 4;
    inserted.m_float = .5f;
    inserted.m_double = .6f;
    inserted.m_uuid = {0x123e4567e89b12d3, 0x7456426614174000};
    inserted.m_date = {2023, 2, 7};
    inserted.m_time = {17, 4, 12, 3543634};
    inserted.m_time2 = {17, 4, 12, 3543634};
    inserted.m_datetime = {{2020, 7, 28}, {2, 15, 52, 6349879}};
    inserted.m_datetime2 = {{2020, 7, 28}, {2, 15, 52, 6349879}};
    inserted.m_timestamp = {3875238472, 248760634};
    inserted.m_timestamp2 = {3875238472, 248760634};
    inserted.m_blob = {std::byte(1), std::byte(2), std::byte(42)};
    inserted.m_decimal = big_decimal{123456789098765};
    inserted.m_boolean = true;

    kv_view.put(nullptr, test_key_type(42), inserted);
    auto res = kv_view.get(nullptr, test_key_type(42));

    ASSERT_TRUE(res.has_value());

    EXPECT_EQ(inserted.m_key, res->m_key);
    EXPECT_EQ(inserted.m_str, res->m_str);
    EXPECT_EQ(inserted.m_int8, res->m_int8);
    EXPECT_EQ(inserted.m_int16, res->m_int16);
    EXPECT_EQ(inserted.m_int32, res->m_int32);
    EXPECT_EQ(inserted.m_int64, res->m_int64);
    EXPECT_EQ(inserted.m_float, res->m_float);
    EXPECT_EQ(inserted.m_double, res->m_double);
    EXPECT_EQ(inserted.m_uuid, res->m_uuid);
    EXPECT_EQ(inserted.m_date, res->m_date);
    EXPECT_EQ(inserted.m_time, res->m_time);
    EXPECT_EQ(inserted.m_datetime, res->m_datetime);
    EXPECT_EQ(inserted.m_timestamp, res->m_timestamp);
    EXPECT_EQ(inserted.m_blob, res->m_blob);
    EXPECT_EQ(inserted.m_decimal, res->m_decimal);
    EXPECT_EQ(inserted.m_boolean, res->m_boolean);

    EXPECT_EQ(ignite_time(17, 4, 12), res->m_time2);
    EXPECT_EQ(ignite_date_time({2020, 7, 28}, {2, 15, 52, 6000000}), res->m_datetime2);
    EXPECT_EQ(ignite_timestamp(3875238472, 248700000), res->m_timestamp2);
}
