/*
 * Copyright (c) 2024 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 "notification_bar.h"

#include <cstddef>
#include <cstdint>
#include <string>
#include <dlfcn.h>

#include "cxx.h"
#include "image_source.h"
#include "locale_config.h"
#include "log.h"
#include "notification.h"
#include "notification_content.h"
#include "notification_local_live_view_button.h"
#include "notification_local_live_view_content.h"
#include "resource_manager.h"
#include "service/notification_bar/mod.rs.h"
#include "task/config.rs.h"

#include "want_agent_helper.h"
#include "sys_event.h"
#include "system_ability_definition.h"
#include "iservice_registry.h"
#include "app_mgr_interface.h"
#include "init_param.h"
#include "parameters.h"

namespace OHOS::Request {
using namespace Global;

std::mutex g_requestIntlUtilSoMtx;

static constexpr int32_t REQUEST_SERVICE_ID = 3815;

static constexpr int32_t REQUEST_STYLE_SIMPLE = 8;

// static constexpr uint32_t BINARY_SCALE = 1024;
// static constexpr uint32_t PERCENT = 100;
// static constexpr uint32_t FRONT_ZERO = 10;
// static constexpr size_t PLACEHOLDER_LENGTH = 2;

static const std::string CLOSE_ICON_PATH = "/etc/request/xmark.svg";
static const std::string CLOSE_ICON_PATH_DARK = "/etc/request/xmark_dark.svg";

constexpr const char* SYSTEM_COLORMODE = "persist.ace.darkmode";

const std::vector<std::string> RESOURCE_STRING_KEYS = {
    "request_agent_download_file",
    "request_agent_download_success",
    "request_agent_download_fail",
    "request_agent_upload_file",
    "request_agent_upload_success",
    "request_agent_upload_fail",
    "request_agent_task_count",
    "request_agent_download_complete"
};

struct RequestSystemResourceStringInfo {
    std::unordered_map<std::string, std::string> stringMap;
    std::string curSystemLanguage;
    std::mutex mtx;
};

RequestSystemResourceStringInfo g_resourceStringInfo;

/* Check whether the system language is consistent */
bool IsSystemLanguageConsistent(const std::string &curLanguage)
{
    return g_resourceStringInfo.curSystemLanguage == curLanguage;
}

/* Updating the System Resource String Cache */
int UpdateSystemResourceStringMap(std::string &curLanguage)
{
    auto resourceMgr = Resource::GetSystemResourceManagerNoSandBox();
    if (resourceMgr == nullptr) {
        REQUEST_HILOGE("GetSystemResourceManagerNoSandBox failed");
        return -1;
    }
    std::unique_ptr<Resource::ResConfig> config(Resource::CreateResConfig());
    if (config == nullptr) {
        REQUEST_HILOGE("Create ResConfig failed");
        return -1;
    }
    UErrorCode status = U_ZERO_ERROR;
    icu::Locale locale = icu::Locale::forLanguageTag(curLanguage, status);
    config->SetLocaleInfo(locale);
    resourceMgr->UpdateResConfig(*config);

    g_resourceStringInfo.curSystemLanguage = curLanguage.c_str();

    std::string outValue;
    for (size_t i = 0; i < RESOURCE_STRING_KEYS.size(); ++i) {
        auto ret = resourceMgr->GetStringByName(RESOURCE_STRING_KEYS[i].c_str(), outValue);
        if (ret != Resource::RState::SUCCESS) {
            REQUEST_HILOGE("Get system resource string %{public}s failed: %{public}d",
                RESOURCE_STRING_KEYS[i].c_str(), ret);

            /* If an item fails to be obtained, it is marked as the initial state and will be retried next time */
            g_resourceStringInfo.curSystemLanguage.clear();
        }
        g_resourceStringInfo.stringMap[RESOURCE_STRING_KEYS[i]] = outValue;
    }

    Resource::ReleaseSystemResourceManager();

    return 0;
}

void DynamicGetSystemLanguage(char* buffer, size_t bufferSize)
{
    std::lock_guard<std::mutex> lock(g_requestIntlUtilSoMtx);
    const char *libApiLanguageTransferPath = "/system/lib64/libdownload_language_transfer.z.so";
    using GetSystemLanguageFunc = void (*)(char *buffer, size_t bufferSize);
    static GetSystemLanguageFunc getSystemLanguageFunc = nullptr;
    static bool initialized = false;

    if (!initialized) {
        void *handle = dlopen(libApiLanguageTransferPath, RTLD_NOW);
        if (handle == nullptr) {
            const char *err = dlerror();
            REQUEST_HILOGE("libdownload_language_transfer.z.so dlopen failed: %{public}s", err ? err : "unknown");
            return;
        }

        getSystemLanguageFunc = reinterpret_cast<GetSystemLanguageFunc>(
            dlsym(handle, "GetSystemLanguageByIntl"));
        if (getSystemLanguageFunc == nullptr) {
            const char *err = dlerror();
            REQUEST_HILOGE("libdownload_language_transfer.z.so dlsym GetSystemLanguageByIntlwl failed: %{public}s",
                err ? err : "unknown");
            dlclose(handle);
            return;
        }

        initialized = true;
    }

    if (getSystemLanguageFunc == nullptr) {
        return;
    }
    getSystemLanguageFunc(buffer, bufferSize);
}

rust::string GetSystemResourceString(const rust::str name)
{
    std::lock_guard<std::mutex> lock(g_resourceStringInfo.mtx);
    char curLanguage[256] = "zh-Hans";

    DynamicGetSystemLanguage(curLanguage, sizeof(curLanguage));
    if (IsSystemLanguageConsistent(curLanguage) != true) {
        /* Language change or initialization */
        std::string strCurLanguage(curLanguage);
        int ret = UpdateSystemResourceStringMap(strCurLanguage);
        if (ret != 0) {
            return "";
        }
    }

    return rust::string(g_resourceStringInfo.stringMap[name.data()]);
}

rust::string GetSystemLanguage()
{
    char curLanguage[256] = "zh-Hans";
    DynamicGetSystemLanguage(curLanguage, sizeof(curLanguage));
    return rust::string(curLanguage);
}

std::shared_ptr<Media::PixelMap> CreatePixelMap()
{
    static std::shared_ptr<Media::PixelMap> cachedPixelMap;
    static std::mutex updateMutex;
    static std::string cachedColorMode;
    
    std::unique_lock<std::mutex> lock(updateMutex);
    
    auto currentColorMode = GetCurrentSystemColorMode();
    if (cachedPixelMap && cachedColorMode == currentColorMode) {
        return cachedPixelMap;
    }
    
    auto newPixelMap = CreatePixelMapByColorMode(currentColorMode);
    if (newPixelMap) {
        cachedPixelMap = newPixelMap;
        cachedColorMode = currentColorMode;
    }
    return cachedPixelMap;
}

std::string GetCurrentSystemColorMode()
{
    auto systemAbilityManager = SystemAbilityManagerClient::GetInstance().GetSystemAbilityManager();
    if (systemAbilityManager == nullptr) {
        REQUEST_HILOGE("get SystemAbilityManager failed.");
        SysEventLog::SendSysEventLog(FAULT_EVENT, SAMGR_FAULT_00, "get SAM failed");
        return "";
    }
    
    auto systemAbility = systemAbilityManager->GetSystemAbility(APP_MGR_SERVICE_ID);
    if (systemAbility == nullptr) {
        REQUEST_HILOGE("get SystemAbility failed.");
        return "";
    }
    
    AppExecFwk::Configuration config;
    sptr<AppExecFwk::IAppMgr> appObject = iface_cast<AppExecFwk::IAppMgr>(systemAbility);
    if (appObject == nullptr) {
        REQUEST_HILOGE("get appObject failed.");
        return "";
    }
    
    int ret = appObject->GetConfiguration(config);
    if (ret != ERR_OK) {
        REQUEST_HILOGE("get configuration failed, ret = %{public}d", ret);
    }
    
    return config.GetItem(OHOS::AAFwk::GlobalConfigurationKey::SYSTEM_COLORMODE);
}

std::shared_ptr<Media::PixelMap> CreatePixelMapByColorMode(const std::string& colorMode)
{
    const char* iconPath = GetIconPathByColorMode(colorMode);
    if (iconPath == nullptr) {
        REQUEST_HILOGE("iconPath is null for color mode: %{public}s", colorMode.c_str());
        return nullptr;
    }
    
    Media::SourceOptions opts;
    uint32_t errorCode = 0;
    
    auto source = Media::ImageSource::CreateImageSource(iconPath, opts, errorCode);
    if (source == nullptr) {
        REQUEST_HILOGE("create image source failed for path: %{public}s", iconPath);
        return nullptr;
    }
    
    Media::DecodeOptions decodeOpts;
    std::unique_ptr<Media::PixelMap> pixel = source->CreatePixelMap(decodeOpts, errorCode);
    if (pixel == nullptr) {
        REQUEST_HILOGE("create pixel map failed, error: %{public}u", errorCode);
        return nullptr;
    }
    
    return std::move(pixel);
}

const char* GetIconPathByColorMode(const std::string& colorMode)
{
    return (colorMode == AppExecFwk::ConfigurationInner::COLOR_MODE_DARK)
           ? CLOSE_ICON_PATH_DARK.c_str()
           : CLOSE_ICON_PATH.c_str();
}

void BasicRequestSettings(Notification::NotificationRequest &request, int32_t uid)
{
    request.SetCreatorUid(REQUEST_SERVICE_ID);
    request.SetOwnerUid(uid);
    request.SetIsAgentNotification(true);
}

std::shared_ptr<OHOS::Notification::NotificationContent> NormalContent(const NotifyContent &content)
{
    auto normalContent = std::make_shared<Notification::NotificationNormalContent>();
    normalContent->SetTitle(std::string(content.title));
    normalContent->SetText(std::string(content.text));
    return std::make_shared<Notification::NotificationContent>(normalContent);
}

std::shared_ptr<OHOS::Notification::NotificationContent> LiveViewContent(const NotifyContent &content)
{
    auto liveViewContent = std::make_shared<Notification::NotificationLocalLiveViewContent>();

    liveViewContent->SetContentType(static_cast<int32_t>(Notification::NotificationContent::Type::LOCAL_LIVE_VIEW));
    liveViewContent->SetType(REQUEST_STYLE_SIMPLE);

    liveViewContent->SetText(std::string(content.text));
    liveViewContent->SetTitle(std::string(content.title));

    if (content.x_mark || content.progress_circle.open) {
        liveViewContent->addFlag(Notification::NotificationLocalLiveViewContent::LiveViewContentInner::BUTTON);
    }

    if (content.x_mark) {
        auto button = liveViewContent->GetButton();
        auto icon = CreatePixelMap();
        if (icon != nullptr) {
            button.addSingleButtonName("cancel");
            button.addSingleButtonIcon(icon);
            liveViewContent->SetButton(button);
        }
    }

    if (content.progress_circle.open) {
        liveViewContent->addFlag(Notification::NotificationLocalLiveViewContent::LiveViewContentInner::PROGRESS);
        Notification::NotificationProgress progress;
        progress.SetIsPercentage(true);
        progress.SetCurrentValue(content.progress_circle.current);
        progress.SetMaxValue(content.progress_circle.total);
        liveViewContent->SetProgress(progress);
    }

    return std::make_shared<Notification::NotificationContent>(liveViewContent);
}

rust::string GetWantAgentBundle(rust::str wantAgent)
{
    auto agent = AbilityRuntime::WantAgent::WantAgentHelper::FromString(std::string(wantAgent));
    if (agent == nullptr) {
        return rust::string("");
    }
    auto want = AbilityRuntime::WantAgent::WantAgentHelper::GetWant(agent);
    if (want == nullptr) {
        return rust::string("");
    }
    return rust::string(want->GetElement().GetBundleName());
}

int PublishNotification(const NotifyContent &content)
{
    Notification::NotificationRequest request(content.request_id);
    BasicRequestSettings(request, content.uid);
    request.SetInProgress(content.progress_circle.open);
    if (content.live_view) {
        request.SetSlotType(Notification::NotificationConstant::SlotType::LIVE_VIEW);
        request.SetContent(LiveViewContent(content));
    } else {
        request.SetContent(NormalContent(content));
    }
    if (!content.want_agent.empty()) {
        request.SetWantAgent(
            OHOS::AbilityRuntime::WantAgent::WantAgentHelper::FromString(std::string(content.want_agent)));
    }
    return Notification::NotificationHelper::PublishNotification(request);
}

NotificationSubscriber::NotificationSubscriber(rust::Box<TaskManagerWrapper> taskManager)
    : _taskManager(std::move(taskManager)){};

void NotificationSubscriber::OnConnected()
{
    RegisterSystemParameterListener();
};
void NotificationSubscriber::OnDisconnected(){};
void NotificationSubscriber::OnDied(){};
void NotificationSubscriber::OnResponse(
    int32_t notificationId, sptr<Notification::NotificationButtonOption> buttonOption)
{
    if (buttonOption == nullptr) {
        REQUEST_HILOGE("buttonOption empty");
        return;
    }
    if (buttonOption->GetButtonName() == "stop") {
        this->_taskManager->pause_task(static_cast<uint32_t>(notificationId));
    } else if (buttonOption->GetButtonName() == "start") {
        this->_taskManager->resume_task(static_cast<uint32_t>(notificationId));
    } else if (buttonOption->GetButtonName() == "cancel") {
        this->_taskManager->stop_task(static_cast<uint32_t>(notificationId));
        Notification::NotificationHelper::CancelNotification(notificationId);
    }
};

void NotificationSubscriber::RegisterSystemParameterListener()
{
    REQUEST_HILOGI("register system parameter modify lister");
    auto colorModeResult = SystemWatchParameter(SYSTEM_COLORMODE, ChangeColorModeCallback, nullptr);
    if (colorModeResult != ERR_OK) {
        REQUEST_HILOGE("register color mode listener fail:%{public}d", colorModeResult);
    }
};

void NotificationSubscriber::ChangeColorModeCallback(const char *key, const char *value, void *context)
{
    REQUEST_HILOGI("Color mode changed");
    RepublishProgressNotify();
}

void SubscribeNotification(rust::Box<TaskManagerWrapper> taskManager)
{
    static auto subscriber = std::make_unique<NotificationSubscriber>(std::move(taskManager));
    Notification::NotificationHelper::SubscribeLocalLiveViewNotification(*subscriber);
}

void RepublishProgressNotify()
{
    std::vector<sptr<Notification::NotificationRequest>> notificationRequests;
    ErrCode ret = Notification::NotificationHelper::GetActiveNotifications(notificationRequests);
    if (ret != ERR_OK) {
        REQUEST_HILOGE("get all active notification fail!");
        return;
    }
    for (const auto& notificationRequest : notificationRequests) {
        if (notificationRequest == nullptr) {
            continue;
        }
        int32_t notificationId = notificationRequest->GetNotificationId();

        if (notificationRequest->GetCreatorUid() != REQUEST_SERVICE_ID ||
            notificationRequest->GetSlotType() != Notification::NotificationConstant::SlotType::LIVE_VIEW) {
            continue;
        }
        
        auto content = notificationRequest->GetContent();
        if (content == nullptr) {
            REQUEST_HILOGE("Notification content is null for id: %{public}d", notificationId);
            continue;
        }

        auto const &normalContent = content->GetNotificationContent();
        
        auto liveViewContent = std::static_pointer_cast<Notification::NotificationLocalLiveViewContent>(normalContent);
        if (liveViewContent == nullptr) {
            REQUEST_HILOGE("Failed to cast to LiveViewContent for id: %{public}d", notificationId);
            continue;
        }
        
        auto icon = CreatePixelMap();
        if (icon != nullptr) {
            auto button = liveViewContent->GetButton();
            if (button.GetAllButtonNames().empty() || button.GetAllButtonIcons().empty()) {
                continue;
            }
            REQUEST_HILOGI("Re-publishing notification for id: %{public}d", notificationId);
            button.ClearButtonIcons();
            button.addSingleButtonName("cancel");
            button.addSingleButtonIcon(icon);
            liveViewContent->SetButton(button);
        }
        
        notificationRequest->SetContent(std::make_shared<Notification::NotificationContent>(liveViewContent));
        Notification::NotificationHelper::PublishContinuousTaskNotification(*notificationRequest);
    }
}

} // namespace OHOS::Request