/*
 * Copyright (c) 2023-2026 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 "native_distributedhardwarefwk_js.h"

#include "ipc_skeleton.h"
#include "js_native_api.h"

#include "accesstoken_kit.h"
#include "ipc_skeleton.h"
#include "tokenid_kit.h"

#include "device_type.h"
#include "distributed_hardware_log.h"
#include "distributed_hardware_fwk_kit.h"

using namespace OHOS::DistributedHardware;

namespace {
#define GET_PARAMS(env, info, num)    \
    size_t argc = num;                \
    napi_value argv[num] = {nullptr}; \
    napi_value thisVar = nullptr;     \
    DH_CALL(napi_get_cb_info(env, info, &argc, argv, &thisVar, nullptr))

#define DH_RETVAL_NOTHING
#define DH_CALL_BASE(theCall, retVal)                                      \
    do {                                                                    \
        if ((theCall) != napi_ok) {                                         \
            DHLOGE("napi call failed, theCall: %{public}s", #theCall); \
            return retVal;                                                  \
        }                                                                   \
    } while (0)

#define DH_CALL(theCall) DH_CALL_BASE(theCall, nullptr)
#define DH_CALL_RETURN_VOID(theCall) DH_CALL_BASE(theCall, DH_RETVAL_NOTHING)

#define DH_ASSERT_BASE(env, assertion, message, code, retVal)                                    \
    do {                                                                                     \
        if (!(assertion)) {                                                                  \
            napi_value errMsg; \
            DH_CALL(napi_create_string_utf8(env, message, NAPI_AUTO_LENGTH, &errMsg)); \
            napi_value error; \
            DH_CALL(napi_create_error(env, nullptr, errMsg, &error)); \
            napi_value errorCode; \
            DH_CALL(napi_create_int32(env, code, &errorCode)); \
            DH_CALL(napi_set_named_property(env, error, "errorCode", errorCode)); \
            DH_CALL(napi_throw(env, error)); \
            return retVal;                                                                   \
        }                                                                                    \
    } while (0)

#define DH_ASSERT(env, assertion, message, code) DH_ASSERT_BASE(env, assertion, message, code, nullptr)

const int32_t DH_NAPI_ARGS_ONE = 1;
const int32_t DH_NAPI_ARGS_TWO = 2;

enum DHBussinessErrorCode {
    // Permission verify failed.
    ERR_NO_PERMISSION = 201,
    // The caller is not a system application.
    ERR_NOT_SYSTEM_APP = 202,
    // Input parameter error.
    ERR_INVALID_PARAMS = 401,
    // The distributed hardware is not started.
    ERR_CODE_DH_NOT_START = 24200101,
    // The source device is not connected.
    ERR_CODE_DEVICE_NOT_CONNECT = 24200102,
};

const std::string ERR_MESSAGE_INVALID_PARAMS = "Input parameter error.";
const std::string ERR_MESSAGE_NO_PERMISSION = "Permission verify failed.";
const std::string ERR_MESSAGE_NOT_SYSTEM_APP = "The caller is not a system application.";
const std::string ERR_MESSAGE_DH_NOT_START = "The distributed hardware is not started.";
const std::string ERR_MESSAGE_DEVICE_NOT_CONNECT = "The source device is not connected.";

bool CheckArgsType(napi_env env, bool assertion, const std::string &paramName, const std::string &type)
{
    if (!(assertion)) {
        std::string errMsg = ERR_MESSAGE_INVALID_PARAMS + "The type of " + paramName +
                " must be " + type;
        napi_throw_error(env, std::to_string(ERR_INVALID_PARAMS).c_str(), errMsg.c_str());
        return false;
    }
    return true;
}

bool IsFunctionType(napi_env env, napi_value value)
{
    napi_valuetype eventHandleType = napi_undefined;
    napi_typeof(env, value, &eventHandleType);
    return CheckArgsType(env, eventHandleType == napi_function, "callback", "function");
}
} // namespace

void DistributedHardwareManager::JsObjectToString(const napi_env &env, const napi_value &object,
    const std::string &fieldStr, char *dest, const int32_t destLen)
{
    bool hasProperty = false;
    DH_CALL_RETURN_VOID(napi_has_named_property(env, object, fieldStr.c_str(), &hasProperty));
    if (hasProperty) {
        napi_value field = nullptr;
        napi_valuetype valueType = napi_undefined;

        napi_get_named_property(env, object, fieldStr.c_str(), &field);
        DH_CALL_RETURN_VOID(napi_typeof(env, field, &valueType));
        if (!CheckArgsType(env, valueType == napi_string, fieldStr.c_str(), "string")) {
            return;
        }
        size_t result = 0;
        DH_CALL_RETURN_VOID(napi_get_value_string_utf8(env, field, dest, destLen, &result));
    } else {
        DHLOGE("devicemanager napi js to str no property: %{public}s", fieldStr.c_str());
    }
}

void DistributedHardwareManager::JsObjectToInt(const napi_env &env, const napi_value &object,
    const std::string &fieldStr, int32_t &fieldRef)
{
    bool hasProperty = false;
    DH_CALL_RETURN_VOID(napi_has_named_property(env, object, fieldStr.c_str(), &hasProperty));
    if (hasProperty) {
        napi_value field = nullptr;
        napi_valuetype valueType = napi_undefined;

        napi_get_named_property(env, object, fieldStr.c_str(), &field);
        DH_CALL_RETURN_VOID(napi_typeof(env, field, &valueType));
        if (!CheckArgsType(env, valueType == napi_number, fieldStr.c_str(), "number")) {
            return;
        }
        napi_get_value_int32(env, field, &fieldRef);
    } else {
        DHLOGE("devicemanager napi js to int no property: %{public}s", fieldStr.c_str());
    }
}

bool DistributedHardwareManager::IsSystemApp()
{
    uint64_t tokenId = OHOS::IPCSkeleton::GetSelfTokenID();
    return OHOS::Security::AccessToken::TokenIdKit::IsSystemAppByFullTokenID(tokenId);
}

bool DistributedHardwareManager::HasAccessDHPermission()
{
    OHOS::Security::AccessToken::AccessTokenID callerToken = OHOS::IPCSkeleton::GetCallingTokenID();
    const std::string permissionName = "ohos.permission.ACCESS_DISTRIBUTED_HARDWARE";
    int32_t result = OHOS::Security::AccessToken::AccessTokenKit::VerifyAccessToken(callerToken,
        permissionName);
    return (result == OHOS::Security::AccessToken::PERMISSION_GRANTED);
}

bool DistributedHardwareManager::Verify(napi_env env, int32_t type)
{
    if (!IsSystemApp()) {
        DHLOGE("GetCallerProcessName not system hap.");
        CreateBusinessErr(env, ERR_NOT_SYSTEM_APP);
        return false;
    }
    if (!HasAccessDHPermission()) {
        DHLOGE("The caller has no ACCESS_DISTRIBUTED_HARDWARE permission.");
        CreateBusinessErr(env, ERR_NO_PERMISSION);
        return false;
    }
    if (!IsSupportType(type)) {
        DHLOGE("param type is invalid.");
        CreateBusinessErr(env, ERR_INVALID_PARAMS);
        return false;
    }
    return true;
}

bool DistributedHardwareManager::IsSupportType(int32_t type)
{
    if (type == ALL || type == CAMERA || type == SCREEN || type == MODEM_MIC || type == MODEM_SPEAKER ||
        type == MIC || type == SPEAKER) {
        return true;
    }
    return false;
}

napi_value DistributedHardwareManager::CreateBusinessErr(napi_env env, int32_t errCode)
{
    napi_value error = nullptr;
    switch (static_cast<DHBussinessErrorCode>(errCode)) {
        case ERR_NOT_SYSTEM_APP:
            napi_throw_error(env, std::to_string(errCode).c_str(), ERR_MESSAGE_NOT_SYSTEM_APP.c_str());
            break;
        case ERR_NO_PERMISSION:
            napi_throw_error(env, std::to_string(errCode).c_str(), ERR_MESSAGE_NO_PERMISSION.c_str());
            break;
        case ERR_INVALID_PARAMS:
            napi_throw_error(env, std::to_string(errCode).c_str(), ERR_MESSAGE_INVALID_PARAMS.c_str());
            break;
        default:
            break;
    }
    return error;
}

napi_value DistributedHardwareManager::PauseDistributedHardware(napi_env env, napi_callback_info info)
{
    DHLOGI("PauseDistributedHardware in");
    napi_value result = nullptr;
    size_t argc = 2;
    napi_value argv[2] = {nullptr};
    napi_value thisVar = nullptr;
    DH_CALL(napi_get_cb_info(env, info, &argc, argv, &thisVar, nullptr));
    DH_ASSERT(env, ((argc >= DH_NAPI_ARGS_ONE) && (argc <= DH_NAPI_ARGS_TWO)),
                             "requires 1 or 2 parameter", ERR_INVALID_PARAMS);

    napi_valuetype valueType = napi_undefined;
    napi_typeof(env, argv[0], &valueType);
    if (!CheckArgsType(env, valueType == napi_object, "description", "object")) {
        return nullptr;
    }
    int32_t type = -1;
    char networkId[96];
    JsObjectToInt(env, argv[0], "type", type);
    if (!Verify(env, type)) {
        return nullptr;
    }
    DHType dhType = DHType::UNKNOWN;
    DHSubtype dhSubtype = static_cast<DHSubtype>(type);
    if (dhSubtype == DHSubtype::AUDIO_MIC || dhSubtype == DHSubtype::AUDIO_SPEAKER) {
        dhType = DHType::AUDIO;
    } else if (dhSubtype == DHSubtype::CAMERA) {
        dhType = DHType::CAMERA;
    }
    JsObjectToString(env, argv[0], "srcNetworkId", networkId, sizeof(networkId));
    std::shared_ptr<DistributedHardwareFwkKit> dhFwkKit = std::make_shared<DistributedHardwareFwkKit>();
    if (argc == DH_NAPI_ARGS_ONE) {    // promise
        napi_deferred deferred;
        napi_value promise = 0;
        napi_create_promise(env, &deferred, &promise);
        int32_t ret = dhFwkKit->PauseDistributedHardware(dhType, std::string(networkId));
        if (ret != 0) {
            DHLOGE("PauseDistributedHardware for DHType: %{public}u filed", (uint32_t)dhType);
        }
        return promise;
    } else if (argc == DH_NAPI_ARGS_TWO) {    // callback
        if (!IsFunctionType(env, argv[1])) {
            return nullptr;
        }
        int32_t ret = dhFwkKit->PauseDistributedHardware(dhType, std::string(networkId));
        if (ret != 0) {
            DHLOGE("PauseDistributedHardware for DHType: %{public}u filed", (uint32_t)dhType);
        }
    }
    napi_get_undefined(env, &result);
    return result;
}

napi_value DistributedHardwareManager::ResumeDistributedHardware(napi_env env, napi_callback_info info)
{
    DHLOGI("ResumeDistributedHardware in");
    napi_value result = nullptr;
    size_t argc = 2;
    napi_value argv[2] = {nullptr};
    napi_value thisVar = nullptr;
    DH_CALL(napi_get_cb_info(env, info, &argc, argv, &thisVar, nullptr));
    DH_ASSERT(env, ((argc >= DH_NAPI_ARGS_ONE) && (argc <= DH_NAPI_ARGS_TWO)),
                        "requires  or 2 parameter", ERR_INVALID_PARAMS);

    napi_valuetype valueType = napi_undefined;
    napi_typeof(env, argv[0], &valueType);
    if (!CheckArgsType(env, valueType == napi_object, "description", "object")) {
        return nullptr;
    }
    int32_t type = -1;
    char networkId[96];
    JsObjectToInt(env, argv[0], "type", type);
    if (!Verify(env, type)) {
        return nullptr;
    }
    DHType dhType = DHType::UNKNOWN;
    DHSubtype dhSubtype = static_cast<DHSubtype>(type);
    if (dhSubtype == DHSubtype::AUDIO_MIC || dhSubtype == DHSubtype::AUDIO_SPEAKER) {
        dhType = DHType::AUDIO;
    } else if (dhSubtype == DHSubtype::CAMERA) {
        dhType = DHType::CAMERA;
    }
    JsObjectToString(env, argv[0], "srcNetworkId", networkId, sizeof(networkId));
    std::shared_ptr<DistributedHardwareFwkKit> dhFwkKit = std::make_shared<DistributedHardwareFwkKit>();
    if (argc == DH_NAPI_ARGS_ONE) {    // promise
        napi_deferred deferred;
        napi_value promise = 0;
        napi_create_promise(env, &deferred, &promise);
        int32_t ret = dhFwkKit->ResumeDistributedHardware(dhType, std::string(networkId));
        if (ret != 0) {
            DHLOGE("ResumeDistributedHardware for DHType: %{public}u filed", (uint32_t)dhType);
        }
        return promise;
    } else if (argc == DH_NAPI_ARGS_TWO) {    // callback
        if (!IsFunctionType(env, argv[1])) {
            return nullptr;
        }
        int32_t ret = dhFwkKit->ResumeDistributedHardware(dhType, std::string(networkId));
        if (ret != 0) {
            DHLOGE("ResumeDistributedHardware for DHType: %{public}u filed", (uint32_t)dhType);
        }
    }
    napi_get_undefined(env, &result);
    return result;
}

napi_value DistributedHardwareManager::StopDistributedHardware(napi_env env, napi_callback_info info)
{
    DHLOGI("StopDistributedHardware in");
    napi_value result = nullptr;
    size_t argc = 2;
    napi_value argv[2] = {nullptr};
    napi_value thisVar = nullptr;
    DH_CALL(napi_get_cb_info(env, info, &argc, argv, &thisVar, nullptr));
    DH_ASSERT(env, (argc >= DH_NAPI_ARGS_ONE) && (argc <= DH_NAPI_ARGS_TWO),
                         "requires 1 or  parameter", ERR_INVALID_PARAMS);

    napi_valuetype valueType = napi_undefined;
    napi_typeof(env, argv[0], &valueType);
    if (!CheckArgsType(env, valueType == napi_object, "description", "object")) {
        return nullptr;
    }
    int32_t type = -1;
    char networkId[96];
    JsObjectToInt(env, argv[0], "type", type);
    if (!Verify(env, type)) {
        return nullptr;
    }
    DHType dhType = DHType::UNKNOWN;
    DHSubtype dhSubtype = static_cast<DHSubtype>(type);
    if (dhSubtype == DHSubtype::AUDIO_MIC || dhSubtype == DHSubtype::AUDIO_SPEAKER) {
        dhType = DHType::AUDIO;
    } else if (dhSubtype == DHSubtype::CAMERA) {
        dhType = DHType::CAMERA;
    }
    JsObjectToString(env, argv[0], "srcNetworkId", networkId, sizeof(networkId));
    std::shared_ptr<DistributedHardwareFwkKit> dhFwkKit = std::make_shared<DistributedHardwareFwkKit>();
    if (argc == DH_NAPI_ARGS_ONE) {    // promise
        napi_deferred deferred;
        napi_value promise = 0;
        napi_create_promise(env, &deferred, &promise);
        int32_t ret = dhFwkKit->StopDistributedHardware(dhType, std::string(networkId));
        if (ret != 0) {
            DHLOGE("StopDistributedHardware for DHType: %{public}u filed", (uint32_t)dhType);
        }
        return promise;
    } else if (argc == DH_NAPI_ARGS_TWO) {    // callback
        if (!IsFunctionType(env, argv[1])) {
            return nullptr;
        }
        int32_t ret = dhFwkKit->StopDistributedHardware(dhType, std::string(networkId));
        if (ret != 0) {
            DHLOGE("StopDistributedHardware for DHType: %{public}u filed", (uint32_t)dhType);
        }
    }
    napi_get_undefined(env, &result);
    return result;
}

napi_value DistributedHardwareManager::Init(napi_env env, napi_value exports)
{
    napi_property_descriptor dhmProperties[] = {
        DECLARE_NAPI_FUNCTION("pauseDistributedHardware", PauseDistributedHardware),
        DECLARE_NAPI_FUNCTION("resumeDistributedHardware", ResumeDistributedHardware),
        DECLARE_NAPI_FUNCTION("stopDistributedHardware", StopDistributedHardware),
    };

    DHLOGI("DistributedHardwareManager::Init is called!");
    DH_CALL(napi_define_properties(env, exports, sizeof(dhmProperties) / sizeof(dhmProperties[0]),
        dhmProperties));
    DHLOGI("All functions are configured..");
    return exports;
}

void DistributedHardwareManager::InitDistributedHardwareType(napi_env env, napi_value exports)
{
    char propertyName[] = "DistributedHardwareType";
    napi_value all = nullptr;
    napi_value camera = nullptr;
    napi_value screen = nullptr;
    napi_value modemMic = nullptr;
    napi_value modemSpk = nullptr;
    napi_value mic = nullptr;
    napi_value speaker = nullptr;
    napi_create_int32(env, ALL, &all);
    napi_create_int32(env, CAMERA, &camera);
    napi_create_int32(env, SCREEN, &screen);
    napi_create_int32(env, MODEM_MIC, &modemMic);
    napi_create_int32(env, MODEM_SPEAKER, &modemSpk);
    napi_create_int32(env, MIC, &mic);
    napi_create_int32(env, SPEAKER, &speaker);
    napi_property_descriptor desc[] = {
        DECLARE_NAPI_STATIC_PROPERTY("ALL", all),
        DECLARE_NAPI_STATIC_PROPERTY("CAMERA", camera),
        DECLARE_NAPI_STATIC_PROPERTY("SCREEN", screen),
        DECLARE_NAPI_STATIC_PROPERTY("MODEM_MIC", modemMic),
        DECLARE_NAPI_STATIC_PROPERTY("MODEM_SPEAKER", modemSpk),
        DECLARE_NAPI_STATIC_PROPERTY("MIC", mic),
        DECLARE_NAPI_STATIC_PROPERTY("SPEAKER", speaker)
    };
    napi_value obj = nullptr;
    napi_create_object(env, &obj);
    napi_define_properties(env, obj, sizeof(desc) / sizeof(desc[0]), desc);
    napi_set_named_property(env, exports, propertyName, obj);
}

void DistributedHardwareManager::InitDistributedHardwareErrorCode(napi_env env, napi_value exports)
{
    char propertyName[] = "DistributedHardwareErrorCode";
    napi_value dhNotStart = nullptr;
    napi_value deviceNotConnect = nullptr;
    napi_create_int32(env, static_cast<int32_t>(DHBussinessErrorCode::ERR_CODE_DH_NOT_START), &dhNotStart);
    napi_create_int32(env, static_cast<int32_t>(DHBussinessErrorCode::ERR_CODE_DEVICE_NOT_CONNECT), &deviceNotConnect);

    napi_property_descriptor desc[] = {
        DECLARE_NAPI_STATIC_PROPERTY("ERR_CODE_DISTRIBUTED_HARDWARE_NOT_STARTED", dhNotStart),
        DECLARE_NAPI_STATIC_PROPERTY("ERR_CODE_DEVICE_NOT_CONNECTED", deviceNotConnect),
    };
    napi_value obj = nullptr;
    napi_create_object(env, &obj);
    napi_define_properties(env, obj, sizeof(desc) / sizeof(desc[0]), desc);
    napi_set_named_property(env, exports, propertyName, obj);
}

/*
 * Function registering all props and functions of ohos.distributedhardware
 */
static napi_value Export(napi_env env, napi_value exports)
{
    DHLOGI("Export is called!");
    DistributedHardwareManager::Init(env, exports);
    DistributedHardwareManager::InitDistributedHardwareType(env, exports);
    DistributedHardwareManager::InitDistributedHardwareErrorCode(env, exports);
    return exports;
}

/*
 * module define
 */
static napi_module g_dhModule = {.nm_version = 1,
                                 .nm_flags = 0,
                                 .nm_filename = nullptr,
                                 .nm_register_func = Export,
                                 .nm_modname = "distributedHardware.hardwareManager",
                                 .nm_priv = ((void *)0),
                                 .reserved = {0}};

/*
 * module register
 */
extern "C" __attribute__((constructor)) void RegisterModule(void)
{
    DHLOGI("RegisterModule is called!");
    napi_module_register(&g_dhModule);
}