/* -------------------------------------------------------------------------
 * This file is part of the MindStudio project.
 * Copyright (c) 2025 Huawei Technologies Co.,Ltd.
 *
 * MindStudio is licensed under Mulan PSL v2.
 * You can use this software according to the terms and conditions of the Mulan PSL v2.
 * You may obtain a copy of Mulan PSL v2 at:
 *
 *          http://license.coscl.org.cn/MulanPSL2
 *
 * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND,
 * EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT,
 * MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE.
 * See the Mulan PSL v2 for more details.
 * -------------------------------------------------------------------------
 */

#include <string>
#include <vector>
#include <iterator>
#include <unistd.h>
#include <gtest/gtest.h>
#include <linux/limits.h>

#include "file.h"
#include "utility/path.h"

using namespace Utility;

TEST(Path, default_construction_expect_current_path)
{
    Path path;
    ASSERT_EQ(path.ToString(), ".");
}

TEST(Path, parse_empty_path_expect_current_path)
{
    Path path("");
    ASSERT_EQ(path.ToString(), ".");
}

TEST(Path, parse_root_path_expect_get_root_path)
{
    Path path("/");
    ASSERT_EQ(path.ToString(), "/");
}

TEST(Path, parse_absolute_dir_path_expect_get_same_dir)
{
    std::string raw = "/usr/local";
    Path path(raw);
    ASSERT_EQ(path.ToString(), raw);
}

TEST(Path, parse_absolute_file_path_expect_get_same_file)
{
    std::string raw = "/usr/local/test.cpp";
    Path path(raw);
    ASSERT_EQ(path.ToString(), raw);
}

TEST(Path, parse_relative_dir_path_expect_get_correct_path)
{
    Path path("./test/");
    ASSERT_EQ(path.ToString(), "test");
}

TEST(Path, parse_relative_file_path_expect_get_correct_path)
{
    Path path("./test/test.cpp");
    ASSERT_EQ(path.ToString(), "test/test.cpp");
}

TEST(Path, parse_relative_dir_path_without_current_operator_expect_get_correct_path)
{
    Path path("test/");
    ASSERT_EQ(path.ToString(), "test");
}

TEST(Path, parse_relative_file_path_without_current_operator_expect_get_correct_path)
{
    Path path("test/test.cpp");
    ASSERT_EQ(path.ToString(), "test/test.cpp");
}

TEST(Path, parse_path_with_current_operator_expect_current_path)
{
    Path path1("test/./test.cpp");
    ASSERT_EQ(path1.ToString(), "test/test.cpp");
    Path path2("/test/./test.cpp");
    ASSERT_EQ(path2.ToString(), "/test/test.cpp");
}

TEST(Path, parse_path_with_parrent_operator_expect_correct_path)
{
    Path path1("test/../test.cpp");
    ASSERT_EQ(path1.ToString(), "test/../test.cpp");
    Path path2("/test/../test.cpp");
    ASSERT_EQ(path2.ToString(), "/test/../test.cpp");
}

TEST(Path, parse_path_with_multiple_seps_expect_ignore_redundant_seps)
{
    Path path("///test///test.cpp");
    ASSERT_EQ(path.ToString(), "/test/test.cpp");
}

TEST(Path, get_basename_of_absolute_dir_path_expect_return_correct_basename)
{
    Path path("/usr/local/");
    ASSERT_EQ(path.Name(), "local");
}

TEST(Path, get_basename_of_relative_dir_path_expect_return_correct_basename)
{
    Path path("local/");
    ASSERT_EQ(path.Name(), "local");
}

TEST(Path, get_basename_of_absolute_file_path_expect_return_correct_basename)
{
    Path path("/usr/local/test.cpp");
    ASSERT_EQ(path.Name(), "test.cpp");
}

TEST(Path, get_basename_of_relative_file_path_expect_return_correct_basename)
{
    Path path("./sample-dir/test.cpp");
    ASSERT_EQ(path.Name(), "test.cpp");
}

TEST(Path, get_basename_of_root_dir_expect_return_empty_basename)
{
    Path path("/");
    ASSERT_EQ(path.Name(), "");
}

TEST(Path, get_basename_of_current_dir_expect_return_empty_basename)
{
    Path path(".");
    ASSERT_EQ(path.Name(), "");
}

TEST(Path, get_parent_of_relative_path_expect_return_correct_parent)
{
    Path path("./sample-dir/test/test.cpp");
    path = path.Parent();
    ASSERT_EQ(path.ToString(), "sample-dir/test");
    path = path.Parent();
    ASSERT_EQ(path.ToString(), "sample-dir");
    path = path.Parent();
    ASSERT_EQ(path.ToString(), ".");
}

TEST(Path, get_parent_of_absolute_path_expect_return_correct_parent)
{
    Path path("/usr/local/test.cpp");
    path = path.Parent();
    ASSERT_EQ(path.ToString(), "/usr/local");
    path = path.Parent();
    ASSERT_EQ(path.ToString(), "/usr");
    path = path.Parent();
    ASSERT_EQ(path.ToString(), "/");
}

TEST(Path, get_parent_of_current_path_expect_return_current_parent)
{
    Path path(".");
    ASSERT_EQ(path.Parent().ToString(), path.ToString());
}

TEST(Path, get_parent_of_root_path_expect_return_root_parent)
{
    Path path("/");
    ASSERT_EQ(path.Parent().ToString(), path.ToString());
}

TEST(Path, get_absolute_path_of_absolute_path_expect_return_original_path)
{
    Path path("/usr/local");
    ASSERT_EQ(path.Absolute().ToString(), path.ToString());
}

TEST(Path, get_absolute_path_of_relative_path_expect_return_correct_path)
{
    Path path("./sample-dir/test/test.cpp");
    char buf[PATH_MAX] = {0};
    if (getcwd(buf, sizeof(buf))) {
        std::string cwd;
        cwd = buf;
        ASSERT_EQ(path.Absolute().ToString(), cwd + "/sample-dir/test/test.cpp");
    }
}

TEST(Path, get_absolute_path_expect_idempotence)
{
    Path path("/usr/local");
    ASSERT_EQ(path.Absolute().ToString(), path.Absolute().Absolute().ToString());
    path = Path("./sample-dir/test/test.cpp");
    ASSERT_EQ(path.Absolute().ToString(), path.Absolute().Absolute().ToString());
}

TEST(Path, get_resolved_path_of_absolute_path_expect_return_correct_path)
{
    Path path("/usr/local");
    ASSERT_EQ(path.Resolved().ToString(), path.ToString());
    path = Path("/usr/../local");
    ASSERT_EQ(path.Resolved().ToString(), "/local");
    path = Path("/usr/../../local");
    ASSERT_EQ(path.Resolved().ToString(), "/local");
}

TEST(Path, get_resolved_path_of_relative_path_expect_return_correct_path)
{
    char buf[PATH_MAX] = {0};
    if (getcwd(buf, sizeof(buf))) {
        std::string cwd;
        cwd = buf;
        Path path("./sample-dir/test.cpp");
        ASSERT_EQ(path.Resolved().ToString(), cwd + "/sample-dir/test.cpp");
        path = Path("./sample-dir/../test.cpp");
        ASSERT_EQ(path.Resolved().ToString(), cwd + "/test.cpp");
        path = Path("./sample-dir/../../test.cpp");
        ASSERT_EQ(path.Resolved().ToString(), Path(cwd).Parent().ToString() + "/test.cpp");
    }
}

TEST(Path, get_resolved_path_expect_idempotence)
{
    Path path("/usr/local");
    ASSERT_EQ(path.Resolved().ToString(), path.Resolved().Resolved().ToString());
    path = Path("./sample-dir/test/test.cpp");
    ASSERT_EQ(path.Resolved().ToString(), path.Resolved().Resolved().ToString());
}

TEST(Path, join_two_empty_pathes_expect_return_current_path_segment)
{
    Path path("");
    Path joined = path / path;
    ASSERT_EQ(joined.ToString(), ".");
}

TEST(Path, join_empty_path_with_another_path_expect_return_another_path)
{
    Path path1("");
    Path path2("/usr/local");
    Path path3("./test");
    Path joined = path1 / path2;
    ASSERT_EQ(joined.ToString(), path2.ToString());
    joined = path2 / path1;
    ASSERT_EQ(joined.ToString(), path2.ToString());
    joined = path1 / path3;
    ASSERT_EQ(joined.ToString(), path3.ToString());
    joined = path3 / path1;
    ASSERT_EQ(joined.ToString(), path3.ToString());
}

TEST(Path, join_several_pathed_expect_return_correct_path)
{
    Path path1("/usr/local");
    Path path2("test");
    Path path3("./test.cpp");
    Path joined = path1 / path2 / path3;
    ASSERT_EQ(joined.ToString(), "/usr/local/test/test.cpp");
}

TEST(Path, join_with_absolute_path_expect_return_only_absolute_path)
{
    Path path1("./test");
    Path path2("/usr/local");
    Path joined = path1 / path2;
    ASSERT_EQ(joined.ToString(), path2.ToString());
}

TEST(Path, check_exist_file_exists_expect_return_true)
{
    Path path("/bin/ls");
    ASSERT_TRUE(path.Exists());
}

TEST(Path, check_exist_dir_exists_expect_return_true)
{
    Path path("/bin/");
    ASSERT_TRUE(path.Exists());
}

TEST(Path, check_not_exist_path_exists_expect_return_false)
{
    Path path("./not_exist");
    ASSERT_FALSE(path.Exists());
}

TEST(Path, check_valid_length)
{
    Path path("./valid_length");
    ASSERT_TRUE(path.IsValidLength());
}

TEST(Path, check_invalid_length)
{
    std::string invalidName(256, 'A');
    Path pathWithInvalidName("./" + invalidName);
    ASSERT_FALSE(pathWithInvalidName.IsValidLength());

    std::string validName(128, 'A');
    std::string invalidPath;
    for (int i = 0; i < 32; i++) {
        invalidPath += "/" + invalidName;
    }
    Path pathWithInvalidPath(invalidName);
    ASSERT_FALSE(pathWithInvalidPath.IsValidLength());
}

TEST(Path, check_soft_link_expect_return_true)
{
    EXPECT_EQ(0, symlink("test_file", "test_file_soft_link"));
    Path pathStr = Utility::Path("test_file");
    EXPECT_FALSE(pathStr.IsSoftLink());
    pathStr = Utility::Path("test_file_soft_link");
    EXPECT_TRUE(pathStr.IsSoftLink());
    remove("test_file_soft_link");
}

TEST(Path, check_not_soft_link_expect_return_false)
{
    std::string targetFile = "./test2.txt";
    FILE *fp = fopen(targetFile.c_str(), "w");
    fclose(fp);

    Path inputPath = Utility::Path{targetFile};
    ASSERT_FALSE(inputPath.IsSoftLink());
    remove("test2.txt");
}

TEST(Path, check_invalid_path_expect_return_false)
{
    std::string pathStr;
    ASSERT_FALSE(Utility::CheckIsValidInputPath(pathStr));
    ASSERT_FALSE(Utility::CheckIsValidOutputPath(pathStr));
}

TEST(Path, check_valid_path_expect_return_true)
{
    Utility::UmaskGuard umaskGuard(Utility::DEFAULT_UMASK_FOR_CSV_FILE);
    std::string pathStr = "test.txt";
    FILE *fp = fopen(pathStr.c_str(), "w");
    fclose(fp);
    ASSERT_TRUE(Utility::CheckIsValidInputPath(pathStr));
    ASSERT_TRUE(Utility::CheckIsValidOutputPath(pathStr));
    remove(pathStr.c_str());
}

TEST(Path, check_read_permission_invalid_path_expect_return_false)
{
    Utility::UmaskGuard umaskGuard(333);
    std::string pathStr = "test.txt";
    FILE *fp = fopen(pathStr.c_str(), "w");
    fclose(fp);
    ASSERT_TRUE(Utility::CheckIsValidInputPath(pathStr));
    ASSERT_TRUE(Utility::CheckIsValidOutputPath(pathStr));
    remove(pathStr.c_str());
}

TEST(Path, check_write_permission_invalid_path_expect_return_false)
{
    Utility::UmaskGuard umaskGuard(555);
    std::string pathStr = "test.txt";
    FILE *fp = fopen(pathStr.c_str(), "w");
    fclose(fp);
    ASSERT_TRUE(Utility::CheckIsValidInputPath(pathStr));
    ASSERT_TRUE(Utility::CheckIsValidOutputPath(pathStr));
    remove(pathStr.c_str());
}