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

#include "ability_manager_errors.h"
#include "ability_manager_service.h"
#include "ability_util.h"
#include "app_scheduler.h"
#include "app_utils.h"
#include "hilog_tag_wrapper.h"
#include "in_process_call_wrapper.h"
#include "global_constant.h"
#include "native_ability_util.h"
#include "parameters.h"
#include "permission_verification.h"
#include "process_options.h"
#include "start_ability_utils.h"
#include "start_options.h"
#include "bundle_mgr_interface.h"

namespace OHOS {
namespace AAFwk {
namespace {
constexpr const char* DEVELOPER_MODE_STATE = "const.security.developermode.state";
}  // namespace
PreloadManagerService &PreloadManagerService::GetInstance()
{
    static PreloadManagerService instance;
    return instance;
}

PreloadManagerService::PreloadManagerService() {}

PreloadManagerService::~PreloadManagerService() {}

int32_t PreloadManagerService::PreloadApplication(const std::string &bundleName, int32_t userId, int32_t appIndex)
{
    CHECK_TRUE_RETURN_RET(!AppUtils::GetInstance().IsPreloadApplicationEnabled() || appIndex != 0,
        ERR_CAPABILITY_NOT_SUPPORT, "preload application not supported");
    CHECK_TRUE_RETURN_RET(!PermissionVerification::GetInstance()->VerifyPreloadApplicationPermission(),
        ERR_PERMISSION_DENIED, "no preload permission");
    Want launchWant;
    AppExecFwk::AbilityInfo abilityInfo;
    if (auto ret = PreloadApplicationVerification(bundleName, userId, appIndex, launchWant,
        abilityInfo); ret != ERR_OK) {
        TAG_LOGE(AAFwkTag::ABILITYMGR, "verify PreloadApplication failed");
        return ret;
    }

    AppExecFwk::AppPreloadPhase appPreloadPhase = abilityInfo.applicationInfo.appPreloadPhase;
    if (appPreloadPhase <= AppExecFwk::AppPreloadPhase::DEFAULT ||
        appPreloadPhase > AppExecFwk::AppPreloadPhase::WINDOW_STAGE_CREATED) {
        TAG_LOGE(AAFwkTag::ABILITYMGR, "invalid preload phase:%{public}d", static_cast<int32_t>(appPreloadPhase));
        return ERR_INVALID_APP_PRELOAD_PHASE;
    }

    if (appPreloadPhase <= AppExecFwk::AppPreloadPhase::ABILITY_STAGE_CREATED) {
        TAG_LOGI(AAFwkTag::ABILITYMGR, "preload to phase:%{public}d", static_cast<int32_t>(appPreloadPhase));
        AppExecFwk::PreloadPhase preloadPhase = static_cast<AppExecFwk::PreloadPhase>(appPreloadPhase);
        return DelayedSingleton<AppScheduler>::GetInstance()->PreloadApplicationByPhase(
            bundleName, userId, appIndex, preloadPhase);
    }
    TAG_LOGI(AAFwkTag::ABILITYMGR, "preload to window stage create");
    CHECK_TRUE_RETURN_RET(abilityInfo.launchMode == AppExecFwk::LaunchMode::STANDARD, ERR_CAPABILITY_NOT_SUPPORT,
        "not support multiton");
    StartOptions options;
    options.processOptions = std::make_shared<ProcessOptions>();
    options.processOptions->startupVisibility = StartupVisibility::STARTUP_HIDE;
    options.processOptions->isPreloadStart = true;
    return DelayedSingleton<AbilityManagerService>::GetInstance()->StartAbility(launchWant, options, nullptr, userId);
}

int32_t PreloadManagerService::LaunchGameCustomized(const std::string &bundleName, int32_t userId, int32_t appIndex)
{
    Want launchWant;
    AppExecFwk::AbilityInfo abilityInfo;
    if (auto ret = PreloadApplicationVerification(bundleName, userId, appIndex, launchWant,
        abilityInfo); ret != ERR_OK) {
        TAG_LOGE(AAFwkTag::ABILITYMGR, "verify preload game failed");
        return ret;
    }

    bool isGameSACall = IPCSkeleton::GetCallingUid() == AbilityRuntime::GlobalConstant::GAME_SA_UID;
    bool isShellCall = AAFwk::PermissionVerification::GetInstance()->IsShellCall();
    bool isDevelopMode = OHOS::system::GetBoolParameter(DEVELOPER_MODE_STATE, false);
    bool isDebugApp = abilityInfo.applicationInfo.appProvisionType == AppExecFwk::Constants::APP_PROVISION_TYPE_DEBUG;
    if (!(isGameSACall || (isShellCall && isDevelopMode && isDebugApp))) {
        TAG_LOGE(AAFwkTag::ABILITYMGR, "Permission check failed: callingUid: %{public}d, isGameSACall = %{public}d, "
            "isShellCall = %{private}d, isDevelopMode = %{private}d, isDebugApp = %{private}d",
            IPCSkeleton::GetCallingUid(), isGameSACall, isShellCall, isDevelopMode, isDebugApp);
        return CHECK_PERMISSION_FAILED;
    }

    StartAbilityWrapParam startAbilityWrapParam;
    startAbilityWrapParam.want = std::move(launchWant);
    startAbilityWrapParam.userId = userId;
    startAbilityWrapParam.isGamePrelaunch = true;
    return DelayedSingleton<AbilityManagerService>::GetInstance()->StartAbilityInner(startAbilityWrapParam);
}

int32_t PreloadManagerService::PreloadApplicationVerification(const std::string &bundleName, int32_t userId,
    int32_t appIndex, Want &want, AppExecFwk::AbilityInfo &abilityInfo)
{
    userId = DelayedSingleton<AbilityManagerService>::GetInstance()->GetValidUserId(userId);
    CHECK_TRUE_RETURN_RET(!DelayedSingleton<AbilityManagerService>::GetInstance()->JudgeMultiUserConcurrency(userId),
        ERR_CROSS_USER, "multi-user non-concurrent unsatisfied");

    bool isExist = false;
    int32_t ret = ERR_OK;
    CHECK_TRUE_RETURN_RET((ret = DelayedSingleton<AppScheduler>::GetInstance()->CheckPreloadAppRecordExist(
        bundleName, userId, appIndex, isExist)) != ERR_OK, ret, "CheckPreloadAppRecordExist failed");
    CHECK_TRUE_RETURN_RET(isExist, ERR_PRELOAD_APP_RECORD_ALREADY_EXIST, "already started");
    
    auto bundleMgrHelper = AbilityUtil::GetBundleManagerHelper();
    CHECK_POINTER_AND_RETURN(bundleMgrHelper, INNER_ERR);

    auto errCode = IN_PROCESS_CALL(bundleMgrHelper->GetLaunchWantForBundle(bundleName, want, userId));
    if (errCode != ERR_OK) {
        TAG_LOGE(AAFwkTag::ABILITYMGR, "getLaunchWantForBundle returns %{public}d", errCode);
        return errCode;
    }
    CHECK_TRUE_RETURN_RET(!IN_PROCESS_CALL(bundleMgrHelper->QueryAbilityInfo(want,
        AppExecFwk::AbilityInfoFlag::GET_ABILITY_INFO_WITH_APPLICATION |
        AppExecFwk::AbilityInfoFlag::GET_ABILITY_INFO_WITH_METADATA, userId, abilityInfo)),
        RESOLVE_ABILITY_ERR, "failed to get abilityInfo");
    CHECK_TRUE_RETURN_RET(NativeAbilityMetaData::IsWithNative(abilityInfo),
        ERR_CAPABILITY_NOT_SUPPORT, "Native ability not supported");
    return ERR_OK;
}
}  // namespace AAFwk
}  // namespace OHOS