/*
 * 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 <sstream>
#include "ani.h"
#include "ani_common_want.h"
#include "ani_plugin_callback.h"
#include "ani_plugin_callback_mgr.h"
#include "ani_plugin_component_data.h"
#include "ani_plugin_component_util.h"
#include "core/common/ace_engine.h"
#include "core/components/plugin/plugin_component_manager.h"

namespace OHOS::Ace::Ani {
    std::atomic_size_t ANIPluginCallback::uuid_ = 0;
    constexpr char PLUGINCOMPONENT_TEMPLATE_CLASSNAME[] = "@ohos.pluginComponent.PluginComponentTemplateImpl";
    
bool AceJSPluginRequestParam::operator==(const AceJSPluginRequestParam& param) const
{
    AppExecFwk::ElementName leftElement = want_.GetElement();
    AppExecFwk::ElementName rightElement = param.want_.GetElement();
    if (leftElement == rightElement) {
        if (name_ == param.name_ && data_ == param.data_ && jsonPath_ == param.jsonPath_) {
            return true;
        }
    }
    return false;
}

bool AceJSPluginRequestParam::operator!=(const AceJSPluginRequestParam& param) const
{
    return !operator==(param);
}

ANIPluginCallback::ANIPluginCallback(
    CallBackType eventType, ACECallbackInfo& cbInfo, ACEAsyncJSCallbackInfo* jsCallbackInfo)
    : eventType_(eventType), asyncJSCallbackInfo_(jsCallbackInfo)
{
    uuid_++;
    cbInfo_.env = cbInfo.env;
    cbInfo_.callback = cbInfo.callback;
    cbInfo_.containerId = cbInfo.containerId;
}

ANIPluginCallback::~ANIPluginCallback()
{
    if (uuid_ > 0) {
        uuid_--;
    }
    DestroyAllResource();
}

void ANIPluginCallback::DestroyAllResource(void)
{
    if (cbInfo_.env != nullptr && cbInfo_.callback != nullptr) {
        cbInfo_.env->GlobalReference_Delete(cbInfo_.callback);
    }
    cbInfo_.env = nullptr;
    cbInfo_.callback = nullptr;
    cbInfo_.containerId = -1;
    asyncJSCallbackInfo_ = nullptr;
}

void ANIPluginCallback::SetWant(const AAFwk::Want& want)
{
    want_ = want;
}

const AAFwk::Want& ANIPluginCallback::GetWant() const
{
    return want_;
}

void ANIPluginCallback::SetRequestParam(const std::shared_ptr<AceJSPluginRequestParam>& param)
{
    requestParam_ = param;
}

size_t ANIPluginCallback::GetID(void) const
{
    return uuid_;
}

bool ANIPluginCallback::RequestStrictEquals(CallBackType eventType, const AAFwk::Want& want, ACECallbackInfo& cbInfo,
    const std::shared_ptr<AceJSPluginRequestParam>& param)
{
    if (eventType != eventType_) {
        return false;
    }
    AppExecFwk::ElementName leftElement = want.GetElement();
    AppExecFwk::ElementName rightElement = want_.GetElement();
    if (!(leftElement == rightElement)) {
        return false;
    }
    if (param == nullptr || requestParam_ == nullptr) {
        return false;
    }
    if (*param != *requestParam_) {
        return false;
    }
    return AceIsSameFuncFromANI(cbInfo, cbInfo_);
}

int32_t ANIPluginCallback::GetContainerId()
{
    return cbInfo_.containerId;
}

bool ANIPluginCallback::OnEventStrictEquals(CallBackType eventType, const AAFwk::Want& want, ACECallbackInfo& cbInfo)
{
    if (eventType != eventType_) {
        return false;
    }
    AppExecFwk::ElementName leftElement = want.GetElement();
    AppExecFwk::ElementName rightElement = want_.GetElement();
    if (leftElement == rightElement) {
        return AceIsSameFuncFromANI(cbInfo, cbInfo_);
    } else {
        return false;
    }
}

void ANIPluginCallback::OnPushEventInner(const OnPluginUvWorkData* workData)
{
    LOGI("plugin-ani OnPushEventInner start");
    PluginComponentTemplate componentTemplate;
    componentTemplate.SetSource(workData->sourceName);
    componentTemplate.SetAbility(workData->abilityName);
    std::vector<ani_ref> vec;
    auto wantObj = OHOS::AppExecFwk::WrapWant(cbInfo_.env, workData->want);
    vec.push_back(wantObj);
    auto param = MakeCallbackParamForRequestOrPush(componentTemplate, workData->data, workData->extraData);
    vec.push_back(param.pluginTemplateObj);
    vec.push_back(param.aniData);
    vec.push_back(param.aniExtraData);
    ani_ref fnReturnVal;
    auto status = cbInfo_.env->FunctionalObject_Call(reinterpret_cast<ani_fn_object>(cbInfo_.callback),
        vec.size(), vec.data(), &fnReturnVal);
    LOGI("plugin-ani OnPushEventInner end status %{public}d", status);
}

void ANIPluginCallback::OnPushEvent(const AAFwk::Want& want, const PluginComponentTemplate& pluginTemplate,
    const std::string& data, const std::string& extraData)
{
    LOGI("plugin-ani OnPushEvent start");
    if (cbInfo_.env == nullptr || cbInfo_.callback == nullptr) {
        return;
    }

    auto container = AceEngine::Get().GetContainer(cbInfo_.containerId);
    if (!container) {
        return;
    }

    auto taskExecutor = container->GetTaskExecutor();
    if (!taskExecutor) {
        return;
    }
    std::weak_ptr<PluginComponentCallBack> weak = weak_from_this();
    taskExecutor->PostTask(
        [weak, want, sourceName = pluginTemplate.GetSource(), abilityName = pluginTemplate.GetAbility(), data,
            extraData]() {
            OnPluginUvWorkData uvWorkData;
            uvWorkData.want = want;
            uvWorkData.sourceName = sourceName;
            uvWorkData.abilityName = abilityName;
            uvWorkData.data = data;
            uvWorkData.extraData = extraData;
            auto callBack = weak.lock();
            if (callBack) {
                auto jsCallback = std::static_pointer_cast<ANIPluginCallback>(callBack);
                jsCallback->OnPushEventInner(&uvWorkData);
            }
        },
        TaskExecutor::TaskType::UI, "ArkUIPluginPushEventInner");
}

void ANIPluginCallback::SendRequestEventResult(ani_object aniRef)
{
    if (aniRef == nullptr) {
        LOGE("plugin-ani result of request event is null");
        return;
    }
    struct RequestEventResult {
        AAFwk::Want want;
        std::string strTemplate;
        std::string strData;
        std::string strExtraData;
    };

    std::shared_ptr<RequestEventResult> data = std::make_shared<RequestEventResult>();
    data->want = want_;
    GetAniStringPropertyByName(cbInfo_.env, aniRef, "template", data->strTemplate);
    GetAniKVObjectPropertyByName(cbInfo_.env, aniRef, "data", data->strData);
    GetAniKVObjectPropertyByName(cbInfo_.env, aniRef, "extraData", data->strExtraData);
    auto container = AceEngine::Get().GetContainer(cbInfo_.containerId);
    if (!container) {
        return;
    }

    auto taskExecutor = container->GetTaskExecutor();
    if (!taskExecutor) {
        return;
    }
    taskExecutor->PostTask(
        [data]() {
            PluginComponentManager::GetInstance()->ReturnRequest(
                data->want, data->strTemplate, data->strData, data->strExtraData);
        },
        TaskExecutor::TaskType::BACKGROUND, "ArkUIPluginReturnRequest");
}

void ANIPluginCallback::OnRequestEventInner(const OnPluginUvWorkData* workData)
{
    LOGI("plugin-ani OnRequestEventInner start");
    std::vector<ani_ref> vec;
    auto wantObj = OHOS::AppExecFwk::WrapWant(cbInfo_.env, workData->want);
    vec.push_back(wantObj);
    auto nameAni = AceWrapStringToAni(cbInfo_.env, workData->name);
    vec.push_back(nameAni);
    ani_ref aniData = AceWrapStringToObject(cbInfo_.env, workData->data);
    vec.push_back(aniData);
    ani_ref fnReturnVal;
    cbInfo_.env->FunctionalObject_Call(reinterpret_cast<ani_fn_object>(cbInfo_.callback), vec.size(), vec.data(),
        &fnReturnVal);
    ani_boolean isUndefined = true;
    if (ANI_OK != cbInfo_.env->Reference_IsUndefined(fnReturnVal, &isUndefined) || isUndefined) {
        LOGE("plugin-ani result of request event is null");
        return;
    }
    SendRequestEventResult(reinterpret_cast<ani_object>(fnReturnVal));
}

void ANIPluginCallback::OnRequestEvent(const AAFwk::Want& want, const std::string& name, const std::string& data)
{
    LOGI("plugin-ani OnRequestEvent start");
    if (cbInfo_.env == nullptr || cbInfo_.callback == nullptr) {
        return;
    }

    auto container = AceEngine::Get().GetContainer(cbInfo_.containerId);
    if (!container) {
        return;
    }

    auto taskExecutor = container->GetTaskExecutor();
    if (!taskExecutor) {
        return;
    }

    std::weak_ptr<PluginComponentCallBack> weak = weak_from_this();
    taskExecutor->PostTask(
        [weak, want, name, data]() {
            auto callBack = weak.lock();
            if (callBack) {
                OnPluginUvWorkData uvWorkData;
                uvWorkData.want = want;
                uvWorkData.data = data;
                uvWorkData.name = name;
                auto jsCallback = std::static_pointer_cast<ANIPluginCallback>(callBack);
                jsCallback->OnRequestEventInner(&uvWorkData);
            }
        },
        TaskExecutor::TaskType::JS, "ArkUIPluginRequestEventInner");
}

ani_ref ANIPluginCallback::MakePluginTemplateObject(const PluginComponentTemplate& pluginTemplate)
{
    ani_class pluginTemplateCls;
    cbInfo_.env->FindClass(PLUGINCOMPONENT_TEMPLATE_CLASSNAME, &pluginTemplateCls);
    ani_method PTOCtor;
    cbInfo_.env->Class_FindMethod(pluginTemplateCls, "<ctor>", ":", &PTOCtor);
    ani_object PTOObj;
    cbInfo_.env->Object_New(pluginTemplateCls, PTOCtor, &PTOObj);
    cbInfo_.env->Object_SetPropertyByName_Ref(PTOObj, "source", AceWrapStringToAni(cbInfo_.env,
        pluginTemplate.GetSource()));
    cbInfo_.env->Object_SetPropertyByName_Ref(PTOObj, "ability", AceWrapStringToAni(cbInfo_.env,
        pluginTemplate.GetAbility()));
    return PTOObj;
}

CallbackParamForRequest ANIPluginCallback::MakeCallbackParamForRequestOrPush(
    const PluginComponentTemplate& pluginTemplate, const std::string& data, const std::string& extraData)
{
    ani_ref pluginTemplateObj = MakePluginTemplateObject(pluginTemplate);
    ani_ref aniData = AceWrapStringToObject(cbInfo_.env, data);
    ani_ref aniExtraData = AceWrapStringToObject(cbInfo_.env, extraData);
    CallbackParamForRequest param {
        .pluginTemplateObj = pluginTemplateObj,
        .aniData = aniData,
        .aniExtraData = aniExtraData
    };
    return param;
}

void ANIPluginCallback::OnRequestCallBackInner(const OnPluginUvWorkData* workData)
{
    PluginComponentTemplate componentTemplate;
    componentTemplate.SetSource(workData->sourceName);
    componentTemplate.SetAbility(workData->abilityName);
    std::vector<ani_ref> vec;
    ani_ref fnReturnVal;
    auto param = CreateInt(cbInfo_.env, ani_int(0));
    vec.push_back(param);
    auto paramForRequest = MakeCallbackParamForRequestOrPush(componentTemplate, workData->data, workData->extraData);
    vec.push_back(paramForRequest.pluginTemplateObj);
    vec.push_back(paramForRequest.aniData);
    vec.push_back(paramForRequest.aniExtraData);
    auto fnObj = reinterpret_cast<ani_fn_object>(cbInfo_.callback);
    if (fnObj == nullptr) {
        LOGI("plugin-ani OnRequestCallBackInner fnObj is null");
        return;
    }
    auto status = cbInfo_.env->FunctionalObject_Call(fnObj, vec.size(), vec.data(), &fnReturnVal);
    LOGI("plugin-ani OnRequestCallBackInner execute status %{public}d", status);
    cbInfo_.env->GlobalReference_Delete(cbInfo_.callback);
}

void ANIPluginCallback::OnRequestCallBack(
    const PluginComponentTemplate& pluginTemplate, const std::string& data, const std::string& extraData)
{
    ANIPluginCallbackMgr::Instance().UnRegisterEvent(GetID());
    if (cbInfo_.env == nullptr) {
        return;
    }
    uvWorkData_.sourceName = pluginTemplate.GetSource();
    uvWorkData_.abilityName = pluginTemplate.GetAbility();
    uvWorkData_.data = data;
    uvWorkData_.extraData = extraData;

    if (getpid() != gettid()) {
        struct ResultData {
            ANIPluginCallback* context = nullptr;
            std::mutex mtx;
            bool ready = false;
            std::condition_variable cv;
        };

        std::shared_ptr<ResultData> resultData = std::make_shared<ResultData>();
        resultData->context = this;

        auto container = AceEngine::Get().GetContainer(cbInfo_.containerId);
        if (!container) {
            return;
        }

        auto taskExecutor = container->GetTaskExecutor();
        if (!taskExecutor) {
            return;
        }
        taskExecutor->PostTask(
            [resultData]() {
                resultData->context->OnRequestCallBackInner(&resultData->context->uvWorkData_);

                {
                    std::unique_lock<std::mutex> lock(resultData->mtx);
                    resultData->ready = true;
                    resultData->cv.notify_all();
                }
            },
            TaskExecutor::TaskType::JS, "ArkUIPluginRequestCallbackInner");

        {
            std::unique_lock<std::mutex> lock(resultData->mtx);
            if (!resultData->ready) {
                resultData->cv.wait(lock, [&] { return resultData->ready; });
            }
        }
    } else {
        OnRequestCallBackInner(&uvWorkData_);
    }
}
} // namespace OHOS::Ace::Ani