/*
 * Copyright (C) 2025-2025. Huawei Technologies Co., Ltd. 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 "dynolog/src/utils.h"

#include <unistd.h>
#include <climits>
#include <cstdint>
#include <cstring>
#include <sys/stat.h>
#include <pwd.h>
#include <unordered_map>
#include <glog/logging.h>
#include <nlohmann/json.hpp>

namespace dynolog {

namespace {
const uint16_t MAX_PATH_SIZE = 1024;
constexpr int INPUT_DIR_CHECK_MODE = R_OK | X_OK;
constexpr mode_t OTHERS_WRITE_MASK = S_IWGRP | S_IWOTH;
const std::unordered_map<std::string, std::string> INVALID_CHAR = {
    {"\n", "\\n"}, {"\f", "\\f"}, {"\r", "\\r"}, {"\b", "\\b"}, {"\t", "\\t"},
    {"\v", "\\v"}, {"\u007F", "\\u007F"}, {"\"", "\\\""}, {"'", "\'"},
    {"\\", "\\\\"}, {"%", "\\%"}, {">", "\\>"}, {"<", "\\<"}, {"|", "\\|"},
    {"&", "\\&"}, {"$", "\\$"}
};
}

std::string Rstrip(const std::string &str1, const std::string &str2)
{
    size_t end = str1.find_last_not_of(str2);
    return end == std::string::npos ? "" : str1.substr(0, end + 1);
}

bool PathUtils::Access(const std::string &path, const int &mode)
{
    if (path.empty()) {
        LOG(ERROR) << "The file path is empty.";
        return false;
    }
    return access(path.c_str(), mode) == 0;
}

bool PathUtils::Exist(const std::string &path)
{
    return Access(path, F_OK);
}

bool PathUtils::IsSoftLink(const std::string &path)
{
    if (path.empty()) {
        LOG(ERROR) << "The file path is empty.";
        return false;
    }
    std::string tmpPath = Rstrip(path, "./");
    struct stat fileStat;
    if (lstat(tmpPath.c_str(), &fileStat) != 0) {
        LOG(ERROR) << "The file stat failed, path: " << path;
        return false;
    }
    return S_ISLNK(fileStat.st_mode);
}

bool PathUtils::IsFile(const std::string &path)
{
    if (path.empty()) {
        LOG(ERROR) << "The file path is empty.";
        return false;
    }
    struct stat fileStat;
    if (stat(path.c_str(), &fileStat) != 0) {
        LOG(ERROR) << "The file stat failed, path: " << path;
        return false;
    }
    return fileStat.st_mode & S_IFREG;
}

bool PathUtils::IsWritableByOthers(const std::string &path)
{
    struct stat fileStat;
    if (stat(path.c_str(), &fileStat) != 0) {
        LOG(ERROR) << "The file stat failed, path: " << path;
        return true;
    }
    return (fileStat.st_mode & OTHERS_WRITE_MASK) != 0;
}

bool PathUtils::IsOwner(const std::string &path)
{
    struct stat info;
    if (stat(path.c_str(), &info) != 0) {
        LOG(ERROR) << "The file stat failed, path: " << path;
        return false;
    }
    uid_t current_uid = getuid();
    if (info.st_uid != current_uid) {
        return false;
    }
    return true;
}

bool PathUtils::DirPathCheck(const std::string &path)
{
    if (path.empty()) {
        LOG(ERROR) << "The path is empty.";
        return false;
    }
    if (path.size() > MAX_PATH_SIZE) {
        LOG(ERROR) << "The length of path is too long, max allowed: " << MAX_PATH_SIZE;
        return false;
    }
    for (auto &item : INVALID_CHAR) {
        if (path.find(item.first) != std::string::npos) {
            LOG(ERROR) << "The path contains invalid character: " << item.first;
            return false;
        }
    }
    if (!Exist(path)) {
        LOG(ERROR) << "The path does not exist: " << path;
        return false;
    }
    if (IsFile(path)) {
        LOG(ERROR) << "The path is a file: " << path;
        return false;
    }
    if (IsSoftLink(path)) {
        LOG(ERROR) << "The path is a soft link: " << path;
        return false;
    }
    if (IsRoot()) {
        return true;
    }
    if (!IsOwner(path)) {
        LOG(ERROR) << "The path is not owned by current user: " << path;
        return false;
    }
    if (!Access(path, INPUT_DIR_CHECK_MODE)) {
        LOG(ERROR) << "The path is not readable: " << path;
        return false;
    }
    if (IsWritableByOthers(path)) {
        LOG(ERROR) << "The path is writable by others: " << path;
        return false;
    }
    return true;
}

bool IsRoot()
{
    return getuid() == 0;
}

class JsonDepthCheckSAX : public nlohmann::json::json_sax_t {
public:
    bool null() override { return true; }
    bool boolean(bool) override { return true; }
    bool number_integer(nlohmann::json::number_integer_t) override { return true; }
    bool number_unsigned(nlohmann::json::number_unsigned_t) override { return true; }
    bool number_float(nlohmann::json::number_float_t, const std::string &) override { return true; }
    bool string(nlohmann::json::string_t &) override { return true; }
    bool binary(nlohmann::json::binary_t &) override { return true; }
    bool key(nlohmann::json::string_t &) override { return true; }
    bool parse_error(std::size_t, const std::string &, const nlohmann::json::exception &) override { return false; }

    bool start_object(std::size_t) override
    {
        depth_++;
        if (depth_ > MAX_DEPTH) {
            throw std::runtime_error("JSON depth exceeds maximum allowed depth");
        }
        return true;
    }

    bool end_object() override
    {
        depth_--;
        return true;
    }

    bool start_array(std::size_t) override
    {
        depth_++;
        if (depth_ > MAX_DEPTH) {
            throw std::runtime_error("JSON depth exceeds maximum allowed depth");
        }
        return true;
    }

    bool end_array() override
    {
        depth_--;
        return true;
    }

private:
    int depth_ = 0;
    const int MAX_DEPTH = 10;
};

bool CheckJsonDepth(const std::string &json_str)
{
    JsonDepthCheckSAX sax;
    try {
        nlohmann::json::sax_parse(json_str, &sax);
    } catch (const std::exception &e) {
        LOG(ERROR) << "JSON depth check failed: " << e.what();
        return false;
    }
    return true;
}

std::string GetCurrentUserHomePath()
{
    static std::string home_path = []() -> std::string {
        struct passwd *pw = getpwuid(getuid());
        if (pw == nullptr || pw->pw_dir == nullptr) {
            LOG(ERROR) << "Failed to get current user info";
            return "";
        }
        return std::string(pw->pw_dir);
    }();
    return home_path;
}
} // namespace dynolog