/*
 * Copyright (c) 2022 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 "napi/native_common.h"
#include "accesstoken_kit.h"

#include "print_task.h"
#include "napi_print_utils.h"
#include "print_callback.h"
#include "print_log.h"
#include "print_manager_client.h"
#include "print_constant.h"

namespace OHOS::Print {

using namespace std;
using namespace Security::AccessToken;

const std::string EVENT_BLOCK = "block";
const std::string EVENT_SUCCESS = "succeed";
const std::string EVENT_FAIL = "fail";
const std::string EVENT_CANCEL = "cancel";

static const std::string SPOOLER_BUNDLE_NAME = "com.ohos.spooler";
static const std::string SPOOLER_PREVIEW_ABILITY_NAME = "PrintServiceExtAbility";
static const std::string LAUNCH_PARAMETER_JOB_ID = "jobId";
static const std::string LAUNCH_PARAMETER_FILE_LIST = "fileList";
static const std::string TOKEN_KEY = "ohos.ability.params.token";
static const std::string UI_EXTENSION_TYPE_NAME = "ability.want.params.uiExtensionType";
static const std::string PRINT_UI_EXTENSION_TYPE = "sysDialog/print";
static const std::string CALLER_PKG_NAME = "caller.pkgName";
static const std::string ABILITY_PARAMS_STREAM = "ability.params.stream";
static const std::string LAUNCH_PARAMETER_FILE_LIST_SIZE = "fileListSize";
static const int32_t MAX_FILE_LIST_SIZE = 100;

PrintTask::PrintTask(const std::vector<std::string> &innerList, const sptr<IRemoteObject> &innerCallerToken_)
    : taskId_("")
{
    if (!innerList.empty()) {
        if (innerList.begin()->find("fd://") == 0) {
            PRINT_HILOGD("list type: fdlist");
            for (auto fdPath : innerList) {
                pathType_ = FD_PATH;
                uint32_t fd = PrintUtils::GetIdFromFdPath(fdPath);
                fdList_.emplace_back(fd);
            }
        } else {
            PRINT_HILOGD("list type: filelist");
            fileList_.assign(innerList.begin(), innerList.end());
            pathType_ = FILE_PATH_ABSOLUTED;
            if (!fileList_.empty() && fileList_.begin()->find("file://") == 0) {
                pathType_ = FILE_PATH;
            }
        }
    }

    supportEvents_[EVENT_BLOCK] = true;
    supportEvents_[EVENT_SUCCESS] = true;
    supportEvents_[EVENT_FAIL] = true;
    supportEvents_[EVENT_CANCEL] = true;
    callerToken_ = innerCallerToken_;
}

PrintTask::PrintTask(const std::string &innerPrintJobName_, const sptr<IPrintCallback> &innerPrintAdapterCallback_,
    const std::shared_ptr<PrintAttributes> &innerPrintAttributes_, const sptr<IRemoteObject> &innerCallerToken_)
    : taskId_("")
{
    supportEvents_[EVENT_BLOCK] = true;
    supportEvents_[EVENT_SUCCESS] = true;
    supportEvents_[EVENT_FAIL] = true;
    supportEvents_[EVENT_CANCEL] = true;
    printJobName_ = innerPrintJobName_;
    printAdapterCallback_ = innerPrintAdapterCallback_;
    printAttributes_ = innerPrintAttributes_;
    callerToken_ = innerCallerToken_;
}

PrintTask::~PrintTask()
{
    supportEvents_.clear();
    Stop();
}

uint32_t PrintTask::Start(napi_env env, napi_callback_info info)
{
    if (fileList_.empty() && fdList_.empty()) {
        PRINT_HILOGE("fileList and fdList are both empty");
        return E_PRINT_INVALID_PARAMETER;
    }
    if (pathType_ == FILE_PATH_ABSOLUTED) {
        for (auto file : fileList_) {
            int32_t fd = PrintUtils::OpenFile(file);
            if (fd >= 0) {
                fdList_.emplace_back(fd);
                continue;
            }
            PRINT_HILOGE("file[%{private}s] is invalid", file.c_str());
            for (auto fd : fdList_) {
                close(fd);
            }
            fdList_.clear();
            fileList_.clear();
            return E_PRINT_INVALID_PARAMETER;
        }
    }

    PRINT_HILOGI("call client's StartPrint interface.");
    std::shared_ptr<AdapterParam> adapterParam = std::make_shared<AdapterParam>();
    CreateDefaultAdapterParam(adapterParam);
    std::string jobId = PrintUtils::GetPrintJobId();
    adapterParam->jobId = jobId;
    taskId_ = jobId;
    uint32_t ret = CallSpooler(env, info, adapterParam, false);
    if (ret != E_PRINT_NONE) {
        PRINT_HILOGE("CallSpooler failed.");
        for (auto fd : fdList_) {
            close(fd);
        }
        fdList_.clear();
        fileList_.clear();
        return ret;
    }
    return PrintManagerClient::GetInstance()->StartPrint(fileList_, fdList_, taskId_);
}

uint32_t PrintTask::StartPrintAdapter(napi_env env, napi_callback_info info)
{
    if (printAdapterCallback_ != nullptr && printAttributes_ != nullptr) {
        PRINT_HILOGI("call client's StartPrintAdapter interface.");
        if (callerToken_ != nullptr) {
            std::shared_ptr<AdapterParam> adapterParam = std::make_shared<AdapterParam>();
            if (adapterParam == nullptr) {
                PRINT_HILOGE("create adapterParam failed.");
                return E_PRINT_SERVER_FAILURE;
            }
            adapterParam->documentName = printJobName_;
            adapterParam->isCheckFdList = false;
            adapterParam->printAttributes = *printAttributes_;
            std::string jobId = PrintUtils::GetPrintJobId();
            adapterParam->jobId = jobId;
            taskId_ = jobId;
            uint32_t ret = CallSpooler(env, info, adapterParam, true);
            if (ret != E_PRINT_NONE) {
                PRINT_HILOGE("CallSpooler failed.");
            }
            return PrintManagerClient::GetInstance()->Print(
                printJobName_, printAdapterCallback_, *printAttributes_, taskId_, callerToken_);
        }
    }
    return E_PRINT_INVALID_PARAMETER;
}

uint32_t PrintTask::CallSpooler(
    napi_env env, napi_callback_info info, const std::shared_ptr<AdapterParam> &adapterParam, bool isPrintByAdapter)
{
    PRINT_HILOGI("enter CallSpooler.");
    if (!CheckPermission(PERMISSION_NAME_PRINT)) {
        PRINT_HILOGE("no permission to access print service, ErrorCode:[%{public}d]", E_PRINT_NO_PERMISSION);
        return E_PRINT_NO_PERMISSION;
    }
    size_t argc = isPrintByAdapter ? NapiPrintUtils::ARGC_FOUR : NapiPrintUtils::ARGC_TWO;
    size_t contextIndex = isPrintByAdapter ? NapiPrintUtils::INDEX_THREE : NapiPrintUtils::INDEX_ONE;
    size_t callBackIndex = isPrintByAdapter ? NapiPrintUtils::INDEX_FOUR : NapiPrintUtils::INDEX_TWO;
    size_t argMaxNum = isPrintByAdapter ? NapiPrintUtils::ARGC_FIVE : NapiPrintUtils::ARGC_THREE;
    napi_value argv[NapiPrintUtils::ARGC_FOUR] = {0};
    napi_value thisArg = nullptr;
    void *data = nullptr;
    napi_value result = nullptr;

    PRINT_CALL_BASE(env, napi_get_undefined(env, &result), E_PRINT_INVALID_PARAMETER);
    PRINT_CALL_BASE(env, napi_get_cb_info(env, info, &argc, argv, &thisArg, &data), E_PRINT_INVALID_PARAMETER);
    PRINT_HILOGI("CallSpooler params size: %{public}zu", argc);
    if (argc < argMaxNum - 1) {
        PRINT_HILOGE("invalid parameters.");
        return E_PRINT_INVALID_PARAMETER;
    }

    auto asyncContext = std::make_shared<BaseContext>();
    asyncContext->env = env;
    asyncContext->requestType = PrintRequestType::REQUEST_TYPE_START;
    if (!ParseAbilityContextReq(env, argv[contextIndex], asyncContext->context, asyncContext->uiExtensionContext)) {
        PRINT_HILOGE("invalid parameters.");
        return E_PRINT_INVALID_PARAMETER;
    }

    if (argc == argMaxNum) {
        napi_valuetype valueType = napi_undefined;
        PRINT_CALL_BASE(env, napi_typeof(env, argv[callBackIndex], &valueType), napi_undefined);
        if (valueType == napi_function) {
            PRINT_CALL_BASE(env, napi_create_reference(env, argv[callBackIndex], 1, &asyncContext->callback),
                E_PRINT_INVALID_PARAMETER);
            PRINT_HILOGD("is a callback api");
        }
    } else {
        PRINT_CALL_BASE(env, napi_create_promise(env, &asyncContext->deferred, &result), E_PRINT_INVALID_PARAMETER);
        PRINT_HILOGD("is a promise api");
    }
    uint32_t ret = StartUIExtensionAbility(asyncContext, adapterParam);
    PRINT_HILOGI("end CallSpooler");
    return ret;
}

bool PrintTask::ParseAbilityContextReq(napi_env env, const napi_value &obj,
    std::shared_ptr<OHOS::AbilityRuntime::AbilityContext> &abilityContext,
    std::shared_ptr<OHOS::AbilityRuntime::UIExtensionContext> &uiExtensionContext)
{
    PRINT_HILOGD("begin ParseAbilityContextReq");
    bool stageMode = false;
    napi_status status = OHOS::AbilityRuntime::IsStageContext(env, obj, stageMode);
    if (status != napi_ok || !stageMode) {
        PRINT_HILOGE("it is not a stage mode");
        return false;
    }

    auto context = OHOS::AbilityRuntime::GetStageModeContext(env, obj);
    if (context == nullptr) {
        PRINT_HILOGE("get context failed");
        return false;
    }

    abilityContext = OHOS::AbilityRuntime::Context::ConvertTo<OHOS::AbilityRuntime::AbilityContext>(context);
    if (abilityContext == nullptr) {
        PRINT_HILOGE("get abilityContext failed");
        uiExtensionContext =
            OHOS::AbilityRuntime::Context::ConvertTo<OHOS::AbilityRuntime::UIExtensionContext>(context);
        if (uiExtensionContext == nullptr) {
            PRINT_HILOGE("get uiExtensionContext failed");
            return false;
        }
    }

    PRINT_HILOGD("end ParseAbilityContextReq");
    return true;
}

uint32_t PrintTask::StartUIExtensionAbility(
    std::shared_ptr<BaseContext> asyncContext, const std::shared_ptr<AdapterParam> &adapterParam)
{
    PRINT_HILOGD("begin StartUIExtensionAbility");

    if (adapterParam == nullptr) {
        PRINT_HILOGE("adapterParam is nullptr.");
        return E_PRINT_INVALID_PARAMETER;
    }
    if ((adapterParam->isCheckFdList && fileList_.empty() && fdList_.empty())) {
        PRINT_HILOGE("to be printed filelist and fdlist are empty.");
        return E_PRINT_INVALID_PARAMETER;
    }
    AAFwk::Want want;
    want.SetElementName(SPOOLER_BUNDLE_NAME, SPOOLER_PREVIEW_ABILITY_NAME);
    want.SetParam(LAUNCH_PARAMETER_JOB_ID, adapterParam->jobId);
    if (fileList_.size() <= MAX_FILE_LIST_SIZE) {
        want.SetParam(LAUNCH_PARAMETER_FILE_LIST, fileList_);
    } else {
        PRINT_HILOGW("fileList exceeds the maximum length.");
    }
    want.SetParam(LAUNCH_PARAMETER_FILE_LIST_SIZE, static_cast<int>(fileList_.size()));
    PrintUtils::BuildAdapterParam(adapterParam, want);
    int32_t callerTokenId = static_cast<int32_t>(IPCSkeleton::GetCallingTokenID());
    int32_t callerUid = IPCSkeleton::GetCallingUid();
    int32_t callerPid = IPCSkeleton::GetCallingPid();
    std::string callerPkg = PrintUtils::GetBundleNameForUid(callerUid);
    want.SetParam(AAFwk::Want::PARAM_RESV_CALLER_TOKEN, callerTokenId);
    want.SetParam(AAFwk::Want::PARAM_RESV_CALLER_UID, callerUid);
    want.SetParam(AAFwk::Want::PARAM_RESV_CALLER_PID, callerPid);
    want.SetParam(CALLER_PKG_NAME, callerPkg);
    want.SetParam(UI_EXTENSION_TYPE_NAME, PRINT_UI_EXTENSION_TYPE);
    want.SetParam(ABILITY_PARAMS_STREAM, fileList_);
    want.SetFlags(AAFwk::Want::FLAG_AUTH_READ_URI_PERMISSION);

    uint32_t ret = StartUIExtensionAbility(want, asyncContext);
    if (ret != E_PRINT_NONE) {
        PRINT_HILOGE("StartUIExtensionAbility fail");
    }
    PRINT_HILOGD("end StartUIExtensionAbility");
    return ret;
}

uint32_t PrintTask::StartUIExtensionAbility(OHOS::AAFwk::Want &want, std::shared_ptr<BaseContext> asyncContext)
{
    PRINT_HILOGI("begin StartUIExtensionAbility");
    if (asyncContext == nullptr) {
        PRINT_HILOGE("asyncContext is nullptr");
        return E_PRINT_INVALID_PARAMETER;
    }

    if (asyncContext->context == nullptr && asyncContext->uiExtensionContext == nullptr) {
        PRINT_HILOGE("asyncContext is nullptr");
        return E_PRINT_INVALID_PARAMETER;
    }

    auto uiContent = GetUIContent(asyncContext.get());
    if (uiContent == nullptr) {
        PRINT_HILOGE("UIContent is nullptr");
        return E_PRINT_INVALID_PARAMETER;
    }

    std::string info = uiContent->GetContentInfo();
    auto callback = std::make_shared<PrintModalUICallback>(asyncContext);
    if (callback == nullptr) {
        PRINT_HILOGE("create callback failed.");
        return E_PRINT_SERVER_FAILURE;
    }
    OHOS::Ace::ModalUIExtensionCallbacks extensionCallbacks = {
        [callback](int32_t releaseCode) { callback->OnRelease(releaseCode); },
        [callback](int32_t resultCode, const OHOS::AAFwk::Want& result) {
            callback->OnResultForModal(resultCode, result);
        },
        [callback](const OHOS::AAFwk::WantParams& request) { callback->OnReceive(request); },
        [callback](int32_t code, const std::string& name, const std::string& message) {
            callback->OnError(code, name, message);
        }
    };

    OHOS::Ace::ModalUIExtensionConfig config;
    config.isProhibitBack = true;
    int32_t sessionId = uiContent->CreateModalUIExtension(want, extensionCallbacks, config);
    PRINT_HILOGI("StartUIExtensionAbility sessionId %{public}d", sessionId);
    callback->SetSessionId(sessionId);

    PRINT_HILOGI("end StartUIExtensionAbility");
    return E_PRINT_NONE;
}

OHOS::Ace::UIContent *PrintTask::GetUIContent(const BaseContext *asyncContext)
{
    if (asyncContext == nullptr) {
        PRINT_HILOGE("asyncContext is nullptr.");
        return nullptr;
    }
    OHOS::Ace::UIContent *uiContent = nullptr;
    if (asyncContext->context != nullptr) {
        PRINT_HILOGI("get uiContext by ability context");
        uiContent = asyncContext->context->GetUIContent();
    } else if (asyncContext->uiExtensionContext != nullptr) {
        PRINT_HILOGI("get uiContext by ui extension ability context");
        uiContent = asyncContext->uiExtensionContext->GetUIContent();
    } else {
        PRINT_HILOGE("get uiContext failed.");
    }

    return uiContent;
}

void PrintTask::CreateDefaultAdapterParam(const std::shared_ptr<AdapterParam> &adapterParam)
{
    adapterParam->documentName = "";
    adapterParam->isCheckFdList = true;
}

void PrintTask::Stop()
{
    PrintManagerClient::GetInstance()->StopPrint(taskId_);
    taskId_ = "";
}

const std::string &PrintTask::GetId() const
{
    return taskId_;
}

napi_value PrintTask::On(napi_env env, napi_callback_info info)
{
    PRINT_HILOGD("Enter ---->");
    size_t argc = NapiPrintUtils::MAX_ARGC;
    napi_value argv[NapiPrintUtils::MAX_ARGC] = { nullptr };
    napi_value thisVal = nullptr;
    void *data = nullptr;
    PRINT_CALL(env, napi_get_cb_info(env, info, &argc, argv, &thisVal, &data));
    PRINT_ASSERT(env, argc == NapiPrintUtils::ARGC_TWO, "need 2 parameter!");

    napi_valuetype valuetype;
    PRINT_CALL(env, napi_typeof(env, argv[0], &valuetype));
    PRINT_ASSERT(env, valuetype == napi_string, "type is not a string");
    std::string type = NapiPrintUtils::GetStringFromValueUtf8(env, argv[NapiPrintUtils::INDEX_ZERO]);
    PRINT_HILOGD("type : %{public}s", type.c_str());

    valuetype = napi_undefined;
    napi_typeof(env, argv[1], &valuetype);
    PRINT_ASSERT(env, valuetype == napi_function, "callback is not a function");

    PrintTask *task;
    PRINT_CALL(env, napi_unwrap(env, thisVal, reinterpret_cast<void **>(&task)));
    if (task == nullptr || !task->IsSupportType(type)) {
        PRINT_HILOGE("Event On type : %{public}s not support", type.c_str());
        return nullptr;
    }

    napi_ref callbackRef = NapiPrintUtils::CreateReference(env, argv[1]);
    sptr<IPrintCallback> callback = new (std::nothrow) PrintCallback(env, callbackRef);
    if (callback == nullptr) {
        PRINT_HILOGE("create print callback object fail");
        return nullptr;
    }
    int32_t ret = PrintManagerClient::GetInstance()->On(task->taskId_, type, callback);
    if (ret != E_PRINT_NONE) {
        PRINT_HILOGE("Failed to register event");
        return nullptr;
    }
    return nullptr;
}

napi_value PrintTask::Off(napi_env env, napi_callback_info info)
{
    PRINT_HILOGD("Enter ---->");
    auto context = std::make_shared<TaskEventContext>();
    if (context == nullptr) {
        PRINT_HILOGE("create context failed.");
        return nullptr;
    }
    auto input =
        [context](
            napi_env env, size_t argc, napi_value *argv, napi_value self, napi_callback_info info) -> napi_status {
        PRINT_ASSERT_BASE(env, argc == NapiPrintUtils::ARGC_ONE, "need 1 parameter!", napi_invalid_arg);
        napi_valuetype valuetype;
        PRINT_CALL_BASE(env, napi_typeof(env, argv[NapiPrintUtils::INDEX_ZERO], &valuetype), napi_invalid_arg);
        PRINT_ASSERT_BASE(env, valuetype == napi_string, "type is not a string", napi_string_expected);
        std::string type = NapiPrintUtils::GetStringFromValueUtf8(env, argv[0]);
        PrintTask *task;
        PRINT_CALL_BASE(env, napi_unwrap(env, self, reinterpret_cast<void **>(&task)), napi_invalid_arg);
        if (task == nullptr || !task->IsSupportType(type)) {
            PRINT_HILOGE("Event On type : %{public}s not support", type.c_str());
            context->SetErrorIndex(E_PRINT_INVALID_PARAMETER);
            return napi_invalid_arg;
        }

        context->type = type;
        context->taskId = task->taskId_;
        PRINT_HILOGD("event type : %{public}s", context->type.c_str());
        return napi_ok;
    };
    auto output = [context](napi_env env, napi_value *result) -> napi_status {
        napi_status status = napi_get_boolean(env, context->result, result);
        PRINT_HILOGD("context->result = %{public}d", context->result);
        return status;
    };
    auto exec = [context](PrintAsyncCall::Context *ctx) {
        int32_t ret = PrintManagerClient::GetInstance()->Off(context->taskId, context->type);
        context->result = ret == E_PRINT_NONE;
        if (ret != E_PRINT_NONE) {
            PRINT_HILOGE("Failed to unregistered event");
            context->SetErrorIndex(ret);
        }
    };
    context->SetAction(std::move(input), std::move(output));
    PrintAsyncCall asyncCall(env, info, std::dynamic_pointer_cast<PrintAsyncCall::Context>(context));
    return asyncCall.Call(env, exec);
}

bool PrintTask::IsSupportType(const std::string &type) const
{
    return supportEvents_.find(type) != supportEvents_.end();
}

bool PrintTask::CheckPermission(const std::string &name)
{
    AccessTokenID tokenId = IPCSkeleton::GetCallingTokenID();
    TypeATokenTypeEnum tokenType = AccessTokenKit::GetTokenTypeFlag(tokenId);
    if (tokenType == TOKEN_INVALID) {
        PRINT_HILOGE("invalid token id %{public}d", tokenId);
        return false;
    }
    int result = AccessTokenKit::VerifyAccessToken(tokenId, name);
    if (result != PERMISSION_GRANTED) {
        PRINT_HILOGE("Current tokenId permission is %{public}d", result);
    }
    return result == PERMISSION_GRANTED;
}
} // namespace OHOS::Print