-- Copyright (c) 2024 Huawei Technologies Co., Ltd.
-- openUBMC is licensed under Mulan PSL v2.
-- You can use this software according to the terms and conditions of the Mulan PSL v2.
-- You may obtain a copy of Mulan PSL v2 at: http://license.coscl.org.cn/MulanPSL2
-- THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND,
-- EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT,
-- MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE.
-- See the Mulan PSL v2 for more details.

local class = require 'mc.class'
local log = require 'mc.logging'
local skynet = require 'skynet'
local debounce = require 'mc.debounce.debounce'
local event = require 'infrastructure.event'
local cjson = require 'cjson'
local c_object_manage = require 'mc.orm.object_manage'

local c_om_event_handler = class()

local DEFAULT_CONT_NUM = 15 -- 防抖次数
local DEFAULT_DEBOUNCE_TYPE = debounce.DEBOUNCED_CONT
local DEFAULT_SUBJECT_TYPE = 0x29 -- 默认告警主体类型是 0x29

local function get_identify_arg_str(alarm_msg_args, indexs)
    local res_str = ''
    if not indexs or not next(indexs) then
        return res_str
    end
    for _, index in ipairs(indexs) do
        res_str = res_str .. alarm_msg_args[index]
    end
    return res_str
end

function c_om_event_handler:ctor(obj, debounce_config)
    self.om_obj = obj
    self.component_name = string.format("Port%s", obj.PortID + 1)
    self.component_node_id = string.format("OpticalModule_EthernetAdapterBoardPort%s", obj.PortID + 1)
    self.debounce_cfg = debounce_config
    self.debounce_vals = {}             -- key是事件标识event_key，value是事件防抖对象
    self.event_key_id_map = {}          -- key是事件标识event_key，value是EventKeyId
    self.subject_type_map = {}          -- key是事件标识event_key，value是SubjectType
    self.prop_names_map = {}            -- key是事件标识event_key，value是告警相关属性列表
    self.trigger_fun_map = {}           -- key是事件标识event_key，value是判断告警是否触发方法
    self.args_fun_map = {}              -- key是事件标识event_key，value是获取告警参数方法
    self.debounce_event_list = {}       -- key是事件标识event_key，value是防抖后的告警是否触发 0表示未触发，1表示触发
    self.cur_event_list = {}            -- key是事件标识event_key，value是event仓的当前告警是否触发 0表示未触发，1表示触发
    self.identify_arg_index_map = {}    -- key是EventCode，value是{参数索引}
end

function c_om_event_handler:init()
    if not self.debounce_cfg or not next(self.debounce_cfg) then
        return
    end
    self:parse_debounce_config()
    -- 获取一次持久化的当前告警
    self:update_latest_alarm_list()
    -- 按照当前告警，初始化告警事件防抖对象
    self:init_debounce_value()
end

-- 解析配置
function c_om_event_handler:parse_debounce_config()
    local identify_arg_str, event_key, args
    for _, e_config in pairs(self.debounce_cfg.Events) do
        if e_config.EventCode then
            -- EventCode和对应的用来区分的参数。前提：相同的EventCode用来区分的参数索引相同。
            self.identify_arg_index_map[e_config.EventCode] = e_config.identify_arg_index
        end
        -- 获取当前告警的唯一标识
        args = e_config.get_args(self.om_obj)
        identify_arg_str = get_identify_arg_str(args, e_config.identify_arg_index)
        event_key = string.format("%s-%s-%s", self.component_name, e_config.EventCode, identify_arg_str)
        self.event_key_id_map[event_key] = e_config.EventKeyId
        self.subject_type_map[event_key] = e_config.SubjectType or DEFAULT_SUBJECT_TYPE
        self.prop_names_map[event_key] = e_config.prop_names
        self.trigger_fun_map[event_key] = e_config.trigger_fun
        self.args_fun_map[event_key] = e_config.get_args
    end
end

-- 初始化防抖对象
function c_om_event_handler:init_debounce_value()
    local dbc_type, dbc_num, identify_arg_str, event_key, args
    for _, v in pairs(self.debounce_cfg.Events) do
        dbc_type = v.type or DEFAULT_DEBOUNCE_TYPE
        dbc_num = v.cont_num or DEFAULT_CONT_NUM
        args = v.get_args(self.om_obj)
        -- 获取当前告警的唯一标识
        identify_arg_str = get_identify_arg_str(args, v.identify_arg_index)
        event_key = string.format("%s-%s-%s", self.component_name, v.EventCode, identify_arg_str)
        -- 获取当前的告警，如果持久化是已有告警，就初始化为已触发。
        self.debounce_vals[event_key] = debounce[dbc_type].new(dbc_num, self.cur_event_list[event_key] and 1 or 0)
    end
end

-- 获取当前event仓的告警，更新 self.cur_event_list
function c_om_event_handler:update_latest_alarm_list()
    local bus = c_object_manage.get_instance().bus
    local ok, alarm_list = pcall(event.get_latest_alarm_list, bus)
    if ok then
        log:debug('[om_event_handler] get_latest_alarm_list successfully. component_name:%s', self.component_name)
    else
        log:debug('[om_event_handler] get_latest_alarm_list failed. component_name:%s err:%s', self.component_name,
            alarm_list)
        return
    end
    local event_err_code_list = {}
    local event_key, identify_arg_str
    -- 通过 ComponentName 和 EventCode 和告警参数组合作为key来区分告警
    for _, alarm in pairs(alarm_list) do
        if not alarm.ComponentName or alarm.ComponentName ~= self.component_name then
            log:debug('[om_event_handler] alarm.ComponentName not match') -- 不是当前对象的告警
            goto continue
        end
        if not alarm.MessageArgs or alarm.MessageArgs == "" then
            log:debug('[om_event_handler] alarm.MessageArgs empty')
            goto continue
        end
        if not alarm.EventCode then
            log:debug('[om_event_handler] alarm.EventCode empty')
            goto continue
        end
        local is_ok, alarm_msg_args = pcall(cjson.decode, alarm.MessageArgs)
        if not is_ok then
            log:debug('[om_event_handler] alarm.MessageArgs decode failed, err: %s', alarm_msg_args)
            goto continue
        end
        -- 获取当前告警的唯一标识
        identify_arg_str = get_identify_arg_str(alarm_msg_args, self.identify_arg_index_map[alarm.EventCode])
        event_key = string.format("%s-%s-%s", alarm.ComponentName, alarm.EventCode, identify_arg_str)
        event_err_code_list[event_key] = alarm_msg_args -- 表示告警已触发，恢复告警需要保存上一次的告警信息参数
        ::continue::
    end
    self.cur_event_list = event_err_code_list
end

-- 发生变化时打印日志
function c_om_event_handler:print_params(event_key, dbd_trigger)
    if not self.prop_names_map[event_key] or not next(self.prop_names_map[event_key]) then
        return
    end
    local log_str = ''
    for _, prop in ipairs(self.prop_names_map[event_key]) do
        log_str = log_str .. string.format("%s=%s ", prop, self.om_obj[prop])
    end
    log:info('[om_event_handler] props: %s dbd_trigger: %s', log_str, dbd_trigger)
end

-- 对于已初始化的防抖对象，扫描资源树上的新值，判断是否要触发告警
function c_om_event_handler:scan_raw_value()
    local is_raw_trigger, dbd_trigger
    for event_key, v in pairs(self.debounce_vals) do
        is_raw_trigger = self.trigger_fun_map[event_key](self.om_obj)
        if is_raw_trigger then
            dbd_trigger = v:get_debounced_val(1)
        else
            dbd_trigger = v:get_debounced_val(0)
        end
        -- 打印数据变化
        if dbd_trigger ~= self.debounce_event_list[event_key] then
            self:print_params(event_key, dbd_trigger)
            self.debounce_event_list[event_key] = dbd_trigger  -- 0表示未触发，1表示触发
        end
    end
end

-- 获取告警参数
function c_om_event_handler:generate_om_event(event_key, is_assert)
    local event_key_id = self.event_key_id_map[event_key]
    local subj_type = self.subject_type_map[event_key]
    local msg_args
    if is_assert == 'false' then
        -- 如果是消除告警，就需要使用之前的告警参数
        msg_args = self.cur_event_list[event_key]
    else
        msg_args = self.args_fun_map[event_key](self.om_obj)
    end
    return event.generate_om_event(self.component_name, is_assert, event_key_id, msg_args, subj_type,
        self.component_node_id)
end

-- 判断是否需要触发或消除告警
function c_om_event_handler:update_om_event()
    local ok, resp
    -- 更新防抖告警
    self:scan_raw_value()
    ok, resp = pcall(function()
        -- 获取event仓记录的当前告警码
        return self:update_latest_alarm_list()
    end)
    if not ok then
        log:error('[om_event_handler] update_latest_alarm_list , error: %s', resp)
    end
    skynet.sleep(10)  -- 等待100毫秒 减少cpu占用
    for event_key, v in pairs(self.debounce_event_list) do
        -- 找出已触发，但是防抖事件判断要消除的告警
        if v == 0 and self.cur_event_list[event_key] then
            -- 消除告警
            ok, resp = pcall(function()
                log:error('[om_event_handler] deassert, event_key: %s', event_key)
                return self:generate_om_event(event_key, 'false')
            end)
            if not ok then
                log:error('[om_event_handler] deassert error code failed, error: %s', resp)
            end
        end
        -- 找出未触发，但是防抖事件判断要产生的告警
        if v == 1 and not self.cur_event_list[event_key] then
            -- 触发告警
            ok, resp = pcall(function()
                log:error('[om_event_handler] assert, event_key: %s', event_key)
                return self:generate_om_event(event_key, 'true')
            end)
            if not ok then
                log:error('[om_event_handler] assert error code failed, error: %s', resp)
            end
        end
    end
end

return c_om_event_handler
