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

#include "parameters.h"
#include "nweb_log.h"
#include "json/json.h"
#include <cerrno>
#include <cstring>
#include <dlfcn_ext.h>
#include <fcntl.h>
#include <fstream>
#include <filesystem>
#include <mutex>
#include <policycoreutils.h>
#include <system_error>
#include <sys/mman.h>
#include <sys/prctl.h>
#include <sys/stat.h>
#include <unordered_set>
#include <regex>
#include <iomanip>

#if (defined(webview_arm64) && !defined(ASAN_DETECTOR))
#include <sys/mount.h>
#endif

namespace OHOS::ArkWeb {

static int g_appEngineVersion = static_cast<int>(ArkWebEngineVersion::SYSTEM_DEFAULT);
static bool g_webEngineInitFlag = false;
static ArkWebEngineVersion g_activeEngineVersion = ArkWebEngineVersion::SYSTEM_DEFAULT;
static int g_cloudEnableAppVersion = static_cast<int>(ArkWebEngineVersion::SYSTEM_DEFAULT);
static std::unique_ptr<std::unordered_set<std::string>> g_legacyApp = nullptr;
static std::unique_ptr<std::unordered_set<std::string>> g_dataMigrateApp = nullptr;
static std::string g_bundleName = "";
static std::string g_apiVersion = "";
static std::string g_appVersion = "";
static std::mutex g_appInfoMutex;

static void* g_reservedAddress = nullptr;
static size_t g_reservedSize = 0;
static bool g_shareRelroEnabled = false;
static bool g_arkwebEngineAccessible = false;
const int SHARED_RELRO_UID = 1037;
const std::string ARK_WEB_ENGINE_LIB_NAME = "libarkweb_engine.so";
const std::string SHARED_RELRO_DIR = "/data/service/el1/public/for-all-app/shared_relro";
const std::string NWEB_RELRO_PATH = SHARED_RELRO_DIR + "/libwebviewchromium64.relro";
const size_t RESERVED_VMA_SIZE = 512 * 1024 * 1024;
const std::string DATA_MIGRATE_APP_ALL = "All";
const std::string DATA_MIGRATE_APP_NONE = "None";

#if defined(webview_arm64)
const std::string ARK_WEB_CORE_MOCK_HAP_LIB_PATH =
    "/data/storage/el1/bundle/libs/arm64";
const std::string ARK_WEB_CORE_HAP_LIB_PATH =
    "/data/storage/el1/bundle/arkwebcore/libs/arm64";
const std::string ARK_WEB_CORE_LEGACY_HAP_LIB_PATH =
    "/data/storage/el1/bundle/arkwebcorelegacy/libs/arm64";
const std::string ARK_WEB_CORE_PATH_FOR_MOCK = "libs/arm64";
const std::string ARK_WEB_CORE_PATH_FOR_BUNDLE = "arkwebcore/libs/arm64";
const std::string ARK_WEB_CORE_LEGACY_PATH_FOR_BUNDLE = "arkwebcorelegacy/libs/arm64";
#elif defined(webview_x86_64)
const std::string ARK_WEB_CORE_MOCK_HAP_LIB_PATH =
    "/data/storage/el1/bundle/libs/x86_64";
const std::string ARK_WEB_CORE_HAP_LIB_PATH =
    "/data/storage/el1/bundle/arkwebcore/libs/x86_64";
const std::string ARK_WEB_CORE_LEGACY_HAP_LIB_PATH =
    "/data/storage/el1/bundle/arkwebcorelegacy/libs/x86_64";
const std::string ARK_WEB_CORE_PATH_FOR_MOCK = "libs/x86_64";
const std::string ARK_WEB_CORE_PATH_FOR_BUNDLE = "arkwebcore/libs/x86_64";
const std::string ARK_WEB_CORE_LEGACY_PATH_FOR_BUNDLE = "arkwebcorelegacy/libs/x86_64";
#else
const std::string ARK_WEB_CORE_MOCK_HAP_LIB_PATH =
    "/data/storage/el1/bundle/libs/arm";
const std::string ARK_WEB_CORE_HAP_LIB_PATH =
    "/data/storage/el1/bundle/arkwebcore/libs/arm";
const std::string ARK_WEB_CORE_LEGACY_HAP_LIB_PATH =
    "/data/storage/el1/bundle/arkwebcorelegacy/libs/arm";
const std::string ARK_WEB_CORE_PATH_FOR_MOCK = "libs/arm";
const std::string ARK_WEB_CORE_PATH_FOR_BUNDLE = "arkwebcore/libs/arm";
const std::string ARK_WEB_CORE_LEGACY_PATH_FOR_BUNDLE = "arkwebcorelegacy/libs/arm";
#endif

#if defined(IS_ASAN)
#if defined(webview_arm64)
const std::string ARK_WEB_CORE_ASAN_PATH_FOR_BUNDLE = "arkwebcore_asan/libs/arm64";
const std::string WEBVIEW_RELATIVE_SANDBOX_PATH_FOR_LIBRARY =
                    "data/storage/el1/bundle/arkwebcore_asan/libs/arm64/libarkweb_engine.so";
const std::string ARK_WEB_CORE_HAP_LIB_PATH_ASAN = "/data/storage/el1/bundle/arkwebcore_asan/libs/arm64";
#elif defined(webview_x86_64)
const std::string ARK_WEB_CORE_ASAN_PATH_FOR_BUNDLE = "arkwebcore_asan/libs/x86_64";
#else
const std::string ARK_WEB_CORE_ASAN_PATH_FOR_BUNDLE = "arkwebcore_asan/libs/arm";
#endif
#endif

const std::string PRECONFIG_LEGACY_HAP_PATH = "/system/app/ArkWebCoreLegacy/ArkWebCoreLegacy.hap";
const std::string  PRECONFIG_EVERGREEN_HAP_PATH =
    "/system/app/com.ohos.arkwebcore/ArkWebCore.hap";
const std::string PRECONFIG_EVERGREEN_WATCH_HAP_PATH =
    "/system/app/NWeb/NWeb.hap";
const std::string SANDBOX_LEGACY_HAP_PATH = "/data/storage/el1/bundle/arkwebcorelegacy/entry.hap";
const std::string SANDBOX_EVERGREEN_HAP_PATH = "/data/storage/el1/bundle/arkwebcore/entry.hap";

const std::string JSON_CONFIG_PATH =
    "/data/service/el1/public/update/param_service/install/system/etc/ArkWebSafeBrowsing/generic/ArkWebCoreCfg.json";
const std::string WEB_PARAM_PREFIX = "web.engine.";
const std::string SYSTEM_PARAM_VERSION_PATH =
    "/system/etc/ArkWebSafeBrowsing/generic/version.txt";
const std::string UPDATE_PARAM_VERSION_PATH =
    "/data/service/el1/public/update/param_service/install/system/etc/ArkWebSafeBrowsing/generic/version.txt";
const int VERSION_TAG_LEN = 3;

#if (defined(webview_arm64) && !defined(ASAN_DETECTOR))
const int MAX_DLCLOSE_COUNT = 10;
const std::string LIB_ARKWEB_ENGINE = "libarkweb_engine.so";
const std::string PERSIST_ARKWEBCORE_PACKAGE_NAME = "persist.arkwebcore.package_name";
const std::string EL1_BUNDLE_PUBLIC = "/data/app/el1/bundle/public/";
const std::string SANDBOX_REAL_PATH = "/data/storage/el1/bundle/arkwebcore";
const std::string APPSPAWN_PRELOAD_ARKWEB_ENGINE = "const.startup.appspawn.preload.arkwebEngine";
#endif

// 前向声明
static ArkWebEngineVersion CalculateActiveWebEngineVersion();

static void SetIntParameter(const std::string& key, const int& intValue)
{
    std::string valueStr = std::to_string(intValue);
    if (OHOS::system::SetParameter(key, valueStr)) {
        WVLOG_I("Successfully set integer param %{public}s with %{public}s", key.c_str(), valueStr.c_str());
    } else {
        WVLOG_E("Failed to set integer param %{public}s with %{public}s", key.c_str(), valueStr.c_str());
    }
}

static void ProcessDefaultParam(const Json::Value& value)
{
    if (!value.isInt()) {
        WVLOG_E("Unsupported type for param web.engine.default, must be integer");
        return;
    }

    int intValue = value.asInt();
    if (intValue != static_cast<int>(ArkWebEngineType::LEGACY) &&
        intValue != static_cast<int>(ArkWebEngineType::EVERGREEN)) {
        WVLOG_E("Invalid value for web.engine.default: %{public}d, "
            "must be LEGACY(%{public}d) or EVERGREEN(%{public}d)",
            intValue,
            static_cast<int>(ArkWebEngineType::LEGACY),
            static_cast<int>(ArkWebEngineType::EVERGREEN));
        return;
    }

    SetIntParameter("web.engine.default", intValue);
}

static void ProcessEnforceParam(const Json::Value& value)
{
    if (!value.isInt()) {
        WVLOG_E("Unsupported type for param web.engine.enforce, must be integer");
        return;
    }

    int intValue = value.asInt();
    if (intValue != static_cast<int>(ArkWebEngineType::EVERGREEN)) {
        WVLOG_E("Invalid value for web.engine.enforce: %{public}d, "
            "must be EVERGREEN(%{public}d)",
            intValue,
            static_cast<int>(ArkWebEngineType::EVERGREEN));
        return;
    }

    SetIntParameter("web.engine.enforce", intValue);
}

static void ProcessLegacyAppParam(const Json::Value& value)
{
    if (!value.isArray()) {
        WVLOG_E("Unsupported type for param web.engine.legacyApp, must be array");
        return;
    }

    auto appSet = std::make_unique<std::unordered_set<std::string>>();
    for (const auto& item : value) {
        if (item.isString()) {
            appSet->insert(item.asString());
        } else {
            WVLOG_E("Non-string item found in array for web.engine.legacyApp, skipping.");
        }
    }

    g_legacyApp = std::move(appSet);
    WVLOG_I("Successfully stored legacyApp in heap memory using smart pointer.");
}

static void ProcessDataMigrateAppParam(const Json::Value& value)
{
    if (!value.isArray()) {
        WVLOG_E("Unsupported type for param web.engine.dataMigrateApp, must be array");
        return;
    }

    auto appSet = std::make_unique<std::unordered_set<std::string>>();
    for (const auto& item : value) {
        if (item.isString()) {
            appSet->insert(item.asString());
        } else {
            WVLOG_E("Non-string item found in array for web.engine.dataMigrateApp, skipping.");
        }
    }

    g_dataMigrateApp = std::move(appSet);
    WVLOG_I("Successfully stored dataMigrateApp in heap memory using smart pointer.");
}

static void ProcessParamItem(const std::string& key, const Json::Value& value)
{
    if (key.find(WEB_PARAM_PREFIX) != 0) {
        WVLOG_E("Skipping param with incorrect prefix %{public}s", key.c_str());
        return;
    }

    if (key == "web.engine.default") {
        ProcessDefaultParam(value);
    } else if (key == "web.engine.enforce") {
        ProcessEnforceParam(value);
    } else if (key == "web.engine.legacyApp") {
        ProcessLegacyAppParam(value);
    } else if (key == "web.engine.dataMigrateApp") {
        ProcessDataMigrateAppParam(value);
    } else {
        WVLOG_E("Unsupported key for param %{public}s", key.c_str());
    }
}

static void ProcessJsonConfig(const Json::Value& root)
{
    if (!root.isObject()) {
        WVLOG_E("Root element is not a JSON object");
        return;
    }

    Json::Value::Members keys = root.getMemberNames();
    for (const auto& key : keys) {
        const Json::Value& value = root[key];
        ProcessParamItem(key, value);
    }
}

static bool GetVersionString(const std::string& versionFilePath, std::string& versionStr)
{
    std::ifstream file(versionFilePath);
    if (!file.is_open()) {
        WVLOG_E("can not open version file: %{public}s", versionFilePath.c_str());
        return false;
    }

    if (std::getline(file, versionStr)) {
        return true;
    }

    WVLOG_E("%{public}s is empty.", versionFilePath.c_str());
    return false;
}

static bool HandleVersionString(const std::string& versionStr, long long& versionNum)
{
    std::regex pattern(R"(version\s{0,1}=\s{0,1}(\d{1,3})\.(\d{1,3})\.(\d{1,3})\.(\d{1,3}))");
    std::smatch matches;
    if (std::regex_match(versionStr, matches, pattern)) {
        std::ostringstream versionStream;
        for (size_t i = 1; i < matches.size(); ++i) {
            int num = std::stoi(matches[i].str());
            versionStream << std::setw(VERSION_TAG_LEN) << std::setfill('0') << num;
        }
        versionNum = std::stoll(versionStream.str());
        return true;
    }

    WVLOG_E("incorrect version format, must be aa.bb.xx.yy: %{public}s", versionStr.c_str());
    return false;
}

static bool CheckCloudCfgVersion(const std::string& systemParamVersionPath,
    const std::string& updateParamVersionPath)
{
    std::string systemParamVersionStr;
    std::string updateParamVersionStr;
    if (!GetVersionString(systemParamVersionPath, systemParamVersionStr) ||
        !GetVersionString(updateParamVersionPath, updateParamVersionStr)) {
        return false;
    }

    long long systemParamVersionNum;
    long long updateParamVersionNum;
    if (!HandleVersionString(systemParamVersionStr, systemParamVersionNum) ||
        !HandleVersionString(updateParamVersionStr, updateParamVersionNum)) {
        return false;
    }

    if (updateParamVersionNum >= systemParamVersionNum) {
        WVLOG_I("web param update version %{public}lld is more than system version %{public}lld, update valid.",
            updateParamVersionNum, systemParamVersionNum);
        return true;
    } else {
        WVLOG_I("web param update version %{public}lld is not more than system version %{public}lld, update invalid.",
            updateParamVersionNum, systemParamVersionNum);
        return false;
    }
}

static void ParseCloudCfg()
{
    std::ifstream jsonFile(JSON_CONFIG_PATH.c_str());
    if (!jsonFile.is_open()) {
        WVLOG_E("Failed to open file %{public}s, reason: %{public}s", JSON_CONFIG_PATH.c_str(), strerror(errno));
        return;
    }

    if (!CheckCloudCfgVersion(SYSTEM_PARAM_VERSION_PATH, UPDATE_PARAM_VERSION_PATH)) {
        return;
    }

    Json::Value root;
    Json::CharReaderBuilder readerBuilder;
    std::string parseErrors;
    bool parsingSuccessful = Json::parseFromStream(readerBuilder, jsonFile, &root, &parseErrors);
    if (!parsingSuccessful) {
        WVLOG_E("JSON parse failed, parseErrors: %{public}s", parseErrors.c_str());
        return;
    }

    ProcessJsonConfig(root);
}

void SelectWebcoreBeforeProcessRun(const std::string& appBundleName)
{
    WVLOG_I("SelectWebcoreBeforeProcessRun for app %{public}s.", appBundleName.c_str());

    if (g_legacyApp && g_legacyApp->find(appBundleName) != g_legacyApp->end()) {
        g_cloudEnableAppVersion = static_cast<int>(ArkWebEngineType::LEGACY);
    }

    g_activeEngineVersion = CalculateActiveWebEngineVersion();

    g_legacyApp.reset();
}

bool IsDataMigrate(const std::string& appBundleName)
{
    WVLOG_I("IsDataMigrate for app %{public}s.", appBundleName.c_str());
    if (g_dataMigrateApp && g_dataMigrateApp->find(DATA_MIGRATE_APP_NONE) != g_dataMigrateApp->end()) {
        return false;
    }
    if (g_dataMigrateApp && g_dataMigrateApp->find(DATA_MIGRATE_APP_ALL) != g_dataMigrateApp->end()) {
        return true;
    }
    return g_dataMigrateApp && g_dataMigrateApp->find(appBundleName) != g_dataMigrateApp->end();
}

void PreloadArkWebLibForBrowser()
{
    ParseCloudCfg();
    struct stat statbuf;
    if (!(fstatat(AT_FDCWD, PRECONFIG_LEGACY_HAP_PATH.c_str(), &statbuf, AT_SYMLINK_NOFOLLOW) == 0)) {
        if (OHOS::system::SetParameter("web.engine.enforce",
                std::to_string(static_cast<int>(ArkWebEngineType::EVERGREEN)))) {
            WVLOG_I("Set param web.engine.enforce with %{public}d", static_cast<int>(ArkWebEngineType::EVERGREEN));
        } else {
            WVLOG_I("Set param web.engine.enforce with %{public}d failed",
                    static_cast<int>(ArkWebEngineType::EVERGREEN));
        }
    }
    g_activeEngineVersion = CalculateActiveWebEngineVersion();
    return;
}

void setActiveWebEngineVersion(ArkWebEngineVersion version)
{
    if (g_webEngineInitFlag) {
        WVLOG_E("library resources have been loaded, can't set appEngineVersion");
        return;
    }

    if (static_cast<int>(version) != static_cast<int>(ArkWebEngineType::LEGACY) &&
      static_cast<int>(version) != static_cast<int>(ArkWebEngineType::EVERGREEN) &&
      static_cast<int>(version) != static_cast<int>(ArkWebEngineVersion::SYSTEM_DEFAULT) &&
      static_cast<int>(version) != static_cast<int>(ArkWebEngineVersion::SYSTEM_EVERGREEN)) {
        WVLOG_I("set EngineVersion not support, setVersion: %{public}d", static_cast<int>(version));
        return;
    }
    g_appEngineVersion = static_cast<int>(version);
    WVLOG_I("app setActiveWebEngineVersion: %{public}d", g_appEngineVersion);
    g_activeEngineVersion = CalculateActiveWebEngineVersion();
}

void SetActiveWebPlayGround(ArkWebEngineVersion version)
{
    if (g_webEngineInitFlag) {
        WVLOG_E("library resources have been loaded, can't set appEngineVersion");
        return;
    }

    if (static_cast<int>(version) != static_cast<int>(ArkWebEngineType::PLAYGROUND)) {
        WVLOG_E("set EngineVersion not support, setVersion: %{public}d", static_cast<int>(version));
        return;
    }
    g_appEngineVersion = static_cast<int>(version);
    WVLOG_I("app setActiveWebEngineVersion: %{public}d", g_appEngineVersion);
    g_activeEngineVersion = version;
}

void SetActiveWebEngineVersionInner(ArkWebEngineVersion version)
{
    g_activeEngineVersion = version;
}

void SetBundleNameInner(const std::string& bundleName)
{
    std::lock_guard<std::mutex> lock(g_appInfoMutex);
    g_bundleName = bundleName;
}

void SetApiVersionInner(const std::string& apiVersion)
{
    std::lock_guard<std::mutex> lock(g_appInfoMutex);
    g_apiVersion = apiVersion;
}

void SetAppVersionInner(const std::string& appVersion)
{
    std::lock_guard<std::mutex> lock(g_appInfoMutex);
    g_appVersion = appVersion;
}

std::string ExtractAndRemoveParam(std::string& renderCmd, const std::string& prefix)
{
    if (prefix.empty()) {
        WVLOG_E("prefix is empty");
        return "";
    }
    size_t posLeft = renderCmd.rfind(prefix);
    if (posLeft == std::string::npos) {
        WVLOG_E("not found arg");
        return "";
    }
    size_t posRight = posLeft + prefix.size();
    size_t posEnd = renderCmd.find('#', posRight);
    std::string value = (posEnd == std::string::npos) ?
                            renderCmd.substr(posRight) :
                            renderCmd.substr(posRight, posEnd - posRight);

    // remove arg
    size_t eraseLength = (posEnd == std::string::npos) ?
                            renderCmd.length() - posLeft :
                            posEnd - posLeft;
    renderCmd.erase(posLeft, eraseLength);

    return value;
}

void UpdateAppInfoFromCmdline(std::string& renderCmd)
{
    WVLOG_D("UpdateAppInfoFromCmdline renderCmd: %{public}s", renderCmd.c_str());

    std::string bundleName = ExtractAndRemoveParam(renderCmd, APP_BUNDLE_NAME_PREFIX);
    if (!bundleName.empty()) {
        SetBundleNameInner(bundleName);
    } else {
        WVLOG_W("Bundle name parameter not found in command line.");
    }

    std::string apiVersion = ExtractAndRemoveParam(renderCmd, APP_API_VERSION_PREFIX);
    if (!apiVersion.empty()) {
        SetApiVersionInner(apiVersion);
    } else {
        WVLOG_W("Api version parameter not found in command line.");
    }

    std::string appVersion = ExtractAndRemoveParam(renderCmd, APP_VERSION_PREFIX);
    if (!appVersion.empty()) {
        SetAppVersionInner(appVersion);
    } else {
        WVLOG_W("App version parameter not found in command line.");
    }
}

ArkWebEngineVersionMetrics MapToMetricsVersion(ArkWebEngineVersion version)
{
    switch (version) {
        case ArkWebEngineVersion::SYSTEM_DEFAULT:
            return ArkWebEngineVersionMetrics::SYSTEM_DEFAULT;
        case ArkWebEngineVersion::SYSTEM_EVERGREEN:
            return ArkWebEngineVersionMetrics::SYSTEM_EVERGREEN;
        case ArkWebEngineVersion::PLAYGROUND:
            return ArkWebEngineVersionMetrics::PLAYGROUND;
        case ArkWebEngineVersion::M114:
            return ArkWebEngineVersionMetrics::M114;
        case ArkWebEngineVersion::M132:
            return ArkWebEngineVersionMetrics::M132;
        case ArkWebEngineVersion::M144:
            return ArkWebEngineVersionMetrics::M144;
    }
    WVLOG_W("Unexpected ArkWebEngineVersion: %{public}d", static_cast<int32_t>(version));
    return ArkWebEngineVersionMetrics::UNKNOWN_METRICS;
}

ArkWebEngineVersion getActiveWebEngineVersion()
{
    return g_activeEngineVersion;
}

std::string GetBundleName()
{
    std::lock_guard<std::mutex> lock(g_appInfoMutex);
    return g_bundleName;
}

std::string GetApiVersion()
{
    std::lock_guard<std::mutex> lock(g_appInfoMutex);
    return g_apiVersion;
}

std::string GetAppVersion()
{
    std::lock_guard<std::mutex> lock(g_appInfoMutex);
    return g_appVersion;
}

ArkWebEngineVersion CalculateActiveWebEngineVersion()
{
    int webEngineEnforce = OHOS::system::GetIntParameter("web.engine.enforce", 0);
    if (webEngineEnforce == static_cast<int>(ArkWebEngineType::EVERGREEN) ||
        webEngineEnforce == static_cast<int>(ArkWebEngineType::LEGACY)) {
        WVLOG_I("CalculateActiveWebEngineVersion, enforce %{public}d", webEngineEnforce);
        return static_cast<ArkWebEngineVersion>(webEngineEnforce);
    }

    if (g_appEngineVersion != static_cast<int>(ArkWebEngineVersion::SYSTEM_DEFAULT)) {
        WVLOG_I("CalculateActiveWebEngineVersion appEngineVersion: %{public}d", g_appEngineVersion);
        if (g_appEngineVersion == static_cast<int>(ArkWebEngineVersion::SYSTEM_EVERGREEN)) {
            return static_cast<ArkWebEngineVersion>(ArkWebEngineType::EVERGREEN);
        }
        return static_cast<ArkWebEngineVersion>(g_appEngineVersion);
    }
    
    if (g_cloudEnableAppVersion == static_cast<int>(ArkWebEngineType::LEGACY)) {
        WVLOG_I("CalculateActiveWebEngineVersion CloudEnableAppVersion: %{public}d", g_cloudEnableAppVersion);
        return static_cast<ArkWebEngineVersion>(g_cloudEnableAppVersion);
    }

    int webEngineDefault = OHOS::system::GetIntParameter("web.engine.default",
        static_cast<int>(ArkWebEngineType::EVERGREEN));
    if (webEngineDefault != static_cast<int>(ArkWebEngineType::LEGACY) &&
      webEngineDefault != static_cast<int>(ArkWebEngineType::EVERGREEN)) {
        WVLOG_E("CalculateActiveWebEngineVersion, webEngineDefault error: %{public}d", webEngineDefault);
        return static_cast<ArkWebEngineVersion>(ArkWebEngineType::EVERGREEN);
    }

    return static_cast<ArkWebEngineVersion>(webEngineDefault);
}

ArkWebEngineType getActiveWebEngineType()
{
    return static_cast<ArkWebEngineType>(getActiveWebEngineVersion());
}

bool IsActiveWebEngineEvergreen()
{
    if (getActiveWebEngineType() == ArkWebEngineType::EVERGREEN) {
        return true;
    }
    return false;
}

void LogForUnsupportedFunc(ArkWebEngineVersion version, const char* msg)
{
    WVLOG_W("%{public}s unsupported engine version: %{public}d",
        msg, static_cast<int>(version));
}

std::string GetArkwebLibPath()
{
    std::string path;
    if (getActiveWebEngineType() == ArkWebEngineType::LEGACY) {
        path =  ARK_WEB_CORE_LEGACY_HAP_LIB_PATH;
    } else {
        path = ARK_WEB_CORE_HAP_LIB_PATH;
#if defined(IS_ASAN) && defined(webview_arm64)
        struct stat statbuf;
        if ((fstatat(AT_FDCWD, WEBVIEW_RELATIVE_SANDBOX_PATH_FOR_LIBRARY.c_str(),
            &statbuf, AT_SYMLINK_NOFOLLOW) == 0)) {
            path = ARK_WEB_CORE_HAP_LIB_PATH_ASAN;
        }
#endif
    }
    WVLOG_I("get arkweb lib path: %{public}s", path.c_str());
    return path;
}

std::string GetArkwebLibPathForMock(const std::string& mockBundlePath)
{
    std::string path;
    if (!mockBundlePath.empty()) {
        path = mockBundlePath + "/" + ARK_WEB_CORE_PATH_FOR_MOCK;
    } else {
        path = ARK_WEB_CORE_MOCK_HAP_LIB_PATH;
    }
    WVLOG_I("get arkweb lib mock path: %{public}s", path.c_str());
    return path;
}

std::string GetArkwebNameSpace()
{
    std::string ns;
    if (getActiveWebEngineType() == ArkWebEngineType::PLAYGROUND) {
        ns = "nweb_ns_playground";
    } else if (getActiveWebEngineType() == ArkWebEngineType::LEGACY) {
        ns = "nweb_ns_legacy";
    } else {
        ns = "nweb_ns";
    }
    WVLOG_I("get arkweb name space: %{public}s", ns.c_str());
    return ns;
}

std::string GetArkwebRelativePathForBundle()
{
    std::string path;
#if defined(IS_ASAN) && defined(webview_arm64)
    struct stat statbuf;
    if (!(fstatat(AT_FDCWD, WEBVIEW_RELATIVE_SANDBOX_PATH_FOR_LIBRARY.c_str(), &statbuf, AT_SYMLINK_NOFOLLOW) == 0)) {
        path = "arkwebcore/libs/arm64";
    } else {
        path = ARK_WEB_CORE_ASAN_PATH_FOR_BUNDLE;
    }
#else
    if (getActiveWebEngineType() == ArkWebEngineType::LEGACY) {
        path = ARK_WEB_CORE_LEGACY_PATH_FOR_BUNDLE;
    } else {
        path = ARK_WEB_CORE_PATH_FOR_BUNDLE;
    }
#endif
    WVLOG_I("get arkweb relative bundle path: %{public}s", path.c_str());
    return path;
}

std::string GetArkwebRelativePathForMock()
{
    std::string path = ARK_WEB_CORE_PATH_FOR_MOCK;
    WVLOG_I("get arkweb mock path: %{public}s", path.c_str());
    return path;
}

std::string GetArkwebInstallPath()
{
    std::vector<std::string> legacyPaths = {SANDBOX_LEGACY_HAP_PATH, PRECONFIG_LEGACY_HAP_PATH,};
    std::vector<std::string> greenPaths = {SANDBOX_EVERGREEN_HAP_PATH, PRECONFIG_EVERGREEN_HAP_PATH,
        PRECONFIG_EVERGREEN_WATCH_HAP_PATH};

    std::vector<std::string> workPaths;
    if (getActiveWebEngineType() == ArkWebEngineType::LEGACY) {
        workPaths = legacyPaths;
    } else {
        workPaths = greenPaths;
    }

    std::string installPath = "";
    struct stat statbuf;
    for (auto path : workPaths) {
        if (fstatat(AT_FDCWD, path.c_str(), &statbuf, AT_SYMLINK_NOFOLLOW) == 0) {
            installPath = path;
            break;
        }
    }
    if (installPath == "") {
        WVLOG_E("failed to find hap path");
    }
    return installPath;
}

void* ArkWebBridgeHelperLoadLibFile(int openMode, const std::string& libFilePath,
    bool isPrintLog = true) 
{
    void* libFileHandler = ::dlopen(libFilePath.c_str(), openMode);
    if (!libFileHandler) {
        if (isPrintLog) {
            WVLOG_E("failed to load lib file %{public}s", libFilePath.c_str());
        }
        return nullptr;
    }

    if (isPrintLog) {
        WVLOG_I("succeed to load lib file %{public}s", libFilePath.c_str());
    }
    return libFileHandler;
}

void* ArkWebBridgeHelperLoadLibFile(int openMode, const std::string& libNsName,
    const std::string& libDirPath, const std::string& libFileName, bool isPrintLog = true)
{
    Dl_namespace dlns;

    dlns_init(&dlns, libNsName.c_str());

    int ret = dlns_create(&dlns, libDirPath.c_str());
    if (ret != 0) {
        WVLOG_E("dlns_create failed for '%{public}s': %{public}s (errno=%{public}d)", 
                libDirPath.c_str(), strerror(errno), ret);
    }

    Dl_namespace ndkns;
    ret = dlns_get("ndk", &ndkns);
    if (ret != 0) {
        WVLOG_E("dlns_get(ndk) failed: %{public}s (errno=%{public}d)", strerror(errno), ret);
    }

    ret = dlns_inherit(&dlns, &ndkns, "allow_all_shared_libs");
    if (ret != 0) {
        WVLOG_E("dlns_inherit failed: %{public}s (errno=%{public}d)", strerror(errno), ret);
    }

    void* libFileHandler = dlopen_ns(&dlns, libFileName.c_str(), openMode);
    if (!libFileHandler) {
        if (isPrintLog) {
            WVLOG_E(
                "failed to load lib file %{public}s/%{public}s", libDirPath.c_str(), libFileName.c_str());
        }
        return nullptr;
    }

    if (isPrintLog) {
        WVLOG_I(
            "succeed to load lib file %{public}s/%{public}s", libDirPath.c_str(), libFileName.c_str());
    }

    return libFileHandler;
}

void* ArkWebBridgeHelperSharedInit(bool runMode, const std::string& mockBundlePath)
{
    std::string libFileName = "libarkweb_engine.so";

    std::string libDirPath;
    if (runMode) {
        libDirPath = GetArkwebLibPath();
    } else {
        libDirPath = GetArkwebLibPathForMock(mockBundlePath);
    }

    std::string libNsName = GetArkwebNameSpace();

    void* libFileHandler;

#ifdef __MUSL__
    libFileHandler = ArkWebBridgeHelperLoadLibFile(RTLD_NOW | RTLD_GLOBAL, libNsName, libDirPath, libFileName);
#else
    libFileHandler = ArkWebBridgeHelperLoadLibFile(RTLD_NOW, libDirPath + "/" + libFileName)
#endif

    if (libFileHandler != nullptr) {
        g_webEngineInitFlag = true;
        WVLOG_I("g_webEngineInitFlag set to true. setActiveWebEngineVersion will be ignored.");
    }

    return libFileHandler;
}

#if (defined(webview_arm64) && !defined(ASAN_DETECTOR))
bool CreateRealSandboxPath()
{
    namespace fs = std::filesystem;
    std::error_code ec;
    if (fs::exists(SANDBOX_REAL_PATH, ec)) {
        WVLOG_I("CreateRealSandboxPath %{public}s already exists", SANDBOX_REAL_PATH.c_str());
        return true;
    }

    if (fs::create_directories(SANDBOX_REAL_PATH, ec)) {
        return true;
    }

    WVLOG_E("CreateRealSandboxPath create_directories failed");
    return false;
}

#endif
void DlopenArkWebLib()
{
#if (defined(webview_arm64) && !defined(ASAN_DETECTOR))
    if (!OHOS::system::GetBoolParameter(APPSPAWN_PRELOAD_ARKWEB_ENGINE, false)) {
        WVLOG_E("DlopenArkWebLib preload arkweb turn off!");
        return;
    }
    const std::string bundleName = OHOS::system::GetParameter(PERSIST_ARKWEBCORE_PACKAGE_NAME, "");
    if (bundleName.empty()) {
        WVLOG_E("DlopenArkWebLib bundleName is null");
        return;
    }

    if (!CreateRealSandboxPath()) {
        return;
    }

    const std::string realPath = EL1_BUNDLE_PUBLIC + bundleName;
    WVLOG_I("DlopenArkWebLib realPath: %{public}s, SANDBOX_REAL_PATH: %{public}s",
        realPath.c_str(),
        SANDBOX_REAL_PATH.c_str());
    if (mount(realPath.c_str(), SANDBOX_REAL_PATH.c_str(), nullptr, MS_BIND | MS_REC, nullptr) != 0) {
        WVLOG_E("DlopenArkWebLib mount error: %{public}s", strerror(errno));
        return;
    }

    ArkWebBridgeHelperLoadLibFile(
        RTLD_NOW | RTLD_GLOBAL,
        "nweb_ns",
        ARK_WEB_CORE_HAP_LIB_PATH.c_str(),
        LIB_ARKWEB_ENGINE.c_str()
    );
#endif
}

#if (defined(webview_arm64) && !defined(ASAN_DETECTOR))
static bool IsNWebLibLoaded(Dl_namespace dlns)
{
    void* handler = dlopen_ns(&dlns, LIB_ARKWEB_ENGINE.c_str(), RTLD_NOW | RTLD_NOLOAD);
    if (handler) {
        dlclose(handler);
        return true;
    }
    return false;
}
#endif

int DlcloseArkWebLib()
{
#if (defined(webview_arm64) && !defined(ASAN_DETECTOR))
    if (!OHOS::system::GetBoolParameter(APPSPAWN_PRELOAD_ARKWEB_ENGINE, false)) {
        WVLOG_E("DlcloseArkWebLib preload arkweb turn off!");
        return 0;
    }
    Dl_namespace dlns;
    if (dlns_get("nweb_ns", &dlns) != 0) {
        WVLOG_I("Failed to get nweb_ns");
        return 0;
    }

    void* webEngineHandle = dlopen_ns(&dlns, LIB_ARKWEB_ENGINE.c_str(), RTLD_NOW | RTLD_NOLOAD);
    if (!webEngineHandle) {
        WVLOG_E("FAILED to find %{public}s, error: %{public}s", LIB_ARKWEB_ENGINE.c_str(), dlerror());
        return 0;
    }

    int cnt = MAX_DLCLOSE_COUNT;
    do {
        cnt--;
        dlclose(webEngineHandle);
    } while (cnt > 0 && IsNWebLibLoaded(dlns));

    if (cnt == 0 && IsNWebLibLoaded(dlns)) {
        return -1;
    }
#endif
    return 0;
}

// Check if target exists or not.
bool CheckTargetExists(const std::filesystem::path& path)
{
    std::error_code ec;
    bool targetExists = std::filesystem::exists(path, ec);
    if (ec) {
        WVLOG_E("CheckTargetExists: error checking target: %{public}s.", ec.message().c_str());
        return false;
    }
    return targetExists;
}

/**
 * Create arkweb engine sandbox in appspawn process when appspawn preload arkweb engine is disabled and
 * share relro is enabled.
 */
void CreateArkWebSandboxPathIfNeed()
{
#if (defined(webview_arm64) && !defined(ASAN_DETECTOR))
    if (CheckTargetExists(SANDBOX_REAL_PATH)) {
        WVLOG_I("%{public}s already exists.", SANDBOX_REAL_PATH.c_str());
        return;
    }
    bool shareRelroEnabled = OHOS::system::GetBoolParameter("web.shareRelro.enabled", false);
    bool preloadArkwebEngineEnabled = OHOS::system::GetBoolParameter(APPSPAWN_PRELOAD_ARKWEB_ENGINE, false);
    WVLOG_I("%{public}s does not exist, share relro enabled: %{public}d, preload arkweb engine enabled: %{public}d.",
            SANDBOX_REAL_PATH.c_str(), shareRelroEnabled, preloadArkwebEngineEnabled);
    if (!preloadArkwebEngineEnabled && shareRelroEnabled) {
        CreateRealSandboxPath();
    }
#else
    WVLOG_I("This is not webview_arm64, no need to create arkweb sandbox path.");
#endif
}

/**
 * Mount akrweb engine lib to arkweb engine sandbox when appspawn preload arkweb engine is disabled and
 * share relro is enabled.
 */
void MountArkwebEngineLib()
{
#if (defined(webview_arm64) && !defined(ASAN_DETECTOR))
    if (!CheckTargetExists(SANDBOX_REAL_PATH)) {
        WVLOG_I("%{public}s does not exist.", SANDBOX_REAL_PATH.c_str());
        return;
    }

    const std::string bundleName = OHOS::system::GetParameter(PERSIST_ARKWEBCORE_PACKAGE_NAME, "");
    if (bundleName.empty()) {
        WVLOG_E("bundleName is null.");
        return;
    }

    const std::string realPath = EL1_BUNDLE_PUBLIC + bundleName;
    WVLOG_I("RealPath: %{public}s, SANDBOX_REAL_PATH: %{public}s.",
            realPath.c_str(), SANDBOX_REAL_PATH.c_str());
    if (mount(realPath.c_str(), SANDBOX_REAL_PATH.c_str(), nullptr, MS_BIND | MS_REC, nullptr) != 0) {
        WVLOG_E("Mount error: %{public}s.", strerror(errno));
        return;
    }
#else
    WVLOG_I("This is not webview_arm64, no need to mount arkweb engine lib.");
#endif
}

bool NeedShareRelro()
{
    // Check if share relro is enabled
    g_shareRelroEnabled = OHOS::system::GetBoolParameter("web.shareRelro.enabled", false);
    if (!g_shareRelroEnabled) {
        WVLOG_I("NeedShareRelro: share relro is disabled.");
        return false;
    }

    std::string arkwebLibPath = GetArkwebLibPath();
    std::filesystem::path arkwebEngineLibPath = arkwebLibPath + "/" + ARK_WEB_ENGINE_LIB_NAME;
    g_arkwebEngineAccessible = CheckTargetExists(arkwebEngineLibPath);
    if (!g_arkwebEngineAccessible) {
        WVLOG_I("Arkweb engine lib is not accessible, try to mount it.");
        MountArkwebEngineLib();
        g_arkwebEngineAccessible = CheckTargetExists(arkwebEngineLibPath);
    }

    // Need to share relro only when share relro is enabled and arkweb engine lib is accessible
    WVLOG_I("NeedShareRelro:share relro is enabled, webEngine accessible:%{public}d.", g_arkwebEngineAccessible);
    return g_arkwebEngineAccessible;
}

bool ReserveAddressSpace()
{
    if (!NeedShareRelro()) {
        return false;
    }
    void* addr = mmap(nullptr, RESERVED_VMA_SIZE, PROT_NONE, MAP_PRIVATE | MAP_ANONYMOUS, -1, 0);
    if (addr == MAP_FAILED) {
        WVLOG_E("ReserveAddressSpace: mmap failed, error=[%{public}s]", strerror(errno));
        return false;
    }
    prctl(PR_SET_VMA, PR_SET_VMA_ANON_NAME, addr, RESERVED_VMA_SIZE, "libwebview reservation");
    g_reservedAddress = addr;
    g_reservedSize = RESERVED_VMA_SIZE;
    WVLOG_I("1.Reserve address space success, size:%{public}zd bytes", g_reservedSize);
    return true;
}

void* CreateRelroFile(const std::string& lib, Dl_namespace* dlns)
{
    if (!g_shareRelroEnabled || !g_arkwebEngineAccessible) {
        WVLOG_I("CreateRelroFile:share relro enabled: %{public}d, webEngine accessible:%{public}d.",
                g_shareRelroEnabled, g_arkwebEngineAccessible);
        return nullptr;
    }
    if (!g_reservedAddress) {
        WVLOG_E("CreateRelroFile: the reserved address is nullptr.");
        return nullptr;
    }

    WVLOG_I("2.CreateRelroFile begin.");
    if (unlink(NWEB_RELRO_PATH.c_str()) != 0 && errno != ENOENT) {
        WVLOG_E("CreateRelroFile unlink failed, error=[%{public}s]", strerror(errno));
    }
    uint64_t relroFdTag = LOG_RENDER_DOMAIN;
    int relroFd = open(NWEB_RELRO_PATH.c_str(), O_RDWR | O_TRUNC | O_CLOEXEC | O_CREAT, S_IRUSR | S_IRGRP | S_IROTH);
    if (relroFd < 0) {
        WVLOG_E("CreateRelroFile open failed, error=[%{public}s]", strerror(errno));
        return nullptr;
    }
    fdsan_exchange_owner_tag(relroFd, 0, relroFdTag);

    WVLOG_I("CreateRelroFile: dlopen_ns_ext extinfo with reserved address.");
    dl_extinfo extinfo = {
        .flag = DL_EXT_WRITE_RELRO | DL_EXT_RESERVED_ADDRESS_RECURSIVE | DL_EXT_RESERVED_ADDRESS,
        .relro_fd = relroFd,
        .reserved_addr = g_reservedAddress,
        .reserved_size = g_reservedSize,
    };
    void* result = dlopen_ns_ext(dlns, lib.c_str(), RTLD_NOW | RTLD_GLOBAL, &extinfo);
    if (!result) {
        WVLOG_E("CreateRelroFile: dlopen_ns_ext failed, error=[%{public}s]", strerror(errno));
    }
    fdsan_close_with_tag(relroFd, relroFdTag);
    return result;
}

void CreateRelroFileInSubProc()
{
    pid_t pid = fork();
    if (pid < 0) {
        WVLOG_E("Fork sub process failed.");
    } else if (pid == 0) {
        WVLOG_I("Fork sub process success.");
        if (prctl(PR_SET_NAME, "shared_relro") != 0) {
            WVLOG_E("Set sub process name failed, error=[%{public}s]", strerror(errno));
        }
        if (setuid(SHARED_RELRO_UID) != 0 || setgid(SHARED_RELRO_UID)  != 0) {
            WVLOG_E("Set uid/gid failed, error=[%{public}s]", strerror(errno));
            exit(0);
        }
        if (RestoreconRecurse(SHARED_RELRO_DIR.c_str()) != 0) {
            WVLOG_E("Restorecon failed, error=[%{public}s]", strerror(errno));
            exit(0);
        }

        const std::string libNsName = GetArkwebNameSpace();
        const std::string libpath = GetArkwebLibPath();
        Dl_namespace dlns;
        dlns_init(&dlns, libNsName.c_str());
        dlns_create(&dlns, libpath.c_str());
        Dl_namespace ndkns;
        dlns_get("ndk", &ndkns);
        dlns_inherit(&dlns, &ndkns, "allow_all_shared_libs");

        void* webEngineHandle = CreateRelroFile(ARK_WEB_ENGINE_LIB_NAME, &dlns);
        if (webEngineHandle) {
            WVLOG_I("Create relro file success.");
        } else {
            WVLOG_E("Create relro file failed.");
        }
        exit(0);
    }
}

void* LoadWithRelroFile(const std::string& lib, Dl_namespace* dlns)
{
    if (!g_shareRelroEnabled || !g_arkwebEngineAccessible) {
        WVLOG_I("LoadWithRelroFile:share relro enabled: %{public}d, webEngine accessible:%{public}d.",
                g_shareRelroEnabled, g_arkwebEngineAccessible);
        return nullptr;
    }
    if (!g_reservedAddress) {
        WVLOG_E("LoadWithRelroFile: the reserved address is nullptr.");
        return nullptr;
    }

    WVLOG_I("3.LoadWithRelroFile begin.");
    uint64_t relroFdTag = LOG_RENDER_DOMAIN;
    int relroFd = open(NWEB_RELRO_PATH.c_str(), O_RDONLY);
    if (relroFd < 0) {
        WVLOG_E("LoadWithRelroFile open failed, error=[%{public}s]", strerror(errno));
        return nullptr;
    }
    fdsan_exchange_owner_tag(relroFd, 0, relroFdTag);

    WVLOG_I("LoadWithRelroFile: dlopen_ns_ext extinfo with reserved address.");
    dl_extinfo extinfo = {
        .flag = DL_EXT_USE_RELRO | DL_EXT_RESERVED_ADDRESS_RECURSIVE | DL_EXT_RESERVED_ADDRESS,
        .relro_fd =relroFd,
        .reserved_addr = g_reservedAddress,
        .reserved_size = g_reservedSize,
    };
    void* result = dlopen_ns_ext(dlns, lib.c_str(), RTLD_NOW | RTLD_GLOBAL, &extinfo);
    fdsan_close_with_tag(relroFd, relroFdTag);
    return result;
}
}