/*
 * Licensed to the Apache Software Foundation (ASF) under one
 * or more contributor license agreements.  See the NOTICE file
 * distributed with this work for additional information
 * regarding copyright ownership.  The ASF licenses this file
 * to you 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.
 */

//  Copyright (c) 2011-present, Facebook, Inc.  All rights reserved.
//  This source code is licensed under both the GPLv2 (found in the
//  COPYING file in the root directory) and Apache 2.0 License
//  (found in the LICENSE.Apache file in the root directory).
//
// Copyright (c) 2011 The LevelDB Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file. See the AUTHORS file for names of contributors.

// Assert utilities is adapted from RocksDB
// https://github.com/facebook/rocksdb/blob/main/test_util/testharness.cc

// Copyright 2024 Google LLC.
//
// 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.

// UniqueTestDirectory utility is adapted from LiteRT
// https://github.com/google-ai-edge/LiteRT/blob/main/litert/test/common.cc

#include "paimon/testing/utils/testharness.h"

#include <unistd.h>

#include <algorithm>
#include <cassert>
#include <cstdlib>
#include <exception>
#include <fstream>
#include <iostream>
#include <random>
#include <vector>

#include "paimon/common/utils/string_utils.h"
#include "paimon/common/utils/uuid.h"
#include "paimon/fs/file_system.h"
#include "paimon/fs/file_system_factory.h"
#include "paimon/status.h"

namespace paimon::test {

std::string GetDataDir() {
    const auto result = std::getenv("PAIMON_TEST_DATA");
    if (!result || !result[0]) {
        return "test/test_data/";
    }
    return std::string(result);
}

std::map<std::string, std::string> GetJindoTestOptions() {
    const char* home = std::getenv("HOME");
    std::string home_str = "";
    if (home) {
        home_str = std::string(home);
    }
    std::string config_file_path = home_str + "/.osscredentials";
    std::ifstream config_file(config_file_path);
    std::string access_key_id = "";
    std::string access_key_secret = "";
    if (config_file.is_open()) {
        std::string line;
        while (std::getline(config_file, line)) {
            std::vector<std::string> key_value = StringUtils::Split(line, "=");
            if (key_value.size() != 2) {
                continue;
            }
            if (key_value[0].find("accessid") != std::string::npos) {
                StringUtils::Trim(&key_value[1]);
                access_key_id = key_value[1];
            }
            if (key_value[0].find("accesskey") != std::string::npos) {
                StringUtils::Trim(&key_value[1]);
                access_key_secret = key_value[1];
            }
        }
        config_file.close();
    }
    std::map<std::string, std::string> options = {
        {"fs.oss.bucket.paimon-unittest.endpoint", "oss-cn-hangzhou-zmf.aliyuncs.com"},
        {"fs.oss.bucket.paimon-unittest.accessKeyId", access_key_id},
        {"fs.oss.bucket.paimon-unittest.accessKeySecret", access_key_secret},
        {"fs.oss.user", "paimon"},
        {"enable-object-store-catalog-in-inte-test", ""},
        {"enable-object-store-commit-in-inte-test", ""},
    };
    return options;
}
std::string GetJindoTestDir() {
    static const std::string dir = "oss://paimon-unittest/temp/";
    return dir;
}

int64_t RandomNumber(int64_t min, int64_t max) {
    static thread_local std::mt19937 generator(
        std::random_device{}());  // NOLINT(whitespace/braces)
    std::uniform_int_distribution<int64_t> distribution(min, max);
    return distribution(generator);
}

std::string GetPidStr() {
    return std::to_string(getpid());
}

::testing::AssertionResult AssertStatus(const char* s_expr, const Status& s) {
    if (s.ok()) {
        return ::testing::AssertionSuccess();
    } else {
        return ::testing::AssertionFailure() << s_expr << std::endl << s.ToString();
    }
}

std::unique_ptr<UniqueTestDirectory> UniqueTestDirectory::Create(const std::string& fs_identifier) {
    static const size_t kMaxTries = 1000;
    std::string tmp_dir = fs_identifier == "jindo"
                              ? GetJindoTestDir()
                              : std::filesystem::temp_directory_path().string() + "/";
    std::map<std::string, std::string> fs_options =
        fs_identifier == "jindo" ? GetJindoTestOptions() : std::map<std::string, std::string>();
    auto fs = FileSystemFactory::Get(fs_identifier, tmp_dir, fs_options);
    if (!fs.ok()) {
        return nullptr;
    }
    for (size_t i = 0; i < kMaxTries; ++i) {
        std::string uuid;
        if (!UUID::Generate(&uuid)) {
            continue;
        }
        std::string test_dir = tmp_dir + "paimon_test_" + uuid;
        auto is_exist = fs.value()->Exists(test_dir);
        if (!is_exist.ok() || is_exist.value()) {
            continue;
        }
        auto status = fs.value()->Mkdirs(test_dir);
        if (status.ok()) {
            return std::unique_ptr<UniqueTestDirectory>(
                new UniqueTestDirectory(test_dir, std::move(fs).value()));
        }
    }
    return nullptr;
}

UniqueTestDirectory::~UniqueTestDirectory() {
    auto is_exist = fs_->Exists(tmpdir_);
    assert(is_exist.ok());
    if (is_exist.value()) {
        [[maybe_unused]] auto status = fs_->Delete(tmpdir_, /*recursive=*/true);
        assert(status.ok());
    }
    fs_.reset();
}

bool TestUtil::CopyDirectory(const std::filesystem::path& source,
                             const std::filesystem::path& destination) {
    namespace fs = std::filesystem;
    try {
        if (!fs::exists(destination)) {
            fs::create_directories(destination);
        }

        for (const auto& entry : fs::directory_iterator(source)) {
            const auto& source_path = entry.path();
            auto destination_path = destination / source_path.filename();

            if (fs::is_directory(source_path)) {
                CopyDirectory(source_path, destination_path);
            } else if (fs::is_regular_file(source_path)) {
                fs::copy(source_path, destination_path, fs::copy_options::overwrite_existing);
            }
        }
    } catch (const std::exception& e) {
        std::cerr << "Error while copying directory: " << e.what() << std::endl;
        return false;
    }
    return true;
}

}  // namespace paimon::test
