/*
 * Copyright (C) 2021-2023 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 "gsm_sms_cb_handler.h"

#include "cb_start_ability.h"
#include "common_event_support.h"
#include "core_manager_inner.h"
#include "radio_event.h"
#include "securec.h"
#include "singleton.h"
#include "sms_hisysevent.h"
#include "string_utils.h"
#include "telephony_log_wrapper.h"
#include "telephony_permission.h"

namespace OHOS {
namespace Telephony {
using namespace OHOS::EventFwk;
GsmSmsCbHandler::GsmSmsCbHandler(int32_t slotId) : TelEventHandler("GsmSmsCbHandler"), slotId_(slotId)
{
    TELEPHONY_LOGI("GsmSmsCbHandler Create slotId (%{public}d)", slotId_);
}

void GsmSmsCbHandler::Init()
{
    cbMsgList_.clear();
    CoreManagerInner::GetInstance().RegisterCoreNotify(
        slotId_, shared_from_this(), RadioEvent::RADIO_CELL_BROADCAST, nullptr);
    TELEPHONY_LOGI("GsmSmsCbHandler::RegisterHandler::slotId= %{public}d", slotId_);
}

void GsmSmsCbHandler::UnRegisterHandler()
{
    CoreManagerInner::GetInstance().UnRegisterCoreNotify(slotId_, shared_from_this(), RadioEvent::RADIO_CELL_BROADCAST);
}

bool GsmSmsCbHandler::CheckCbActive(const std::shared_ptr<GsmCbCodec> &cbMessage)
{
    if (cbMessage == nullptr) {
        TELEPHONY_LOGE("CbMessage is null!");
        return false;
    }
    return true;
}

uint8_t GsmSmsCbHandler::CheckCbMessage(const std::shared_ptr<GsmCbCodec> &cbMessage)
{
    uint8_t currPageCnt = 0;
    bool bFind = false;
    if (cbMessage == nullptr) {
        TELEPHONY_LOGE("CheckCbMessage cbMessage nullptr err.");
        return currPageCnt;
    }

    std::shared_ptr<GsmCbCodec::GsmCbMessageHeader> cbHeader = cbMessage->GetCbHeader();
    if (cbHeader == nullptr) {
        TELEPHONY_LOGE("CheckCbMessage GetCbHeader err.");
        return currPageCnt;
    }

    for (std::size_t i = 0; i < cbMsgList_.size(); i++) {
        SmsCbInfo &cbInfo = cbMsgList_[i];
        if (*cbInfo.header.get() == *cbHeader.get()) {
            int updateNum = cbHeader->serialNum.updateNum - cbInfo.header->serialNum.updateNum;
            if (updateNum != 0) {
                break;
            }

            if (cbInfo.cbMsgs.count(cbHeader->page)) {
                currPageCnt = 0x01;
                return currPageCnt;
            }
            cbInfo.cbMsgs.insert(std::make_pair(cbHeader->page, cbMessage));
            currPageCnt = cbInfo.cbMsgs.size();
            bFind = true;
            break;
        }
    }

    if (bFind == false || cbHeader->totalPages == 0x01) {
        AddCbMessageToList(cbMessage);
        currPageCnt = 0x01;
    }

    return currPageCnt;
}

std::unique_ptr<SmsCbInfo> GsmSmsCbHandler::FindCbMessage(const std::shared_ptr<GsmCbCodec> &cbMessage)
{
    std::unique_ptr<SmsCbInfo> cbInfo = nullptr;
    if (cbMessage == nullptr) {
        TELEPHONY_LOGE("FindCbMessage cbMessage nullptr err.");
        return cbInfo;
    }

    std::shared_ptr<GsmCbCodec::GsmCbMessageHeader> cbHeader = cbMessage->GetCbHeader();
    if (cbHeader == nullptr) {
        TELEPHONY_LOGE("FindCbMessage header err.");
        return cbInfo;
    }

    for (std::size_t i = 0; i < cbMsgList_.size(); i++) {
        SmsCbInfo &info = cbMsgList_[i];
        if (*info.header.get() == *cbHeader.get()) {
            cbInfo = std::make_unique<SmsCbInfo>(info.header, info.cbMsgs);
            if (cbInfo == nullptr) {
                TELEPHONY_LOGE("Make SmsCbInfo err.");
                return cbInfo;
            }
        }
    }
    return cbInfo;
}

bool GsmSmsCbHandler::AddCbMessageToList(const std::shared_ptr<GsmCbCodec> &cbMessage)
{
    if (cbMessage == nullptr) {
        TELEPHONY_LOGE("AddCbMessageToList cbMessage nullptr err.");
        return false;
    }
    std::shared_ptr<GsmCbCodec::GsmCbMessageHeader> cbHeader = cbMessage->GetCbHeader();
    if (cbHeader == nullptr) {
        TELEPHONY_LOGE("AddCbMessageToList header err.");
        return false;
    }
    std::unique_ptr<SmsCbInfo> cbInfo = FindCbMessage(cbMessage);
    if (cbInfo == nullptr) {
        SmsCbInfo info;
        info.header = cbHeader;
        info.cbMsgs.insert(std::make_pair(cbHeader->page, cbMessage));
        InitLocation(info);
        auto size = cbMsgList_.size();
        if (size > MAX_CB_MSG_LIST_SIZE) {
            auto halfSize = size / 2;
            cbMsgList_.erase(cbMsgList_.begin(), cbMsgList_.begin() + halfSize);
            TELEPHONY_LOGI("AddCbMessageToList clear message count: %{public}zu", halfSize);
        }
        cbMsgList_.push_back(info);
        return true;
    }
    return false;
}

void GsmSmsCbHandler::ClearExpiredMessage()
{
    if (cbMsgList_.empty()) {
        return;
    }
    std::time_t timep;
    int64_t currentTime = time(&timep);
    for (auto i = cbMsgList_.size() - 1; i > 0; i--) {
        if (currentTime - cbMsgList_[i].header->recvTime > static_cast<long long>(DEFAULT_EXPIRED_TIME)) {
            cbMsgList_.erase(cbMsgList_.begin(), cbMsgList_.begin() + i + 1);
            TELEPHONY_LOGI("ClearExpiredMessage clear message count: %{public}zu", i);
            return;
        }
    }
}

bool GsmSmsCbHandler::InitLocation(SmsCbInfo &info)
{
    const uint8_t cellWideImmediate = 0;
    const uint8_t plmnWide = 1;
    const uint8_t LaWide = 2;
    const uint8_t cellWide = 3;
    const int32_t defaultValue = -1;
    info.plmn_ = CoreManagerInner::GetInstance().GetOperatorNumeric(slotId_);
    sptr<CellLocation> location = CoreManagerInner::GetInstance().GetCellLocation(slotId_);
    if (location == nullptr) {
        TELEPHONY_LOGE("location is nullptr.");
        return false;
    }
    if (location->GetCellLocationType() != CellLocation::CellType::CELL_TYPE_GSM) {
        TELEPHONY_LOGE("location type isn't GSM.");
        return false;
    }
    sptr<GsmCellLocation> gsmLocation = sptr<GsmCellLocation>(static_cast<GsmCellLocation *>(location.GetRefPtr()));
    info.lac_ = gsmLocation->GetLac();
    info.cid_ = gsmLocation->GetCellId();
    switch (info.header->serialNum.geoScope) {
        case LaWide:
            info.cid_ = defaultValue;
            break;
        case cellWide:
        case cellWideImmediate:
            break;
        case plmnWide:
        default:
            info.cid_ = defaultValue;
            info.lac_ = defaultValue;
            break;
    }
    plmn_ = info.plmn_;
    cid_ = info.cid_;
    lac_ = info.lac_;
    return true;
}

bool GsmSmsCbHandler::RemoveCbMessageFromList(const std::shared_ptr<GsmCbCodec> &cbMessage)
{
    bool result = false;
    if (cbMessage == nullptr) {
        TELEPHONY_LOGE("RemoveCbMessageFromList cbMessage nullptr err.");
        return false;
    }

    std::shared_ptr<GsmCbCodec::GsmCbMessageHeader> header = cbMessage->GetCbHeader();
    if (header == nullptr) {
        TELEPHONY_LOGE("RemoveCbMessageFromList header err.");
        return false;
    }
    auto it = cbMsgList_.begin();
    while (it != cbMsgList_.end()) {
        SmsCbInfo &info = *it;
        if (*info.header.get() == *header.get() || !info.MatchLocation(plmn_, lac_, cid_)) {
            it = cbMsgList_.erase(it);
            result = true;
        } else {
            ++it;
        }
    }
    return result;
}

void GsmSmsCbHandler::HandleCbMessage(std::shared_ptr<CBConfigReportInfo> &message)
{
    if (message == nullptr) {
        TELEPHONY_LOGE("GsmSmsCbHandler HandleCbMessage message == nullptr");
        return;
    }

    std::string pdu(message->pdu);
    std::shared_ptr<GsmCbCodec> cbMessage = GsmCbCodec::CreateCbMessage(pdu);
    if (cbMessage == nullptr) {
        TELEPHONY_LOGE("create Sms CbMessage fail");
        SmsHiSysEvent::WriteSmsReceiveFaultEvent(slotId_, SmsMmsMessageType::CELL_BROAD_CAST,
            SmsMmsErrorCode::SMS_ERROR_CELL_BROADCAST_PUD_ANALYSIS_FAIL, "publish cell broadcast event fail");
        return;
    }
    std::shared_ptr<GsmCbCodec::GsmCbMessageHeader> header = cbMessage->GetCbHeader();
    if (header == nullptr) {
        TELEPHONY_LOGE("HandleCbMessage header is null.");
        return;
    }

    if (!CheckCbActive(cbMessage)) {
        TELEPHONY_LOGE("The Cell Broadcast msg is not active");
        return;
    }

    ClearExpiredMessage();
    uint8_t pageCnt = CheckCbMessage(cbMessage);
    if (header->totalPages == pageCnt) {
        SendCbMessageBroadcast(cbMessage);
        RemoveCbMessageFromList(cbMessage);
    }
}

bool GsmSmsCbHandler::SendCbMessageBroadcast(const std::shared_ptr<GsmCbCodec> &cbMessage)
{
    if (cbMessage == nullptr) {
        TELEPHONY_LOGE("SendCbMessageBroadcast cbMessage nullptr err.");
        return false;
    }
    EventFwk::Want want;
    EventFwk::CommonEventData data;
    if (!SetWantData(want, cbMessage)) {
        TELEPHONY_LOGE("SendCbMessageBroadcast SetWantData fail.");
        return false;
    }
    DelayedSingleton<CbStartAbility>::GetInstance()->StartAbility(want);
    data.SetWant(want);
    EventFwk::CommonEventPublishInfo publishInfo;
    publishInfo.SetOrdered(true);
    std::vector<std::string> gsmCbPermissions;
    gsmCbPermissions.emplace_back(Permission::RECEIVE_MESSAGES);
    publishInfo.SetSubscriberPermissions(gsmCbPermissions);
    bool publishResult = EventFwk::CommonEventManager::PublishCommonEvent(data, publishInfo, nullptr);
    if (!publishResult) {
        TELEPHONY_LOGE("SendBroadcast PublishBroadcastEvent result fail");
        SmsHiSysEvent::WriteSmsReceiveFaultEvent(slotId_, SmsMmsMessageType::CELL_BROAD_CAST,
            SmsMmsErrorCode::SMS_ERROR_PUBLISH_COMMON_EVENT_FAIL, "publish cell broadcast event fail");
        return false;
    }
    DelayedSingleton<SmsHiSysEvent>::GetInstance()->SetCbBroadcastStartTime();
    return true;
}

void GsmSmsCbHandler::ProcessEvent(const AppExecFwk::InnerEvent::Pointer &event)
{
    if (event == nullptr) {
        TELEPHONY_LOGE("GsmSmsCbHandler event nullptr error.");
        return;
    }

    uint32_t eventId = 0;
    eventId = event->GetInnerEventId();
    TELEPHONY_LOGI("ProcessEvent eventId = %{public}d", eventId);
    switch (eventId) {
        case RadioEvent::RADIO_CELL_BROADCAST: {
            std::shared_ptr<CBConfigReportInfo> cbMessage = nullptr;
            cbMessage = event->GetSharedObject<CBConfigReportInfo>();
            HandleCbMessage(cbMessage);
            break;
        }
        default:
            TELEPHONY_LOGE("GsmSmsCbHandler eventId unknown error.");
            break;
    }
}

bool GsmSmsCbHandler::SetWantData(EventFwk::Want &want, const std::shared_ptr<GsmCbCodec> &cbMessage)
{
    if (cbMessage == nullptr) {
        TELEPHONY_LOGE("cbMessage is nullptr.");
        return false;
    }
    SmsCbData::CbData sendData;
    std::unique_ptr<SmsCbInfo> info = FindCbMessage(cbMessage);
    if (info == nullptr) {
        TELEPHONY_LOGE("find cb message fail.");
        return false;
    }
    std::string rawMsgBody;
    TELEPHONY_LOGI("cbMsgs size:%{public}zu.", info->cbMsgs.size());
    for (auto it = info->cbMsgs.begin(); it != info->cbMsgs.end(); it++) {
        rawMsgBody.append(it->second->GetCbMessageRaw());
    }
    GetCbData(cbMessage, sendData);
    cbMessage->ConvertToUTF8(rawMsgBody, sendData.msgBody);
    PackageWantData(sendData, want);
    if (sendData.isPrimary && !StringUtils::IsEmpty(sendData.msgBody)) {
        TELEPHONY_LOGI("secondary cb msg");
        sendData.isPrimary = false;
        want.SetParam(SmsCbData::IS_ETWS_PRIMARY, sendData.isPrimary);
    }
    if (sendData.isPrimary) {
        want.SetAction(CommonEventSupport::COMMON_EVENT_SMS_EMERGENCY_CB_RECEIVE_COMPLETED);
    } else {
        want.SetAction(CommonEventSupport::COMMON_EVENT_SMS_CB_RECEIVE_COMPLETED);
    }
    return true;
}

void GsmSmsCbHandler::GetCbData(const std::shared_ptr<GsmCbCodec> &cbMessage, SmsCbData::CbData &sendData)
{
    if (cbMessage == nullptr) {
        TELEPHONY_LOGE("Get Cb Data error.");
        return;
    }
    cbMessage->GetMsgType(sendData.msgType);
    cbMessage->GetLangType(sendData.langType);
    cbMessage->GetDcs(sendData.dcs);
    cbMessage->GetPriority(sendData.priority);
    cbMessage->GetCmasMessageClass(sendData.cmasClass);
    cbMessage->GetCmasResponseType(sendData.cmasRes);
    cbMessage->GetCmasSeverity(sendData.severity);
    cbMessage->GetCmasUrgency(sendData.urgency);
    cbMessage->GetCmasCertainty(sendData.certainty);
    cbMessage->IsEtwsMessage(sendData.isEtws);
    cbMessage->GetWarningType(sendData.warnType);
    cbMessage->IsCmasMessage(sendData.isCmas);
    cbMessage->GetSerialNum(sendData.serial);
    cbMessage->GetReceiveTime(sendData.recvTime);
    cbMessage->GetMessageId(sendData.msgId);
    cbMessage->GetServiceCategory(sendData.category);
    cbMessage->GetFormat(sendData.format);
    cbMessage->IsEtwsPrimary(sendData.isPrimary);
    cbMessage->GetGeoScope(sendData.geoScope);
    cbMessage->GetCmasCategory(sendData.cmasCate);
}

void GsmSmsCbHandler::PackageWantData(SmsCbData::CbData &sendData, EventFwk::Want &want)
{
    want.SetParam(SmsCbData::GEO_SCOPE, static_cast<char>(sendData.geoScope));
    want.SetParam(SmsCbData::CMAS_RESPONSE, static_cast<char>(sendData.cmasRes));
    want.SetParam(SmsCbData::SLOT_ID, static_cast<int>(slotId_));
    want.SetParam(SmsCbData::FORMAT, static_cast<char>(sendData.format));
    want.SetParam(SmsCbData::CB_MSG_TYPE, static_cast<char>(sendData.msgType));
    want.SetParam(SmsCbData::MSG_ID, static_cast<int>(sendData.msgId));
    want.SetParam(SmsCbData::SERVICE_CATEGORY, static_cast<int>(sendData.category));
    want.SetParam(SmsCbData::LANG_TYPE, static_cast<char>(sendData.langType));
    want.SetParam(SmsCbData::PRIORITY, static_cast<char>(sendData.priority));
    want.SetParam(SmsCbData::MSG_BODY, sendData.msgBody);
    want.SetParam(SmsCbData::CMAS_CLASS, static_cast<char>(sendData.cmasClass));
    want.SetParam(SmsCbData::CMAS_CATEGORY, static_cast<char>(sendData.cmasCate));
    want.SetParam(SmsCbData::SEVERITY, static_cast<char>(sendData.severity));
    want.SetParam(SmsCbData::URGENCY, static_cast<char>(sendData.urgency));
    want.SetParam(SmsCbData::CERTAINTY, static_cast<char>(sendData.certainty));
    want.SetParam(SmsCbData::IS_CMAS_MESSAGE, sendData.isCmas);
    want.SetParam(SmsCbData::SERIAL_NUM, static_cast<int>(sendData.serial));
    want.SetParam(SmsCbData::RECV_TIME, std::to_string(sendData.recvTime));
    want.SetParam(SmsCbData::DCS, static_cast<char>(sendData.dcs));
    want.SetParam(SmsCbData::IS_ETWS_PRIMARY, sendData.isPrimary);
    want.SetParam(SmsCbData::IS_ETWS_MESSAGE, sendData.isEtws);
    want.SetParam(SmsCbData::PLMN, StringUtils::ToUtf8(plmn_));
    want.SetParam(SmsCbData::LAC, static_cast<int>(lac_));
    want.SetParam(SmsCbData::CID, static_cast<int>(cid_));
    want.SetParam(SmsCbData::WARNING_TYPE, static_cast<int>(sendData.warnType));
}
} // namespace Telephony
} // namespace OHOS