/*
 * Copyright (c) 2024 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 <fstream>
#include <iostream>
#include <securec.h>

#include "business_error.h"
#include "napi_parse_utils.h"
#include "nweb_napi_scope.h"
#include "nweb_log.h"
#include "web_errors.h"
#include "webview_createpdf_execute_callback.h"
namespace OHOS::NWeb {
using namespace NWebError;
const std::string JS_EXT_ARR_CLASS_NAME = "PdfData";
thread_local napi_ref g_jsArrExtClassRef;
// static
void WebviewCreatePDFExecuteCallback::InitJSExcute(napi_env env, napi_value exports)
{
    napi_value jsArrExtClass = nullptr;
    napi_property_descriptor jsArrExtClsProperties[] = { DECLARE_NAPI_FUNCTION(
        "pdfArrayBuffer", NapiArrayBufferExt::GetArrayBuffer) };
    napi_define_class(env, JS_EXT_ARR_CLASS_NAME.c_str(), JS_EXT_ARR_CLASS_NAME.length(),
        NapiArrayBufferExt::JsConstructor, nullptr, sizeof(jsArrExtClsProperties) / sizeof(jsArrExtClsProperties[0]),
        jsArrExtClsProperties, &jsArrExtClass);
    napi_create_reference(env, jsArrExtClass, 1, &g_jsArrExtClassRef);
    napi_set_named_property(env, exports, JS_EXT_ARR_CLASS_NAME.c_str(), jsArrExtClass);
}

void WebviewCreatePDFExecuteCallback::ReleaseArrayBufferExecuteParamAndUvWork(
    ArrayBufferExecuteParam* param, uv_work_t* work)
{
    if (param != nullptr) {
        if (param->result_ != nullptr) {
            delete[] param->result_;
            param->result_ = nullptr;
        }
        delete param;
        param = nullptr;
    }
    if (work != nullptr) {
        delete work;
        work = nullptr;
    }
}

void WebviewCreatePDFExecuteCallback::OnReceiveValue(const char* value, const long size)
{
    if (value == nullptr || size <= 0) {
        WVLOG_E("[CreatePDF] value is null or size is invalid");
        return;
    }

    ArrayBufferExecuteParam* param = new (std::nothrow) ArrayBufferExecuteParam();
    if (param == nullptr) {
        WVLOG_E("[CreatePDF] allocate ArrayBufferExecuteParam failed");
        return;
    }
    param->env_ = env_;
    param->callbackRef_ = callbackRef_;
    param->deferred_ = deferred_;
    param->result_ = new (std::nothrow) char[size + 1];
    if (param->result_ == nullptr) {
        WVLOG_E("new char failed");
        delete param;
        return;
    }
    if (memcpy_s(param->result_, size, value, size) != 0) {
        WVLOG_E("[CreatePDF] memcpy failed");
        delete[] param->result_;
        delete param;
        return;
    }
    param->size_ = size;
    auto task = [param]() {
        std::shared_ptr<ArrayBufferExecuteParam> context(
            static_cast<ArrayBufferExecuteParam*>(param), [](ArrayBufferExecuteParam* ptr) {
                delete[] ptr->result_;
                delete ptr; 
            });
        napi_env env = param->env_;
        NApiScope scope(env);
        if (!scope.IsVaild()) {
            WVLOG_E("[CreatePDF] open handle scope failed");
            return;
        }

        if (param->callbackRef_) {
            UvAfterWorkCbAsync(env, param->callbackRef_, param->result_, param->size_);
        } else if (param->deferred_) {
            UvAfterWorkCbPromise(env, param->deferred_, param->result_, param->size_);
        }
    };
    if (napi_status::napi_ok != napi_send_event(env_, task, napi_eprio_immediate, "WebviewCreatePDF_napi")) {
        WVLOG_E("OnReceiveValue: Failed to SendEvent");
        delete[] param->result_;
        delete param;
    }
}

void WebviewCreatePDFExecuteCallback::UvAfterWorkCbAsync(
    napi_env env, napi_ref callbackRef, const char* result, const long size)
{
    napi_value setResult[INTEGER_TWO] = { 0 };

    if (result == nullptr) {
        setResult[INTEGER_ZERO] = BusinessError::CreateError(env, NWebError::INVALID_RESOURCE);
        napi_get_null(env, &setResult[INTEGER_ONE]);
    } else {
        napi_get_undefined(env, &setResult[INTEGER_ZERO]);
        napi_value jsArrExt = nullptr;
        NAPI_CALL_RETURN_VOID(env, napi_get_reference_value(env, g_jsArrExtClassRef, &jsArrExt));
        NAPI_CALL_RETURN_VOID(env, napi_new_instance(env, jsArrExt, 0, NULL, &setResult[INTEGER_ONE]));

        WebJsArrayBufferExt* webArrayBufferExt = new (std::nothrow) WebJsArrayBufferExt(result, size);
        if (webArrayBufferExt == nullptr) {
            WVLOG_E("new WebJsArrayBufferExt failed.");
            return;
        }

        napi_status status = napi_wrap(
            env, setResult[INTEGER_ONE], webArrayBufferExt,
            [](napi_env env, void* data, void* hint) {
                WebJsArrayBufferExt* webArrayBufferExt = static_cast<WebJsArrayBufferExt*>(data);
                delete webArrayBufferExt;
                webArrayBufferExt = nullptr;
            },
            nullptr, nullptr);
        if (status != napi_status::napi_ok) {
            if (webArrayBufferExt) {
                delete webArrayBufferExt;
                webArrayBufferExt = nullptr;
            }
            WVLOG_E("napi_wrap failed");
            return;
        }
    }
    napi_value args[INTEGER_TWO] = { setResult[INTEGER_ZERO], setResult[INTEGER_ONE] };
    napi_value callback = nullptr;
    napi_value callbackResult = nullptr;

    napi_get_reference_value(env, callbackRef, &callback);
    napi_call_function(env, nullptr, callback, INTEGER_TWO, args, &callbackResult);
    napi_delete_reference(env, callbackRef);
}

void WebviewCreatePDFExecuteCallback::UvAfterWorkCbPromise(
    napi_env env, napi_deferred deferred, const char* result, const long size)
{
    napi_value setResult[INTEGER_TWO] = { 0 };
    setResult[INTEGER_ZERO] = NWebError::BusinessError::CreateError(env, NWebError::INVALID_RESOURCE);

    napi_value jsArrExt = nullptr;
    napi_status status = napi_get_reference_value(env, g_jsArrExtClassRef, &jsArrExt);
    if (status != napi_status::napi_ok) {
        WVLOG_E("napi_get_reference_value failed.");
        return;
    }
    status = napi_new_instance(env, jsArrExt, 0, NULL, &setResult[INTEGER_ONE]);
    if (status != napi_status::napi_ok) {
        WVLOG_E("napi_new_instance failed.");
        return;
    }
    WebJsArrayBufferExt* webArrayBufferExt = new (std::nothrow) WebJsArrayBufferExt(result, size);
    if (webArrayBufferExt == nullptr) {
        WVLOG_E("new WebJsArrayBufferExt failed.");
        return;
    }

    status = napi_wrap(
        env, setResult[INTEGER_ONE], webArrayBufferExt,
        [](napi_env env, void* data, void* hint) {
            WebJsArrayBufferExt* webArrayBufferExt = static_cast<WebJsArrayBufferExt*>(data);
            delete webArrayBufferExt;
            webArrayBufferExt = nullptr;
        },
        nullptr, nullptr);
    if (status != napi_status::napi_ok) {
        if (webArrayBufferExt) {
            delete webArrayBufferExt;
            webArrayBufferExt = nullptr;
        }
        WVLOG_E("napi_wrap failed.");
        return;
    }

    napi_value args[INTEGER_TWO] = { setResult[INTEGER_ZERO], setResult[INTEGER_ONE] };
    if (result == nullptr) {
        napi_reject_deferred(env, deferred, args[INTEGER_ZERO]);
    } else {
        napi_resolve_deferred(env, deferred, args[INTEGER_ONE]);
    }
}

napi_value NapiArrayBufferExt::JsConstructor(napi_env env, napi_callback_info info)
{
    napi_value thisVar = nullptr;
    napi_get_cb_info(env, info, nullptr, nullptr, &thisVar, nullptr);
    return thisVar;
}

napi_value NapiArrayBufferExt::GetArrayBuffer(napi_env env, napi_callback_info info)
{
    napi_value thisVar = nullptr;
    napi_value result = nullptr;
    size_t argc = INTEGER_ONE;
    napi_value argv[INTEGER_ONE] = { 0 };

    WebJsArrayBufferExt* webArrayBufferExt = nullptr;
    NAPI_CALL(env, napi_get_cb_info(env, info, &argc, argv, &thisVar, nullptr));
    NAPI_CALL(env, napi_unwrap(env, thisVar, (void**)&webArrayBufferExt));
    if (webArrayBufferExt == nullptr) {
        WVLOG_E("unwrap webArrayBufferExt failed.");
        return result;
    }

    const char* pdfResult = webArrayBufferExt->GetPDFResult();
    const long size = webArrayBufferExt->GetPDFSize();
    if (pdfResult == nullptr || size <= 0) {
        WVLOG_E("[CreatePDF] invalid PDF result or size");
        return nullptr;
    }
    napi_value arraybuffer = nullptr;
    void* bufferData = nullptr;

    napi_status status = napi_create_arraybuffer(env, size, &bufferData, &arraybuffer);
    if (status != napi_ok) {
        WVLOG_E("[CreatePDF] create array buffer failed, status: %{public}d", status);
        return nullptr;
    }
    if (bufferData == nullptr) {
        WVLOG_E("[CreatePDF] bufferData is null after array buffer creation");
        return nullptr;
    }
    if (memcpy_s(bufferData, size, pdfResult, size) != 0) {
        WVLOG_E("[CreatePDF] memcpy failed");
        return nullptr;
    }
    status = napi_create_typedarray(env, napi_typedarray_type::napi_uint8_array, size, arraybuffer, 0, &result);
    if (status != napi_ok) {
        WVLOG_E("[CreatePDF] create typed array failed, status: %{public}d", status);
        return nullptr;
    }
    return result;
}

} // namespace OHOS::NWeb