/*

 * Copyright (c) 2025 Huawei Device Co., Ltd.

 * 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 "atomicfile_n_exporter.h"



#include <cstdio>

#include <cstdlib>

#include <cstring>

#include <memory>

#include <sys/stat.h>



#include "atomicfile_entity.h"

#include "class_file/file_entity.h"

#include "class_file/file_n_exporter.h"

#include "../fs_utils.h"

#include "common_func.h"

#include "file_utils.h"

#include "filemgmt_libhilog.h"

#include "filemgmt_libn.h"

#include "uv.h"



namespace OHOS {

namespace FileManagement {

namespace ModuleFileIO {

using namespace OHOS::FileManagement::LibN;



namespace {

const std::string READ_STREAM_CLASS = "ReadStream";

const std::string WRITE_STREAM_CLASS = "WriteStream";

const std::string TEMP_FILE_SUFFIX = "_XXXXXX";



struct BufferData {

    uint8_t* buffer = nullptr;

    size_t length = 0;



    ~BufferData()

    {

        delete[] buffer;

    }

};

}



static void FinalizeCallback(napi_env env, void *finalizeData, void *finalizeHint)

{

    BufferData *bufferData = static_cast<BufferData *>(finalizeHint);

    delete bufferData;

}



static napi_value CreateStream(napi_env env, napi_callback_info info, const std::string &streamName,

    const std::string &fileName)

{

    NFuncArg funcArg(env, info);

    if (!funcArg.InitArgs(NARG_CNT::ZERO)) {

        HILOGE("Number of arguments unmatched");

        NError(E_PARAMS).ThrowErr(env);

        return nullptr;

    }



    const char moduleName[] = "@ohos.file.streamrw";

    napi_value streamrw;

    napi_status status = napi_load_module(env, moduleName, &streamrw);

    if (status != napi_ok) {

        HILOGE("Failed to load module");

        NError(UNKROWN_ERR).ThrowErr(env, "Failed to load module");

        return nullptr;

    }



    napi_value constructor = nullptr;

    status = napi_get_named_property(env, streamrw, streamName.c_str(), &constructor);

    if (status != napi_ok) {

        HILOGE("Failed to get named property");

        NError(UNKROWN_ERR).ThrowErr(env, "Failed to get named property");

        return nullptr;

    }



    napi_value filePath = NVal::CreateUTF8String(env, fileName).val_;

    napi_value argv[NARG_CNT::ONE] = {filePath};

    napi_value streamObj;

    size_t argc = 1;

    status = napi_new_instance(env, constructor, argc, argv, &streamObj);

    if (status != napi_ok) {

        HILOGE("Failed to create napi new instance");

        NError(UNKROWN_ERR).ThrowErr(env, "Failed to create napi new instance");

        return nullptr;

    }



    return NVal(env, streamObj).val_;

}



static void CallFunctionByName(napi_env env, napi_value objStream, const std::string &funcName)

{

    napi_valuetype valuetype;

    napi_typeof(env, objStream, &valuetype);

    if (valuetype != napi_object) {

        HILOGE("Valuetype is unmatched");

        return;

    }



    napi_value key;

    napi_status status = napi_create_string_utf8(env, funcName.c_str(), funcName.length(), &key);

    if (status != napi_ok) {

        HILOGE("Failed to create string utf8");

        return;

    }



    napi_value value;

    status = napi_get_property(env, objStream, key, &value);

    if (status != napi_ok) {

        HILOGE("Failed to get property");

        return;

    }



    status = napi_call_function(env, objStream, value, 0, nullptr, nullptr);

    if (status != napi_ok) {

        HILOGE("Failed to call %{public}s function", funcName.c_str());

        return;

    }

}



static NVal InstantiateFile(napi_env env, int fd, std::string path, bool isUri)

{

    napi_value objFile = NClass::InstantiateClass(env, FileNExporter::className_, {});

    if (!objFile) {

        close(fd);

        HILOGE("Failed to instantiate class");

        NError(UNKROWN_ERR).ThrowErr(env, "Failed to instantiate class");

        return NVal();

    }



    auto fileEntity = NClass::GetEntityOf<FileEntity>(env, objFile);

    if (fileEntity == nullptr) {

        close(fd);

        HILOGE("Failed to get fileEntity");

        NError(UNKROWN_ERR).ThrowErr(env, "Failed to get fileEntity");

        return NVal();

    }

    auto fdg = CreateUniquePtr<DistributedFS::FDGuard>(fd, false);

    if (fdg == nullptr) {

        close(fd);

        HILOGE("Failed to request heap memory.");

        NError(ENOMEM).ThrowErr(env);

        return NVal();

    }

    fileEntity->fd_.swap(fdg);

    if (isUri) {

        fileEntity->path_ = "";

        fileEntity->uri_ = path;

    } else {

        fileEntity->path_ = path;

        fileEntity->uri_ = "";

    }

    return { env, objFile };

}



static std::tuple<AtomicFileEntity*, int32_t> GetAtomicFileEntity(napi_env env, napi_callback_info info)

{

    NFuncArg funcArg(env, info);

    if (!funcArg.InitArgs(NARG_CNT::ZERO)) {

        HILOGE("Number of arguments unmatched");

        return {nullptr, E_PARAMS};

    }



    auto rafEntity = NClass::GetEntityOf<AtomicFileEntity>(env, funcArg.GetThisVar());

    if (rafEntity == nullptr) {

        HILOGE("Failed to get atomicFile");

        return {nullptr, UNKROWN_ERR};

    }



    return {rafEntity, 0};

}



napi_value AtomicFileNExporter::GetBaseFile(napi_env env, napi_callback_info info)

{

    auto [rafEntity, errcode] = GetAtomicFileEntity(env, info);

    if (errcode != 0) {

        if (errcode == UNKROWN_ERR) {

            NError(errcode).ThrowErr(env, "Failed to get atomicFile");

        } else {

            NError(errcode).ThrowErr(env);

        }

        return nullptr;

    }



    if (rafEntity->baseFileName.size() >= PATH_MAX) {

        HILOGE("Base file name is too long");

        NError(UNKROWN_ERR).ThrowErr(env, "Base file name is too long");

        return nullptr;

    }



    char realPath[PATH_MAX];

    char *result = realpath(rafEntity->baseFileName.c_str(), realPath);

    int err = errno;

    if (result == nullptr) {

        HILOGE("Failed to resolve real path, err:%{public}d", errno);

        NError(err).ThrowErr(env);

        return nullptr;

    }



    int fd = open(result, O_RDONLY);

    if (fd < 0) {

        HILOGE("Failed to open file, err:%{public}d", errno);

        NError(errno).ThrowErr(env);

        return nullptr;

    }

    return InstantiateFile(env, fd, rafEntity->baseFileName, false).val_;

}



napi_value AtomicFileNExporter::OpenRead(napi_env env, napi_callback_info info)

{

    auto [rafEntity, errcode] = GetAtomicFileEntity(env, info);

    if (errcode != 0) {

        if (errcode == UNKROWN_ERR) {

            NError(errcode).ThrowErr(env, "Failed to get atomicFile");

        } else {

            NError(errcode).ThrowErr(env);

        }

        return nullptr;

    }



    return CreateStream(env, info, READ_STREAM_CLASS, rafEntity->baseFileName);

}



static std::tuple<std::unique_ptr<BufferData>, int32_t> ReadFileToBuffer(napi_env env, FILE* fp)

{

    int fd = fileno(fp);

    if (fd < 0) {

        HILOGE("Failed to get file descriptor, err:%{public}d", errno);

        return {nullptr, UNKROWN_ERR};

    }



    struct stat fileStat {};

    if (fstat(fd, &fileStat) < 0) {

        HILOGE("Failed to get file stats, err:%{public}d", errno);

        return {nullptr, errno};

    }



    long fileSize = fileStat.st_size;

    if (fileSize <= 0) {

        HILOGE("Invalid file size");

        return {nullptr, EIO};

    }



    auto bufferData = std::make_unique<BufferData>();

    bufferData->buffer = new(std::nothrow) uint8_t[fileSize];

    if (bufferData->buffer == nullptr) {

        HILOGE("Failed to allocate memory");

        return {nullptr, ENOMEM};

    }

    bufferData->length = fread(bufferData->buffer, sizeof(uint8_t), fileSize, fp);

    if ((bufferData->length != static_cast<size_t>(fileSize) && !feof(fp)) || ferror(fp)) {

        HILOGE("Failed to read file, actual length is:%zu, fileSize:%ld", bufferData->length, fileSize);

        delete[] bufferData->buffer;

        bufferData->buffer = nullptr;

        bufferData->length = 0;

        return {nullptr, EIO};

    }

    return {std::move(bufferData), 0};

}



napi_value AtomicFileNExporter::ReadFully(napi_env env, napi_callback_info info)

{

    auto [rafEntity, errcode] = GetAtomicFileEntity(env, info);

    if (errcode != 0) {

        if (errcode == UNKROWN_ERR) {

            NError(errcode).ThrowErr(env, "Failed to get atomicFile");

        } else {

            NError(errcode).ThrowErr(env);

        }

        return nullptr;

    }



    char realPath[PATH_MAX];

    char *result = realpath(rafEntity->baseFileName.c_str(), realPath);

    if (result == nullptr) {

        HILOGE("Failed to resolve file real path, err:%{public}d", errno);

        NError(errno).ThrowErr(env);

        return nullptr;

    }



    auto file = std::unique_ptr<FILE, decltype(&std::fclose)>(

        std::fopen(result, "rb"), &std::fclose);

    if (!file) {

        HILOGE("Failed to open file, err:%{public}d", errno);

        NError(errno).ThrowErr(env);

        return nullptr;

    }



    auto [bufferData, readErrcode] = ReadFileToBuffer(env, file.get());

    if (readErrcode != 0) {

        if (readErrcode == UNKROWN_ERR) {

            NError(readErrcode).ThrowErr(env, "Failed to read file to buffer");

        } else {

            NError(readErrcode).ThrowErr(env);

        }

        return nullptr;

    }



    napi_value externalBuffer = nullptr;

    size_t length = bufferData->length;

    napi_status status = napi_create_external_arraybuffer(

        env, bufferData->buffer, bufferData->length, FinalizeCallback, bufferData.release(), &externalBuffer);

    if (status != napi_ok) {

        NError(UNKROWN_ERR).ThrowErr(env, "Failed to create external arraybuffer");

        return nullptr;

    }



    napi_value outputArray = nullptr;

    status = napi_create_typedarray(env, napi_int8_array, length, externalBuffer, 0, &outputArray);

    if (status != napi_ok) {

        NError(UNKROWN_ERR).ThrowErr(env, "Failed to create typedarray");

        return nullptr;

    }



    return outputArray;

}



napi_value AtomicFileNExporter::StartWrite(napi_env env, napi_callback_info info)

{

    auto [rafEntity, errcode] = GetAtomicFileEntity(env, info);

    if (errcode != 0) {

        if (errcode == UNKROWN_ERR) {

            NError(errcode).ThrowErr(env, "Failed to get atomicFile");

        } else {

            NError(errcode).ThrowErr(env);

        }

        return nullptr;

    }



    std::string parentPath = GetParentDirectory(rafEntity->newFileName);

    if (access(parentPath.c_str(), F_OK) != 0) {

        HILOGE("Parent directory does not exist, err:%{public}d", errno);

        NError(ENOENT).ThrowErr(env);

        return nullptr;

    }



    char *tmpfile = const_cast<char *>(rafEntity->newFileName.c_str());

    if (mkstemp(tmpfile) == -1) {

        HILOGE("Fail to create tmp file err:%{public}d!", errno);

        NError(ENOENT).ThrowErr(env);

        return nullptr;

    }



    napi_value writeStream = CreateStream(env, info, WRITE_STREAM_CLASS, rafEntity->newFileName);

    if (writeStream == nullptr) {

        HILOGE("Failed to create write stream");

        return nullptr;

    }

    napi_status status = napi_create_reference(env, writeStream, 1, &rafEntity->writeStreamObj);

    if (status != napi_ok) {

        HILOGE("Failed to create reference");

        NError(UNKROWN_ERR).ThrowErr(env, "Failed to create reference");

        return nullptr;

    }

    return writeStream;

}



napi_value AtomicFileNExporter::FinishWrite(napi_env env, napi_callback_info info)

{

    auto [rafEntity, errcode] = GetAtomicFileEntity(env, info);

    if (errcode != 0) {

        if (errcode == UNKROWN_ERR) {

            NError(errcode).ThrowErr(env, "Failed to get atomicFile");

        } else {

            NError(errcode).ThrowErr(env);

        }

        return nullptr;

    }



    napi_value writeStream;

    napi_status status = napi_get_reference_value(env, rafEntity->writeStreamObj, &writeStream);

    if (status != napi_ok) {

        HILOGE("Failed to get reference value");

        NError(UNKROWN_ERR).ThrowErr(env, "Failed to get reference value");

        return nullptr;

    }



    CallFunctionByName(env, writeStream, "closeSync");



    int32_t result = std::rename(rafEntity->newFileName.c_str(), rafEntity->baseFileName.c_str());

    if (result != 0) {

        HILOGE("Failed to rename file, ret:%{public}d", result);

        status = napi_delete_reference(env, rafEntity->writeStreamObj);

        if (status != napi_ok) {

            HILOGE("Failed to delete reference");

            NError(UNKROWN_ERR).ThrowErr(env, "Failed to delete reference");

            return nullptr;

        }

        NError(UNKROWN_ERR).ThrowErr(env, "Failed to rename file");

        return nullptr;

    }

    std::string tmpNewFileName = rafEntity->baseFileName;

    rafEntity->newFileName = tmpNewFileName.append(TEMP_FILE_SUFFIX);

    status = napi_delete_reference(env, rafEntity->writeStreamObj);

    if (status != napi_ok) {

        HILOGE("Failed to delete reference");

        NError(UNKROWN_ERR).ThrowErr(env, "Failed to delete reference");

        return nullptr;

    }

    return nullptr;

}



napi_value AtomicFileNExporter::FailWrite(napi_env env, napi_callback_info info)

{

    auto [rafEntity, errcode] = GetAtomicFileEntity(env, info);

    if (errcode != 0) {

        if (errcode == UNKROWN_ERR) {

            NError(errcode).ThrowErr(env, "Failed to get atomicFile");

        } else {

            NError(errcode).ThrowErr(env);

        }

        return nullptr;

    }



    napi_value writeStream;

    napi_status status = napi_get_reference_value(env, rafEntity->writeStreamObj, &writeStream);

    if (status != napi_ok) {

        HILOGE("Failed to get reference value");

        NError(UNKROWN_ERR).ThrowErr(env, "Failed to get reference value");

        return nullptr;

    }



    CallFunctionByName(env, writeStream, "closeSync");



    if (!RemoveFile(rafEntity->newFileName.c_str())) {

        HILOGW("Failed to remove file");

        status = napi_delete_reference(env, rafEntity->writeStreamObj);

        if (status != napi_ok) {

            HILOGE("Failed to delete reference");

            NError(UNKROWN_ERR).ThrowErr(env, "Failed to delete reference");

            return nullptr;

        }

        NError(UNKROWN_ERR).ThrowErr(env, "Failed to remove file");

        return nullptr;

    }

    std::string tmpNewFileName = rafEntity->baseFileName;

    rafEntity->newFileName = tmpNewFileName.append(TEMP_FILE_SUFFIX);

    status = napi_delete_reference(env, rafEntity->writeStreamObj);

    if (status != napi_ok) {

        HILOGE("Failed to delete reference");

        NError(UNKROWN_ERR).ThrowErr(env, "Failed to delete reference");

    }

    return nullptr;

}



napi_value AtomicFileNExporter::Delete(napi_env env, napi_callback_info info)

{

    auto [rafEntity, errcode] = GetAtomicFileEntity(env, info);

    if (errcode != 0) {

        if (errcode == UNKROWN_ERR) {

            NError(errcode).ThrowErr(env, "Failed to get atomicFile");

        } else {

            NError(errcode).ThrowErr(env);

        }

        return nullptr;

    }



    bool errFlag = false;

    if (FileIsExist(rafEntity->newFileName.c_str()) && !RemoveFile(rafEntity->newFileName.c_str())) {

        errFlag = true;

    }

    if (FileIsExist(rafEntity->baseFileName.c_str()) && !RemoveFile(rafEntity->baseFileName.c_str())) {

        errFlag = true;

    }

    if (errFlag) {

        HILOGE("Failed to remove file");

    }



    rafEntity->newFileName.clear();

    rafEntity->baseFileName.clear();

    return nullptr;

}



napi_value AtomicFileNExporter::Constructor(napi_env env, napi_callback_info info)

{

    NFuncArg funcArg(env, info);

    if (!funcArg.InitArgs(NARG_CNT::ONE)) {

        HILOGE("Number of arguments unmatched");

        NError(E_PARAMS).ThrowErr(env);

        return nullptr;

    }



    auto [resGetFirstArg, file, num] = NVal(env, funcArg[NARG_POS::FIRST]).ToUTF8String();

    if (!resGetFirstArg) {

        HILOGE("Invalid path");

        NError(E_PARAMS).ThrowErr(env);

        return nullptr;

    }



    auto atomicFileEntity = CreateUniquePtr<AtomicFileEntity>();

    if (atomicFileEntity == nullptr) {

        HILOGE("Failed to request heap memory");

        NError(ENOMEM).ThrowErr(env);

        return nullptr;

    }

    std::string filePath = file.get();

    atomicFileEntity->baseFileName = filePath;

    atomicFileEntity->newFileName = filePath.append(TEMP_FILE_SUFFIX);

    if (!NClass::SetEntityFor<AtomicFileEntity>(env, funcArg.GetThisVar(), move(atomicFileEntity))) {

        HILOGE("Failed to wrap entity for obj AtomicFile");

        NError(EIO).ThrowErr(env);

        return nullptr;

    }



    return funcArg.GetThisVar();

}



bool AtomicFileNExporter::Export()

{

    std::vector<napi_property_descriptor> props = {

        NVal::DeclareNapiFunction("getBaseFile", GetBaseFile),

        NVal::DeclareNapiFunction("openRead", OpenRead),

        NVal::DeclareNapiFunction("readFully", ReadFully),

        NVal::DeclareNapiFunction("startWrite", StartWrite),

        NVal::DeclareNapiFunction("finishWrite", FinishWrite),

        NVal::DeclareNapiFunction("failWrite", FailWrite),

        NVal::DeclareNapiFunction("delete", Delete),

    };



    std::string className = GetClassName();

    bool succ = false;

    napi_value classValue = nullptr;

    std::tie(succ, classValue) = NClass::DefineClass(

        exports_.env_, className, AtomicFileNExporter::Constructor, move(props));

    if (!succ) {

        HILOGE("INNER BUG. Failed to define class");

        NError(ENOMEM).ThrowErr(exports_.env_);

        return false;

    }

    succ = NClass::SaveClass(exports_.env_, className, classValue);

    if (!succ) {

        HILOGE("INNER BUG. Failed to save class");

        NError(ENOMEM).ThrowErr(exports_.env_);

        return false;

    }



    return exports_.AddProp(className, classValue);

}



std::string AtomicFileNExporter::GetClassName()

{

    return AtomicFileNExporter::className_;

}



AtomicFileNExporter::AtomicFileNExporter(napi_env env, napi_value exports) : NExporter(env, exports) {}

AtomicFileNExporter::~AtomicFileNExporter() {}

} // namespace ModuleFileIO

} // namespace FileManagement

} // namespace OHOS