// Copyright (C) 2019-2020 Zilliz. All rights reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance
// with the License. You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software distributed under the License
// is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
// or implied. See the License for the specific language governing permissions and limitations under the License

#include <boost/uuid/random_generator.hpp>
#include <boost/uuid/uuid_io.hpp>
#include <fcntl.h>
#include <gtest/gtest.h>
#include <limits.h>
#include <nlohmann/json.hpp>
#include <nlohmann/json_fwd.hpp>
#include <stdio.h>
#include <unistd.h>
#include <cstdint>
#include <cstring>
#include <exception>
#include <initializer_list>
#include <iostream>
#include <map>
#include <memory>
#include <optional>
#include <string>
#include <vector>

#include "common/Common.h"
#include "common/EasyAssert.h"
#include "filemanager/InputStream.h"
#include "gtest/gtest.h"
#include "index/Utils.h"
#include "storage/IndexData.h"
#include "test_utils/Constants.h"

using namespace milvus;
using namespace milvus::index;

// A simple wrapper that removes a temporary file.
struct TmpFileWrapperIndexUtilsTest {
    int fd = -1;
    std::string filename;

    explicit TmpFileWrapperIndexUtilsTest(const std::string& _filename)
        : filename{_filename} {
        fd = open(filename.c_str(),
                  O_RDWR | O_CREAT | O_EXCL,
                  S_IRUSR | S_IWUSR | S_IXUSR);
    }
    TmpFileWrapperIndexUtilsTest(const TmpFileWrapperIndexUtilsTest&) = delete;
    TmpFileWrapperIndexUtilsTest(TmpFileWrapperIndexUtilsTest&&) = delete;
    TmpFileWrapperIndexUtilsTest&
    operator=(const TmpFileWrapperIndexUtilsTest&) = delete;
    TmpFileWrapperIndexUtilsTest&
    operator=(TmpFileWrapperIndexUtilsTest&&) = delete;
    ~TmpFileWrapperIndexUtilsTest() {
        if (fd != -1) {
            close(fd);
            remove(filename.c_str());
        }
    }
};

TEST(UtilIndex, ReadFromFD) {
    auto uuid = boost::uuids::random_generator()();
    auto uuid_string = boost::uuids::to_string(uuid);
    auto file = TestLocalPath + uuid_string;

    auto tmp_file = TmpFileWrapperIndexUtilsTest(file);
    ASSERT_NE(tmp_file.fd, -1);

    size_t data_size = 100 * 1024 * 1024;  // 100M
    auto index_data = std::shared_ptr<uint8_t[]>(new uint8_t[data_size]);
    auto max_loop = size_t(INT_MAX) / data_size + 1;  // insert data > 2G
    for (int i = 0; i < static_cast<int>(max_loop); ++i) {
        auto size_write = write(tmp_file.fd, index_data.get(), data_size);
        ASSERT_GE(size_write, 0);
    }

    auto read_buf =
        std::shared_ptr<uint8_t[]>(new uint8_t[data_size * max_loop]);
    EXPECT_NO_THROW(milvus::index::ReadDataFromFD(
        tmp_file.fd, read_buf.get(), data_size * max_loop));
}

TEST(UtilIndex, TestGetValueFromConfig) {
    nlohmann::json cfg = nlohmann::json::parse(
        R"({"a" : 100, "b" : true, "c" : "true", "d" : 1.234, "e" : null})");
    auto a_value = GetValueFromConfig<int64_t>(cfg, "a");
    ASSERT_EQ(a_value.value(), 100);

    auto b_value = GetValueFromConfig<bool>(cfg, "b");
    ASSERT_TRUE(b_value.value());

    auto c_value = GetValueFromConfig<bool>(cfg, "c");
    ASSERT_TRUE(c_value.value());

    auto d_value = GetValueFromConfig<double>(cfg, "d");
    ASSERT_NEAR(d_value.value(), 1.234, 0.001);

    try {
        GetValueFromConfig<std::string>(cfg, "d");
    } catch (const std::exception& e) {
        std::cout << e.what() << std::endl;
        ASSERT_EQ(std::string(e.what()).find("config type error for key") !=
                      std::string::npos,
                  true);
    }

    auto e_value = GetValueFromConfig<std::string>(cfg, "e");
    ASSERT_FALSE(e_value.has_value());
}

TEST(UtilIndex, TestGetValueFromConfigWithoutTypeCheck) {
    nlohmann::json cfg = nlohmann::json::parse(
        R"({"a" : 100, "b" : true, "c" : "true", "d" : 1.234, "e" : "1.234", "f" : null})");
    SetDefaultConfigParamTypeCheck(false);
    auto a_value = GetValueFromConfig<int64_t>(cfg, "a");
    ASSERT_EQ(a_value.value(), 100);
    std::cout << "a_value: " << a_value.value() << std::endl;

    auto b_value = GetValueFromConfig<bool>(cfg, "b");
    ASSERT_TRUE(b_value.value());
    std::cout << "b_value: " << b_value.value() << std::endl;
    auto c_value = GetValueFromConfig<bool>(cfg, "c");
    ASSERT_TRUE(c_value.value());
    std::cout << "c_value: " << c_value.value() << std::endl;
    auto d_value = GetValueFromConfig<double>(cfg, "d");
    ASSERT_NEAR(d_value.value(), 1.234, 0.001);
    std::cout << "d_value: " << d_value.value() << std::endl;
    auto e_value = GetValueFromConfig<double>(cfg, "e");
    ASSERT_FALSE(e_value.has_value());
    auto f_value = GetValueFromConfig<bool>(cfg, "f");
    ASSERT_FALSE(f_value.has_value());
}

TEST(UtilIndex, AssembleIndexDataCodecConcatenatesSlices) {
    std::vector<std::string> slices = {"hello", " ", "world"};

    IndexDataCodec codec;
    for (const auto& slice : slices) {
        auto data = std::shared_ptr<uint8_t[]>(new uint8_t[slice.size()]);
        std::memcpy(data.get(), slice.data(), slice.size());
        auto index_data =
            std::make_unique<storage::IndexData>(data.get(), slice.size());
        index_data->SetData(std::move(data));
        codec.size_ += slice.size();
        codec.codecs_.push_back(std::move(index_data));
    }

    auto assembled = AssembleIndexDataCodec(codec);
    ASSERT_EQ(assembled->PayloadSize(), codec.size_);
    EXPECT_EQ(
        std::string(reinterpret_cast<const char*>(assembled->PayloadData()),
                    assembled->PayloadSize()),
        "hello world");
}

TEST(UtilIndex, AssembleIndexDataCodecMovesSingleSlice) {
    std::string slice = "hello";
    auto data = std::shared_ptr<uint8_t[]>(new uint8_t[slice.size()]);
    std::memcpy(data.get(), slice.data(), slice.size());
    auto index_data =
        std::make_unique<storage::IndexData>(data.get(), slice.size());
    index_data->SetData(std::move(data));

    auto* original_codec = index_data.get();
    IndexDataCodec codec;
    codec.size_ = slice.size();
    codec.codecs_.push_back(std::move(index_data));

    auto assembled = AssembleIndexDataCodec(std::move(codec));
    EXPECT_EQ(assembled.get(), original_codec);
    EXPECT_EQ(
        std::string(reinterpret_cast<const char*>(assembled->PayloadData()),
                    assembled->PayloadSize()),
        slice);
}
