-- 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.

-- Description: sel lifecycle management.
local ipmi_req = require 'sensor.ipmi.ipmi'
local log = require 'mc.logging'
local cc = require 'sel.sel_const'
local event = require 'sel.sel_event'
local common = require 'sel.sel_common'
local err = require 'sensor_error'
local utils = require 'sensor_utils'
local oper = require 'sensor_operation'
local ipmi = require 'ipmi'
local ipmi_enums = require 'ipmi.enums'
local cls_mgnt = require 'mc.class_mgnt'
local comp_code = require 'ipmi.types'.Cc
local queue = require 'skynet.queue'
local skynet = require 'skynet'
local json = require 'cjson'
local context = require 'mc.context'
local file_sec = require 'utils.file'
local task_mgmt = require 'mc.mdb.task_mgmt'
local client = require 'sensor.client'

local ct = ipmi_enums.ChannelType
local sb = string.byte
local task_lock = false
local task_state = task_mgmt.state
local task_status = task_mgmt.status
local PATTERN_FRUCTRLPATH = "/bmc/kepler/Systems/(%d+)/FruCtrl/.*"
local FRUCTRL_INTF = "bmc.kepler.Systems.FruCtrl"

local cached_sel_data = {
    flag = true,  -- 是否为缓存状态
    data = {}  -- 缓存的事件数据
}

local sel_management = {}
sel_management.__index = sel_management

function sel_management.new(bus, db, sel_sigs, sensor_sigs)
    return setmetatable({
        bus = bus,
        db = db,
        event_mgr = event.new(db),
        sel_sigs = sel_sigs,
        sensor_sigs = sensor_sigs,
        reserve_id = cc.RESERVE_ID_INIT,
        version = cc.IPMI_SEL_VER,
        max_count = cc.SEL_TOTOAL_NUM,
        accessible = cc.SEL_LOG_ACCESSABLE,
        logable = cc.SEL_LOG_ENABLE,
        clear_status = cc.SEL_CLEAR_COMPLETED,
        overflow = 0,
        reserved = 0,
        del_cmd_supported = 0,
        partial_add_cmd_supported = 0,
        reserve_cmd_supported = 1,
        get_alloc_info_supported = 1,
        save_sel_queue = queue(),
        sel_updating_flag = false,  -- sel事件更新标志
        reset_flag = false,  -- 平滑重启标志
        mode_mgr = {},
        rearm_task = false,
        mdb_boot = {},
        event_obj = nil
    }, sel_management)
end

-- 根据sel记录是否已满实现两种策略
-- sel 未满，清空模式与滚动模式策略相同
-- sel 已满，不包含清空模式，只针对滚动模式做策略

local sel_unfull_management = {}

function sel_unfull_management:record_sel_almost_full(sel_mgr, value)
    sel_mgr:record_sel_almost_full(value)
end

function sel_unfull_management:record_sel_full(sel_mgr, value)
    sel_mgr:record_sel_full(value)
end

function sel_unfull_management:get_sel_free(max_count, cur_record_id)
    return max_count - cur_record_id
end

function sel_unfull_management:get_rid_and_next_id(rid, cur_record_id, query_max_count)
    local max_count = (cur_record_id < query_max_count) and cur_record_id or query_max_count
    if rid < max_count then
        return rid, rid + 1
    end
    return max_count, 0xFFFF
end

function sel_unfull_management:check_rid_range(rid, cur_record_id, _)
    return rid <= cur_record_id
end

function sel_unfull_management:get_start_id(cur_record_id, query_max_count, _)
    return (cur_record_id >= query_max_count) and (cur_record_id - query_max_count) or 0
end

function sel_unfull_management:query_sel(db, start_id, count, cur_record_id, query_max_count)
    local total = math.min(cur_record_id, query_max_count)
    -- 如果 count 是 0，则证明是查询总数，此处直接返回
    if count == 0 then
        return total, {}
    end
    count = math.min(count, cur_record_id - start_id + 1)
    local events = db:query_sel(utils.GET_SEL_RULE, start_id, count)
    return total, events
end

local sel_full_management = {}

function sel_full_management:record_sel_almost_full(...)
end

function sel_full_management:record_sel_full(...)
end

function sel_full_management:get_sel_free(_, _)
    return 0
end

function sel_full_management:get_rid_and_next_id(rid, _, query_max_count)
    if rid < query_max_count then
        return rid, rid + 1
    end
    return query_max_count, 0xFFFF
end

function sel_full_management:check_rid_range(rid, _, max_count)
    return rid <= max_count
end

function sel_full_management:get_start_id(cur_record_id, query_max_count, max_count)
    return (cur_record_id + max_count - query_max_count) % max_count
end

function sel_full_management:query_sel(db, start_id, count, cur_record_id, query_max_count)
    local total = query_max_count
    -- 如果 count 是 0，则证明是查询总数，此处直接返回
    if count == 0 then
        return total, {}
    end
    -- 防止查询结果中间断档
    if start_id < cur_record_id then
        count = math.min(count, cur_record_id - start_id + 1)
    end
    local events = db:query_sel(utils.GET_SEL_RULE, start_id, count)
    -- 如果为循环记录方式需要补充查询
    if #events < count then
        local eventspl = db:query_sel(utils.GET_SEL_RULE, 1, count - #events)
        for _, v in pairs(events) do
            table.insert(eventspl, #eventspl + 1, v)
        end
        return total, eventspl
    end
    return total, events
end


local register_sel_handler = {
    ['SensorSelInfo'] = function (self, o)
        self.sel_info = o
        utils.push_regist_dump('SEL configuration is registered.')
        -- 初始化sel的数量
        self:init_sel_num()
        self:listen_SelMode()
        -- 初始化事件接收者地址
        self.event_mgr:set_event_receiver(o.ReceiverAddr, 0x00)
        self:handle_cached_sel()
    end,
    ['BootError'] = function (self, o)
        self.mdb_boot[o:get_system_id()] = o:get_mdb_object('bmc.kepler.Systems.IPMIEvent')
        utils.push_regist_dump('Boot Error(systemid:%s) is registered.',  o:get_system_id())
    end,
    ['BaseOSBoot'] = function (self, o)
        self.mdb_os_boot = o:get_mdb_object('bmc.kepler.Systems.IPMIEvent')
        utils.push_regist_dump('OS Boot is registered.', o:get_system_id())
    end
}

function sel_management:register(clz, obj)
    if not register_sel_handler[clz] then
        log:notice('SEL class [%s] has no handler.', clz)
        return
    end
    register_sel_handler[clz](self, obj)
end

function sel_management:register_ipmi(cb)
    cb(ipmi_req.GetSelInfo, function(...) return self:ipmi_get_sel_info(...) end)
    cb(ipmi_req.GetSelAllocInfo, function(...) return self:ipmi_get_sel_alloc_info(...) end)
    cb(ipmi_req.GetSelReserveId, function(...) return self:ipmi_get_sel_reserve_id(...) end)
    cb(ipmi_req.GetSelEntry, function(...) return self:ipmi_get_sel_entry(...) end)
    cb(ipmi_req.ClearSel, function(...) return self:ipmi_clear_sel(...) end)
    cb(ipmi_req.AddSelEntry, function(...) return self:ipmi_add_sel(...) end)
    cb(ipmi_req.SetSensorEventReceiver, function(...) return self:ipmi_set_event_receiver(...) end)
    cb(ipmi_req.GetSensorEventReceiver, function(...) return self:ipmi_get_event_receiver(...) end)
    cb(ipmi_req.ReportSensorEvent, function(...) return self:ipmi_report_sensor_event(...) end)
    cb(ipmi_req.OEMGetSystemSel, function(...) return self:ipmi_get_oem_system_sel(...) end)
    cb(ipmi_req.OEMGetSystemSel2, function(...) return self:ipmi_get_oem_system_sel(...) end)
    cb(ipmi_req.SetBIOSEventData, function(...) return self:ipmi_set_bios_event_data(...) end)
    cb(ipmi_req.SetBaseOSBootEvent, function(...) return self:ipmi_set_base_os_boot_event(...) end)
end

-- 优先以RecordSeq为准查找最新一条事件，若无RecordSeq功能特性则以TimeStamp为准查找
local function get_newest_record(self)
    local new_record
    if self.sel_info.RecordSeq ~= 0 then
        new_record = self.db:query_sel_max_record_seq()
    else
        new_record = self.db:query_sel_max_timestamp()
    end

    return new_record
end

function sel_management:init_sel_num()
    local current_id_valid = true
    if self.sel_info.CurRecordId ~= 0 then
        local new_record = get_newest_record(self)
        local last_record = self.db:query_sel_list(self.sel_info.CurRecordId)
        current_id_valid = last_record and new_record and last_record.RecordId == new_record.RecordId
        -- 数据变更场景下，清空新记录以后的数据
        if not current_id_valid and new_record then
            self.db:delete_sel(new_record.RecordId + 1, self.max_count)
            log:notice('clear sel records(start id:[%s]) in initialization phase', new_record.RecordId)
        end
    end

    local count = self.db:query_sel_count()
    if count == self.max_count then
        self.mode_mgr = (self.sel_info.SelMode == cc.ROTATE_AFTER_FULL) and
            sel_full_management or sel_unfull_management
    end
    if not current_id_valid then
        self.sel_info.CurRecordId = count
    end
end

function sel_management:initialize()
    self.event_mgr:initialize()

    -- 初始化信号的监听槽
    self.sel_sigs.add:on(function(...) return self:add_sel_msg(...) end)
    self.sel_sigs.remove:on(function(...) return self:remove_sel_msg(...) end)
    self.sel_sigs.find:on(function(...) return self.event_mgr:find_sel_msg(...) end)
    self.sel_sigs.post:on(function(...) return self:post_sel_msg(...) end)
    self.sel_sigs.get:on(function (...) return self.event_mgr:get_recovered_sel_msgs(...) end)
    self.sel_sigs.rearm:on(function(...) return self:rearm_sel(...) end)
    self.sel_sigs.enable:on(function(...) return self:enable_log(...) end)

    -- 初始化当前记录模式
    self.mode_mgr = sel_unfull_management
end

function sel_management:enable_log(e)
    if e == cc.SEL_LOG_ENABLE or e == cc.SEL_LOG_DISABLE then
        if self.logable ~= e then
            log:info('sel log enable is changed from %d to %d.', self.logable, e)
        end
        self.logable = e
    end
end

local function rearm_check_reset_sel(msg)
    -- SYS 重启事件不支持 rearm
    if msg.SensorType == cc.SYS_RESET_SENSOR_TYPE then
        return false
    end

    -- BMC 重启事件不支持 rearm
    if msg.SensorType == cc.BMC_RESET_SENSOR_TYPE and msg.EventType == cc.BMC_RESET_EVENT_TYPE then
        return false
    end

    return true
end

function sel_management:rearm_sel(id)
    local records = id and self.event_mgr:find_sel_msg(utils.SENSOR_ID_MODE, {SensorId = id}) or
        self.event_mgr:find_sel_msg(utils.SEL_NO_CONDITION, {})

    if not records then
        log:notice("sensor [%s] has no sel", id or 'ALL')
        return
    end

    for _, record in pairs(records) do
        local msg = record.__datas
        if not rearm_check_reset_sel(msg) then
            goto continue
        end
        self:post_sel_msg({
            msg = msg,
            generator_id_h = 0,
            generator_id_l = 0x20
        })
        ::continue::
    end
end

function sel_management:remove_sel_msg(mode, msg)
    msg.Level = self:get_sel_level_and_desc(msg)
    return self.event_mgr:remove_sel_msg(mode, msg)
end

function sel_management:add_sel_msg(msg)
    msg.Level = self:get_sel_level_and_desc(msg)
    return self.event_mgr:add_sel_msg(msg)
end

function sel_management:get_sel_level_and_desc_exactly(s)
    local descs = self.db:query_sel_desc(s.SensorType, s.EventType, s.EventData1 & 0x0F)
    if not descs then
        return nil, nil
    end
    for _, v in ipairs(descs) do
        repeat
            if v.SelData2 ~= s.EventData2 then
                break
            end
            if v.SelData3 ~= 0xFF and v.SelData3 ~= s.EventData3 then
                break
            end
            return v.AlarmLevel, v.SelDesc
        until true
    end
end

function sel_management:get_sel_level_and_desc_fuzzily(s)
    local descs = self.db:query_sel_desc(s.SensorType, s.EventType, s.EventData1 & 0x0F)
    if not descs then
        return nil, nil
    end
    for _, v in ipairs(descs) do
        repeat
            if v.SelData2 ~= 0xFF and v.SelData2 ~= s.EventData2 then
                break
            end
            if v.SelData3 ~= 0xFF and v.SelData3 ~= s.EventData3 then
                break
            end
            return v.AlarmLevel, v.SelDesc
        until true
    end
end

function sel_management:get_sel_level_and_desc(sel)
    -- 先精准查找，根据 SensorType, EventType, EventData1, EventData2
    local level, desc = self:get_sel_level_and_desc_exactly(sel)
    if level or desc then
        return level, desc
    end

    -- 然后模拟查找，根据SensorType, EventType, EventData1
    level, desc = self:get_sel_level_and_desc_fuzzily(sel)
    if level or desc then
        return level, desc
    end

    -- 再使用一个默认的SEL描述
    return 0, 'Unknown Sensor Event Description'
end

local RETRY_TIMES <const> = 5
local DELAY_ONE_MINUTE<const> = 6000
local function retry_send_verchange_msg(bus, req_data)
    local ok, ret
    skynet.fork_once(function()
        for i = 1, RETRY_TIMES do
            skynet.sleep(DELAY_ONE_MINUTE)
            ok, ret = pcall(ipmi.request, bus, ct.CT_SMM:value(),
                {DestNetFn = cc.PLATFORM_EVENT_NETFN, Cmd = cc.PLATFORM_EVENT_CMD, Payload = req_data})
            if not ok or ret ~= comp_code.Success then
                log:error('send verchange msg failed, retry times:%d, ret:%s', i, ret)
            else
                log:notice('retry send verchange msg successfully, retry times:%d', i)
                return
            end
        end
    end)
end

function sel_management:send_platform_msg(msg)
    if self.event_mgr.addr == cc.EVENT_RECEIVER_DISABLE then
        log:info('send platform msg is disabled')
        return
    end

    local req_data = common.pack_sel_msg(msg)
    local ok, ret = pcall(ipmi.request, self.bus, ct.CT_SMM:value(),
        {DestNetFn = cc.PLATFORM_EVENT_NETFN, Cmd = cc.PLATFORM_EVENT_CMD, Payload = req_data})
    if not ok or ret ~= comp_code.Success then
        log:info('send platform msg to remote failed, ret:%s', ret)
        -- 若verchange事件上报失败则起任务重试
        if msg.SensorType == cc.VERCHANGE_SENSOR_TYPE then
            retry_send_verchange_msg(self.bus, req_data)
        end
        return
    end

    log:info('send platform msg to remote successfully')
end

function sel_management:post_sel_msg(data)
    -- 第一步：先发送 SEL 事件到对应的平台
    self:send_platform_msg(data.msg)

    -- 第二步：保存 SEL 事件到物理库
    self:add_sys_sel({
        GenerateId = (data.generator_id_h << 8) | data.generator_id_l,
        SelMsgVersion = data.msg.EventMsgVersion,
        SensorName = data.msg.SensorName,
        SensorType = data.msg.SensorType,
        SensorNumber = data.msg.SensorNumber,
        SelEventType = common.pack_sel_event_type(data.msg.EventType, data.msg.EventDir),
        SelData1 = data.msg.EventData1,
        SelData2 = data.msg.EventData2,
        SelData3 = data.msg.EventData3,
        SensorId = data.msg.SensorId,
        SubjectName = data.msg.SubjectName
    })
end

function sel_management:report_sel_mmc(req)
    return err.SUCCESS
end

function sel_management:report_sel_me(req)
    return err.SUCCESS
end

function sel_management:report_sel_smm(req)
    return err.SUCCESS
end

function sel_management:get_sel_status_sensor()
    local objs = cls_mgnt('DiscreteSensor'):get_all()
    for _, v in pairs(objs) do
        if v['SensorType'] == 0x10 and v['ReadingType'] == 0x6F then
            return v
        end
    end
end

function sel_management:record_sel_almost_full(value)
    local obj = self:get_sel_status_sensor()
    
    if not obj then
        log:error('sel status sensor object does not exist.')
        return
    end

    local sel_data = {}
    sel_data.EventMsgVersion = utils.EVENT_MSG_VERSION
    sel_data.SensorType = obj['SensorType']
    sel_data.SensorNumber = obj['SensorNumber']
    sel_data.EventType = obj['ReadingType'] & 0x7F
    sel_data.EventDir = value and utils.EVENT_DIR_ASSERT or utils.EVENT_DIR_DEASSERT
    sel_data.EventData1 = 0x05
    sel_data.EventData2 = 0xFF
    sel_data.EventData3 = 0x5A
    sel_data.Level = self:get_sel_level_and_desc(sel_data)
    sel_data.SensorId = obj:get_object_name()
    self.sensor_sigs.addsel:emit(sel_data.SensorId, sel_data, sel_data.EventDir)
end

function sel_management:record_sel_full(value)
    local obj = self:get_sel_status_sensor()

    if not obj then
        log:error('sel status sensor object does not exist.')
        return
    end

    local sel_data = {}
    sel_data.EventMsgVersion = utils.EVENT_MSG_VERSION
    sel_data.SensorType = obj['SensorType']
    sel_data.SensorNumber = obj['SensorNumber']
    sel_data.EventType = obj['ReadingType'] & 0x7F
    sel_data.EventDir = value and utils.EVENT_DIR_ASSERT or utils.EVENT_DIR_DEASSERT
    sel_data.EventData1 = 0x04
    sel_data.EventData2 = 0xFF
    sel_data.EventData3 = 0xFF
    sel_data.Level = self:get_sel_level_and_desc(sel_data)
    sel_data.SensorId = obj:get_object_name()
    self.sensor_sigs.addsel:emit(sel_data.SensorId, sel_data, sel_data.EventDir)
end

function sel_management:check_reboot_permitted()
    self.reset_flag = true

    -- 保证sel当前数据插入数据库完成后再重启
    for _ = 1, 50 do
        if not self.sel_updating_flag then
            return true
        end
        skynet.sleep(10)  -- 间隔10*10ms轮询
    end
    return false
end

function sel_management:clear_reset_flag()
    self.reset_flag = false
end

function sel_management:save_sel(sel_data)
    return self.save_sel_queue(function()
        if cached_sel_data.flag then
            log:info('the SensorSelInfo is not registered and not permitted to add sel temporarily. sel_data: %s',
                json.encode(sel_data))
            table.insert(cached_sel_data.data, sel_data)
            return
        end

        -- 开始重启后禁止保存 SEL
        if self.reset_flag then
            log:notice('reboot prepared, stopped to save sel:%s', json.encode(sel_data))
            return
        end

        if self.sel_info.CurRecordId >= self.max_count then
            self.sel_info.CurRecordId = 0
            -- 清空模式下触发清空先产生一条clear事件
            if self.sel_info.SelMode == cc.CLEAR_AFTER_FULL then
                self.mode_mgr = sel_unfull_management
                self.sel_info.DelTimestamp = os.time()
                self:record_sel_cleared()
                self.mode_mgr:record_sel_almost_full(self, false)
                self.mode_mgr:record_sel_full(self, false)
            else
                self.mode_mgr = sel_full_management
            end
        end
        sel_data.RecordId = (1 + self.sel_info.CurRecordId)
        sel_data.RecordSeq = self.sel_info.RecordSeq + 1

        -- C0h-DFh为OEM时间戳
        if sel_data.SelType < 0xC0 or sel_data.SelType > 0xDF then
            sel_data.Timestamp = os.time()
        end

        if not self:save_sel_record(sel_data) then
            return
        end

        -- 保存SEL信息
        self.sel_info.CurRecordId = sel_data.RecordId
        self.sel_info.RecordSeq = sel_data.RecordSeq
        self.sel_info.AddTimestamp = sel_data.Timestamp
        self.reserve_id = 1 + self.reserve_id

        -- 如果SEL当前的个数达到总数的90%，则记录SEL快满的事件
        if self.sel_info.CurRecordId == 0.9 * self.max_count then
            self.mode_mgr:record_sel_almost_full(self, true)
        end

        -- 还差一条事件计满时产生full事件
        if self.sel_info.CurRecordId == self.max_count - 1 then
            self.mode_mgr:record_sel_full(self, true)
        end
        return sel_data.RecordId
    end)
end

-- 处理缓存的事件
function sel_management:handle_cached_sel()
    cached_sel_data.flag = false
    for k, sel_data in pairs(cached_sel_data.data) do
        self:save_sel(sel_data)
        cached_sel_data.data[k] = nil
    end
end

function sel_management:save_sel_record(sel_data)
    -- 开始重启后禁止更新 SEL 记录，避免重启过程数据破坏
    if self.reset_flag then
        log:notice('reboot prepared, stopped to save sel record')
        return false
    end

    -- 判断 SEL 中是否已经存在，若存在则先删除再保存 SEL 记录
    self.sel_updating_flag = true
    self.db:delete_sel_list(sel_data.RecordId)
    local ret = self.db:insert_sel_list(sel_data)
    self.sel_updating_flag = false

    return ret
end

function sel_management:clear_redundant_sel()
    self.mode_mgr = sel_unfull_management
    self.db:delete_sel(self.sel_info.CurRecordId + 1, self.max_count)
    -- 清除事件快满/全满SEL
    if self.sel_info.CurRecordId ~= self.max_count then
        self:record_sel_full(false)
    end
    if self.sel_info.CurRecordId < 0.9 * self.max_count then
        self:record_sel_almost_full(false)
    end
end

local function handle_sel_data(self, sel_data)
    -- SEL 记录满足过滤条件
    local reading_type = common.unpack_sel_event_type(sel_data.SelEventType)
    local filter = self.db:query_sel_filter(sel_data.SensorType, reading_type, sel_data.SelData1)
    if not filter then
        log:notice('sel log does not match the filter. %s', json.encode(sel_data))
        return
    end

    sel_data.SelType = cc.SEL_RECORD_TYPE_SYS
    local record_id = self:save_sel(sel_data)
    if record_id then
        log:notice('add sel[%s] successfully, record id is %d', json.encode(sel_data), record_id)
    else
        log:error('add sel[%s] failed', json.encode(sel_data))
    end
end

function sel_management:add_sys_sel(sel_data)
    -- SEL 记录未使能则不记录
    if self.logable ~= cc.SEL_LOG_ENABLE then
        log:info('sel log is diabled and cannot record the sel.')
        return
    end

    handle_sel_data(self, sel_data)
end

function sel_management:report_sel_host(req)
    local sel_data = {}
    sel_data.GenerateId = sb(req.Datas, 1)
    local msg = common.parse_sel_msg(req.Datas:sub(2))
    sel_data.SelMsgVersion = msg.version
    sel_data.SensorType = msg.sensor_type
    sel_data.SensorNumber = msg.sensor_number
    sel_data.SelEventType = common.pack_sel_event_type(msg.event_type, msg.direction)
    sel_data.SelData1 = msg.data1
    sel_data.SelData2 = msg.data2
    sel_data.SelData3 = msg.data3
    sel_data.SensorId = cc.SEL_NO_SENSOR
    sel_data.SensorName = 'Unknown Sensor'
    sel_data.SubjectName = 'Unknown'
    self:add_sys_sel(sel_data)
    return err.SUCCESS
end

function sel_management:report_sel_other(req)
    local sel_data = {}
    sel_data.GenerateId = 0
    local msg = common.parse_sel_msg(req.Datas)
    sel_data.SelMsgVersion = msg.version
    sel_data.SensorType = msg.sensor_type
    sel_data.SensorNumber = msg.sensor_number
    sel_data.SelEventType = common.pack_sel_event_type(msg.event_type, msg.direction)
    sel_data.SelData1 = msg.data1
    sel_data.SelData2 = msg.data2
    sel_data.SelData3 = msg.data3
    sel_data.SensorId = cc.SEL_NO_SENSOR
    sel_data.SensorName = 'Unknown Sensor'
    sel_data.SubjectName = 'Unknown'
    self:add_sys_sel(sel_data)
    return err.SUCCESS
end

function sel_management:ipmi_report_sensor_event(req, ctx)
    local result = err.SUCCESS
    if ctx.ChanType == ct.CT_MMC:value() then
        result = self:report_sel_mmc(req)
    elseif ctx.ChanType == ct.CT_ME:value() then
        result = self:report_sel_me(req)
    elseif ctx.ChanType == ct.CT_SMM:value() then
        result = self:report_sel_smm(req)
    elseif ctx.ChanType == ct.CT_HOST:value() then
        result = self:report_sel_host(req)
    else
        result = self:report_sel_other(req)
    end

    if result ~= err.SUCCESS then
        log:error('ipmi report sensor event failed, result is %d.', result)
        error(err.ipmi_error_map(result))
    end

    return {CompletionCode = 0x00}
end

function sel_management:ipmi_get_event_receiver()
    local rsp = {}
    rsp.CompletionCode = 0x00
    rsp.Addr, rsp.Lun = self.event_mgr:get_event_receiver()
    rsp.Reserved = 0
    return rsp
end

function sel_management:ipmi_set_event_receiver(req, ctx)
    -- Addr: [0]-always 0b when [7:1] hold I2C slave address
    if req.Addr ~= cc.EVENT_RECEIVER_DISABLE and req.Addr & 1 ~= 0 then
        log:error('set event receiver address %02x is invalid.', req.Addr)
        oper.log(ctx, oper.SEL_RECVER, oper.FAILED)
        error(err.ipmi_error_map(err.ERR_INVALID_FIELD))
    end

    -- receiver addr不持久化，因此不更新到ReceiverAddr属性中
    self.event_mgr:set_event_receiver(req.Addr, req.Lun)

    if req.Addr ~= cc.EVENT_RECEIVER_DISABLE then
        -- 前一次rearm任务未完成则不重新创建协程rearm
        if self.rearm_task then
            log:notice('the previous rearm task is not completed.')
            goto continue
        end

        skynet.fork_once(function ()
            self.rearm_task = true
            self:rearm_sel()
            self.rearm_task = false
        end)
    end

    ::continue::
    oper.log(ctx, oper.SEL_RECVER, oper.SUCCESS)
    local rsp = {}
    rsp.CompletionCode = 0x00
    return rsp
end

function sel_management:ipmi_get_sel_info()
    local free = self.mode_mgr:get_sel_free(self.max_count, self.sel_info.CurRecordId)
    local sel_count = self.max_count - free
    local op = (self.overflow << 7) | (self.del_cmd_supported << 3) | (self.partial_add_cmd_supported << 2)
    op = (op | (self.reserve_cmd_supported << 1) | self.get_alloc_info_supported) & 0xFF

    local rsp = {}
    rsp.CompletionCode = 0x00
    rsp.SelVersion = self.version
    rsp.SelCountL = sel_count & 0xFF
    rsp.SelCountH = (sel_count >> 8) & 0xFF
    free = free * cc.SEL_ITEM_SIZE
    rsp.FreeL = free & 0xFF
    rsp.FreeH = (free >> 8) & 0xFF
    local time = common.set_local_time(self.sel_info.AddTimestamp)
    rsp.AddTime0 = time & 0xFF
    rsp.AddTime1 = (time >> 8) & 0xFF
    rsp.AddTime2 = (time >> 16) & 0xFF
    rsp.AddTime3 = (time >> 24) & 0xFF
    time = common.set_local_time(self.sel_info.DelTimestamp)
    rsp.DelTime0 = time & 0xFF
    rsp.DelTime1 = (time >> 8) & 0xFF
    rsp.DelTime2 = (time >> 16) & 0xFF
    rsp.DelTime3 = (time >> 24) & 0xFF
    rsp.OperationSupport = op

    return rsp
end

function sel_management:ipmi_get_sel_alloc_info()
    local count = self.mode_mgr:get_sel_free(self.max_count, self.sel_info.CurRecordId)

    local rsp = {}
    rsp.CompletionCode = 0x00
    rsp.UnitL = self.max_count & 0xFF
    rsp.UnitH = (self.max_count >> 8) & 0xFF
    rsp.SizeL = cc.SEL_ITEM_SIZE
    rsp.SizeH = 0
    rsp.FreeL = count & 0xFF
    rsp.FreeH = (count >> 8) & 0xFF
    rsp.BlockL = rsp.FreeL
    rsp.BlockH = rsp.FreeH
    rsp.MaxSize = cc.SEL_ITEM_SIZE
    return rsp
end

function sel_management:ipmi_get_sel_reserve_id()
    self.reserve_id = self.reserve_id + 1
    local rsp = {}
    rsp.CompletionCode = 0x00
    rsp.ReserveIdL = self.reserve_id & 0xFF
    rsp.ReserveIdH = (self.reserve_id >> 8) & 0xFF
    return rsp
end

function sel_management:get_sel_record_entry(record_id)
    local query_max_count = self.sel_info.QueryMaxCount or cc.SEL_TOTOAL_NUM
    local rid
    if record_id == 0xFFFF then  -- 指定查询最后一条sel
        rid = self.sel_info.CurRecordId
    else
        -- 根据定制化最大条数确定真实的查询record id
        if record_id == 0 then
            record_id = 1
        end
        local start_id = self.mode_mgr:get_start_id(self.sel_info.CurRecordId, query_max_count, self.max_count)
        rid = start_id + record_id
        rid = (rid > self.max_count) and (rid % self.max_count) or rid
    end

    local record = self.db:query_sel_list(rid)
    if not record or not record.SensorId then
        log:warn('there is no sel by record id 0x%02X', rid)
        return nil, nil
    end

    -- 根据 sel 记录中的 sensor id 获取当前最新的sensor number及 owner lun
    local sensor_number, owner_lun = self:get_sensor_prop(record.SensorId)
    if sensor_number then
        record.SensorNumber = sensor_number
        record.GenerateId = (owner_lun << 8) | (record.GenerateId & 0x00FF)
    end

    -- 返回下一条 sel 记录的 record id
    -- RecordId 需要适配sel记录模式做处理
    local next_id
    record.RecordId, next_id = self.mode_mgr:get_rid_and_next_id(record_id, self.sel_info.CurRecordId, query_max_count)

    -- 需要根据本地时间校准事件时间戳
    record.Timestamp = common.set_local_time(record.Timestamp)

    return next_id, record
end

function sel_management:ipmi_get_oem_system_sel(req)
    if not self.mode_mgr:check_rid_range(req.RecordId, self.sel_info.CurRecordId, self.max_count) then
        error(err.ipmi_error_map(err.ERR_OUT_OF_RANGE))
    end

    local next_id, record = self:get_sel_record_entry(req.RecordId)
    if next_id and record then
        return {
            CompletionCode = 0x00,
            ManuId = 0x0007DB,
            RecordId = ((next_id & 0xFF) << 8) | ((next_id >> 8) & 0xFF),
            Datas = common.makeup_sel_datas(record)
        }
    else
        error(err.ipmi_error_map(err.ERR_INVALID_ID))
    end
end

function sel_management:ipmi_get_sel_entry(req)
    local len = req.Count == 0xFF and cc.SEL_ITEM_SIZE or req.Count
    if req.Offset >= cc.SEL_ITEM_SIZE or len > cc.SEL_ITEM_SIZE then
        log:error('input entry offset or length is out of range')
        error(err.ipmi_error_map(err.ERR_INVALID_FIELD))
    end

    local rid = (req.ReserveIdH << 8) | req.ReserveIdL
    if rid == self.reserve_id or (req.Offset == 0 and rid == 0) then
        local record_id = (req.RecordIdH << 8) | req.RecordIdL
        if not self.mode_mgr:check_rid_range(record_id, self.sel_info.CurRecordId, self.max_count) then
            error(err.ipmi_error_map(err.ERR_OUT_OF_RANGE))
        end
        local next_id, record = self:get_sel_record_entry(record_id)
        local rsp = {}
        rsp.CompletionCode = 0x00
        if next_id and record then
            rsp.RecordIdL = next_id & 0xFF
            rsp.RecordIdH = (next_id >> 8) & 0xFF
            rsp.Datas = common.makeup_sel_datas(record)
        else
            error(err.ipmi_error_map(err.ERR_SEL_ERASED))
        end
        return rsp
    else
        log:error('input reserve id 0x%04x is invalid or not match the offset 0x%02x', rid, req.Offset)
        error(err.ipmi_error_map(err.ERR_INVALID_RSVID))
    end
end

function sel_management:record_sel_cleared()
    local obj = self:get_sel_status_sensor()

    if not obj then
        log:error('sel status sensor object does not exist.')
        return
    end

    local sel_data = {}
    sel_data.GenerateId = (obj['OwnerLun'] << 8) | obj['OwnerId']
    sel_data.SelMsgVersion = utils.EVENT_MSG_VERSION
    sel_data.SensorType = obj['SensorType']
    sel_data.SensorNumber = obj['SensorNumber']
    sel_data.SelEventType = common.pack_sel_event_type(obj['ReadingType'], utils.EVENT_DIR_ASSERT)
    sel_data.SelData1 = 0x02
    sel_data.SelData2 = 0xFF
    sel_data.SelData3 = 0xFF
    sel_data.SensorId = obj:get_object_name()
    sel_data.SensorName = obj['SensorName'] or 'Unknown Sensor'
    sel_data.SubjectName = 'MainBoard'
    self:add_sys_sel(sel_data)
end

-- 清除精细化告警
local function clear_event_list()
    local events_obj
    for _, obj in pairs(client:GetEventsObjects()) do
        -- 仅一个对象
        events_obj = obj
        break
    end
    if not events_obj then
        log:error("get events obj failed")
        return
    end
    local ok, err = events_obj.pcall:ClearEventList(context.new()) -- 此处避免记录2条日志传入空白上下文
    if not ok then
        log:error("clear event failed:[%s]", err)
    end
end

function sel_management:ipmi_clear_sel(req, ctx)
    local flag = string.format('%s%s%s', string.char(req.FlagC), string.char(req.FlagL), string.char(req.FlagR))
    if flag ~= 'CLR' then
        oper.log(ctx, oper.SEL_CLEAR, oper.FAILED, 'invalid parameter')
        log:error('clear sel ipmi request flag is invalid')
        error(err.ipmi_error_map(err.ERR_INVALID_FIELD))
    end

    if req.Operation ~= cc.SEL_ERASE_GET_STATE and req.Operation ~= cc.SEL_ERASE_INITIATE then
        oper.log(ctx, oper.SEL_CLEAR, oper.FAILED, 'invalid parameter')
        log:error('clear sel ipmi request operation is invalid')
        error(err.ipmi_error_map(err.ERR_INVALID_FIELD))
    end

    skynet.fork_once(function()
        clear_event_list()
    end)

    if req.Operation == cc.SEL_ERASE_INITIATE and self.clear_status ~= cc.SEL_CLEAR_INPROGRESS then
        if ((req.ReserveIdH << 8) | req.ReserveIdL) ~= self.reserve_id then
            oper.log(ctx, oper.SEL_CLEAR, oper.FAILED, 'inavlid reserved id')
            log:error('clear sel ipmi request reserve id is invalid')
            error(err.ipmi_error_map(err.ERR_INVALID_RSVID))
        end

        self:clear_sensor_sel(ctx)
    end

    local rsp = {}
    rsp.Reserved = 0
    rsp.Status = self.clear_status
    rsp.CompletionCode = 0x00
    return rsp
end

function sel_management:clear_sensor_sel(ctx)
    self.clear_status = cc.SEL_CLEAR_INPROGRESS
    self.db:delete_sel(1, self.max_count)
    self.sel_info.CurRecordId = 0
    self.mode_mgr = sel_unfull_management
    self.reserve_id = cc.RESERVE_ID_INIT
    self.sel_info.DelTimestamp = os.time()
    self:record_sel_cleared()
    self.clear_status = cc.SEL_CLEAR_COMPLETED
    oper.log(ctx, oper.SEL_CLEAR, oper.SUCCESS)
    -- 尝试清除事件快满/全满SEL
    self:record_sel_almost_full(false)
    self:record_sel_full(false)
end

function sel_management:rpc_clear_sel(ctx)
    skynet.fork_once(function()
        clear_event_list()
    end)
    self:clear_sensor_sel(ctx)
end

-- 获取当前最新的SensorNumber和OwnerLun，更新到sel信息中
function sel_management:get_sensor_prop(sensor_id)
    -- 优先查找离散传感器
    local obj = cls_mgnt('DiscreteSensor'):get(sensor_id)
    if obj then
        return obj['SensorNumber'], obj['OwnerLun']
    end

    -- 再行查找门限传感器
    obj = cls_mgnt('ThresholdSensor'):get(sensor_id)
    if obj then
        return obj['SensorNumber'], obj['OwnerLun']
    end

    log:warn('sensor [%s] has no match sensor number and owner lun.', sensor_id)
    return nil, nil
end

function sel_management:get_sensor_name(sensor_id)
    -- 优先查找离散传感器
    local obj = cls_mgnt('DiscreteSensor'):get(sensor_id)
    if obj then
        return obj['SensorName']
    end

    -- 再行查找门限传感器
    obj = cls_mgnt('ThresholdSensor'):get(sensor_id)
    if obj then
        return obj['SensorName']
    end

    log:warn('sensor [%s] has no match sensor name.', sensor_id)
    return 'Unknown Sensor'
end

function sel_management:get_sensor_number(id)
    -- 优先查找离散传感器
    local objs = cls_mgnt('DiscreteSensor'):get_all()
    for _, v in pairs(objs) do
        if v:get_object_name() == id then
            return v['SensorNumber']
        end
    end

    -- 再行查找门限传感器
    objs = cls_mgnt('ThresholdSensor'):get_all()
    for _, v in pairs(objs) do
        if v:get_object_name() == id then
            return v['SensorNumber']
        end
    end

    return nil
end

function sel_management:get_sensor_id(sno, stype)
    -- 优先查找离散传感器
    local objs = cls_mgnt('DiscreteSensor'):get_all()
    for _, v in pairs(objs) do
        repeat
            if sno and v['SensorNumber'] ~= sno then
                break
            end
            if stype and v['SensorType'] ~= stype then
                break
            end
            return v:get_object_name()
        until true
    end

    -- 再行查找门限传感器
    objs = cls_mgnt('ThresholdSensor'):get_all()
    for _, v in pairs(objs) do
        repeat
            if sno and v['SensorNumber'] ~= sno then
                break
            end
            if stype and v['SensorType'] ~= stype then
                break
            end
            return v:get_object_name()
        until true
    end

    return nil
end

function sel_management:add_sel_only(req, id)
    return self:save_sel({
        SelType = req.SelType,
        Timestamp = req.Timestamp,
        GenerateId = req.GeneratorId,
        SelMsgVersion = req.MsgVersion,
        SensorType = req.SensorType,
        SensorName = id and self:get_sensor_name(id) or 'Unknown Sensor',
        SubjectName = "Unknown",
        SensorNumber = req.SensorNumber,
        SelEventType = common.pack_sel_event_type(req.EventType, req.EventDir),
        SelData1 = sb(req.Datas:sub(1, 1)),
        SelData2 = sb(req.Datas:sub(2, 2)),
        SelData3 = sb(req.Datas:sub(3, 3)),
        SensorId = id or cc.SEL_NO_SENSOR
    })
end

function sel_management:add_oem_sel(RecordType, Timestamp, ManufactureID, SELDatas)
    local t_event_type, t_event_dir = common.unpack_sel_event_type(SELDatas:byte(3, 3))
    local req = {
        SelType = RecordType, 
        Timestamp = Timestamp,
        -- ManufactureID 2 Byte 赋值给 GeneratorId
        GeneratorId = ManufactureID & 0xFFFF,
        -- ManufactureID 第 3 Byte 赋值给 MsgVersion
        MsgVersion = (ManufactureID & 0xFF0000) >> 16,
        SensorType = SELDatas:byte(1, 1),
        SensorNumber = SELDatas:byte(2, 2),
        EventType = t_event_type,
        EventDir = t_event_dir,
        Datas = SELDatas:sub(4, 6)
    }
    return self:add_sel_only(req)
end

function sel_management:add_sel_by_sensor(req, id)
    if not id then
        log:error('sensor id is invalid and cannot add sel.')
        return
    end

    local sel = {
        SensorId = id,
        EventMsgVersion = req.MsgVersion,
        SensorType = req.SensorType,
        SensorNumber = req.SensorNumber,
        EventType = req.EventType,
        EventDir = req.EventDir,
        EventData1 = sb(req.Datas:sub(1, 1)),
        EventData2 = sb(req.Datas:sub(2, 2)),
        EventData3 = sb(req.Datas:sub(3, 3))
    }
    sel.Level = self:get_sel_level_and_desc(sel)

    self.sensor_sigs.addsel:emit(id, sel, req.EventDir)
end

function sel_management:match_boot(stype, sysid)
    if not self.mdb_boot[sysid] then
        return false
    end
    return self.mdb_boot[sysid].SensorType == stype
end

function sel_management:update_boot(rtype, datas, sysid)
    -- 先更新对应的 Boot Error 值
    local data = utils.toww(0xFF, sb(datas:sub(1, 1)), sb(datas:sub(2, 2)), sb(datas:sub(3, 3)))
    self.mdb_boot[sysid].ReadingType = rtype
    self.mdb_boot[sysid].EventData = data

    -- 再更新默认的 Boot Error 值
    self.mdb_boot[sysid].ReadingType = 0xFF
    self.mdb_boot[sysid].EventData = 0xFFFFFFFF
end

local function get_event_object()
    local obj = nil
    local cnt = 0
    while cnt < 60 do
        for _, v in pairs(client:GetEventsObjects()) do
            return v
        end
        cnt = cnt + 1
        skynet.sleep(100)
    end
    return obj
end 

-- 沿用v2逻辑
local EVENT_CODE = string.format('0x%08X', tostring(0xFFFFFFFF & (~(1 << 23))))
local function add_sel_event(self, req, ctx)
    if not self.event_obj then
        self.event_obj = get_event_object()
    end
    local params = {
        { 'EventCode', EVENT_CODE }, 
        { 'Severity', 'Normal' },
        { 'State','Asserted' },
        { 'Description', 'NoSensor: OEM SEL Record.' },
        -- 事件主体固定为System类型
        { 'SubjectType', '44' },
        { 'OldEventCode', EVENT_CODE },
        { 'SystemId', tostring(ctx.HostId) or '1' }
    }
    self.event_obj:AddSel(context.get_context_or_default(), params)
end

function sel_management:ipmi_add_sel(req, ctx)
    if ctx.ChanType == ct.CT_SMM then
        log:info('[Not Supported] current sel will be transferred to SMM.')
        error(err.ipmi_error_map(err.ERR_UNSPECIFIED))
    end

    if ctx.ChanType ~= ct.CT_HOST and self.logable ~= cc.SEL_LOG_ENABLE then
        log:error('add sel ipmi request is not supported')
        utils.log(ctx, 'Add SEL entry failed')
        error(err.ipmi_error_map(err.ERR_CANNOT_SUPPORT))
    end

    local record_id = 0x00
    if req.SelType >= 0xC0 then
        -- OEM 类型的SEL则直接添加即可
        record_id = self:add_sel_only(req)
        skynet.fork(function()
            local ok, err = pcall(add_sel_event, self, req, ctx)
            if not ok then
                log:error("faild to add an oem sel event, because %s happened", err)
            end
        end)
    elseif ctx.ChanType == ct.CT_ME then
        -- ME 通道过来的事件则直接不处理
        log:info('current sel is from ME and cannot process.')
    elseif ctx.ChanType == ct.CT_HOST then
        -- HOST 通道过来的事件是 BIOS 上报的事件，需要单独处理
        log:info('current sel is from HOST and update the bios event data.')
        -- 先更新对应的 BIOS 事件数据
        if self:match_boot(req.SensorType, ctx.HostId) then
            self:update_boot(req.EventType, req.Datas, ctx.HostId)
        end
        local sensor_id = self:get_sensor_id(req.SensorNumber, req.SensorType)
        if sensor_id then
            -- 如果可以找到传感器，则触发传感器事件
            self:add_sel_by_sensor(req, sensor_id)
        else
            -- 如果找不到对应的传感器，则直接添加 SEL 即可
            record_id = self:add_sel_only(req)
        end
    else
        -- 其他 通道过来的事件，则根据传感器查找，找不到则直接添加添加 SEL 即可
        record_id = self:add_sel_only(req, self:get_sensor_id(req.SensorNumber, req.SensorType))
    end

    if not record_id then
        log:error('add sel ipmi request failed')
        oper.log(ctx, oper.SEL_ADD, oper.FAILED)
        error(err.ipmi_error_map(err.ERR_UNSPECIFIED))
    end

    local rsp = {}
    rsp.CompletionCode = 0x00
    rsp.RecordId = record_id
    oper.log(ctx, oper.SEL_ADD, oper.SUCCESS)
    return rsp
end

local maint_handlers = {
    [cc.NO_BOOT_DEV] = function(code)
        log:maintenance(log.MLOG_ERROR, code, 'BIOS, boot, No bootable media.')
    end,
    [cc.NO_BOOT_DISK] = function (code)
        log:maintenance(log.MLOG_WARN, code, 'BIOS, boot, Non-bootable disk in drive.')
    end,
    [cc.PXE_SVR_ERR] = function (code)
        log:maintenance(log.MLOG_WARN, code, 'BIOS, boot, PXE server not found.')
    end,
    [cc.INVALID_BOOT_DEV] = function (code)
        log:maintenance(log.MLOG_WARN, code, 'BIOS, boot, Invalid boot sector.')
    end,
    [cc.CPU_EEPROM_READ_FAIL] = function (code)
        log:maintenance(log.MLOG_WARN, code, 'BIOS, boot, Cpu eeprom get fail.')
    end
}
local fault_codes = {
    [cc.NO_BOOT_DEV] = 'SVR-0072004',
    [cc.NO_BOOT_DISK] = 'SVR-0072005',
    [cc.PXE_SVR_ERR] = 'SVR-0072006',
    [cc.INVALID_BOOT_DEV] = 'SVR-0072007',
    [cc.CPU_EEPROM_READ_FAIL] = 'SVR-0072010'

}
local function record_maint_log(status)
    if maint_handlers[status] then
        maint_handlers[status](fault_codes[status])
    else
        log:error('ipmi set boot error invalid status [%d]', status)
    end
end

local boot_errs = {
    cc.NO_BOOT_DEV, cc.NO_BOOT_DISK, cc.PXE_SVR_ERR,
    cc.INVALID_BOOT_DEV, cc.TIMEOUT_FOR_SELECTION, cc.CPU_EEPROM_READ_FAIL
}
function sel_management:ipmi_set_bios_event_data(req, ctx)
    local asserted = req.EventDir == 0
    local selector = 1 + req.DeviceStatus
    if boot_errs[selector] then
        if not ctx.HostId then
            ctx.HostId = 1
        end
        if not self.mdb_boot[ctx.HostId] then
            log:info('Boot Error data is not changed because boot not exist.')   
            error(err.ipmi_error_map(err.ERR_UNSPECIFIED))
        end
        self.mdb_boot[ctx.HostId].EventData = asserted and req.DeviceStatus or 0xFFFFFFFF
        log:info('Boot Error data is changed by ipmi to 0x%08X', self.mdb_boot[ctx.HostId].EventData)
    end
    if asserted then
        record_maint_log(req.DeviceStatus)
    end

    return ipmi_req.SetBIOSEventData.rsp.new(0x00, 0x0007DB)
end

function sel_management:ipmi_set_base_os_boot_event(req, ctx)
    local asserted = req.EventDir == 0
    if cc.BOOT_COMPLETED == req.DeviceStatus then
        -- 判断当前事件是否已经assert，如果是需要先deassert才能实现连续上报
        if self.mdb_os_boot.EventData == cc.BOOT_COMPLETED then
            self.mdb_os_boot.EventData = 0xFFFFFFFF
            skynet.sleep(200)
        end
        self.mdb_os_boot.EventData = asserted and req.DeviceStatus or 0xFFFFFFFF
        log:info('OS Boot data is changed by ipmi to 0x%08X', self.mdb_os_boot.EventData)
    end
    oper.log(ctx, oper.BASE_OS_BOOT_EVENT, oper.SUCCESS, self.mdb_os_boot.EventData)
    return ipmi_req.SetBIOSEventData.rsp.new(0x00, 0x0007DB)
end

function sel_management:on_fructrl_changed(vals, sysid)
    if not self.mdb_boot[sysid] then
        log:error('No need to process fructrl changes because boot not exist.')
        return
    end

    local powered_off = false
    local rebooted = false
    for prop, val in pairs(vals) do
        if prop == 'PowerState' and val:value() == 'OFF' then
            powered_off = true
            break
        end
        if prop == 'SysResetDetected' and val:value() == 1 then
            rebooted = true
            break
        end
    end

    if powered_off or rebooted then
        self.mdb_boot[sysid].EventData = 0xFFFFFFFF
        log:info('Boot Error data is changed by fructrl[%s] to 0x%08X',
            powered_off and 'poweroff' or 'reboot', self.mdb_boot[sysid].EventData)
    end
end

-- 打桩测试：模拟过程中的Boot Error传感器的状态
function sel_management:update_boot_error(data)
    local objs = cls_mgnt('BootError'):get_all()
    for _, v in pairs(objs) do
        if v:get_object_name():match('BootError') then
            v['EventData'] = data
            break
        end
    end
end

-- 打桩测试：更改sel数据库最大记录量
function sel_management:set_sel_max_count(max_count)
    self.max_count = max_count
    self.sel_info.QueryMaxCount = max_count
end

function sel_management:listen_host()
    client:OnFruCtrlPropertiesChanged(function (values, path, _)
        local sysid = tonumber(string.match(path or '', PATTERN_FRUCTRLPATH))
        self:on_fructrl_changed(values, sysid)
    end)
end

function sel_management:listen_SelMode()
    self.sel_info.property_changed:on(function(name, value)
        if name == 'SelMode' then
            oper.log(context.get_context(), oper.SEL_MODE, oper.SUCCESS, 
                value == cc.ROTATE_AFTER_FULL and 'RotateAfterFull' or "ClearAfterFull")
            skynet.fork_once(function ()
                self:clear_redundant_sel()
            end)
            log:notice('Set SEL record mode to %s successfully.', value)
        end
    end)
end

function sel_management:dump_sel(path)
    local u = require 'mc.utils'
    if skynet.getenv('TEST_DATA_DIR') then
        u.copy_file(skynet.getenv('TEST_DATA_DIR') .. '/sensor.db', path)
    else
        u.copy_file('/data/opt/bmc/persistence.local/sensor.db', path)
    end
end

local function import_query_sel_max_num(self, ctx, import)
    if not import or not import.Import or type(import.Value) ~= 'number' then
        oper.log(ctx, oper.SEL_MAXNUM, oper.FAILED)
        log:error('sel property [QuerySelMaxNum] import not supported.')
        return common.RET_ERROR
    end

    if import.Value < 1 or import.Value > cc.SEL_TOTOAL_NUM then
        oper.log(ctx, oper.SEL_MAXNUM, oper.FAILED)
        log:error('sel property [QuerySelMaxNum] import out of range.')
        return common.RET_ERROR
    end

    if import.Value ~= self.sel_info.QueryMaxCount then
        self.sel_info.QueryMaxCount = import.Value
        oper.log(ctx, oper.SEL_MAXNUM, oper.SUCCESS, import.Value)
    end
    log:info('sel property [QuerySelMaxNum] import to %s successfully.', import.Value)
    return common.RET_OK
end

local function import_sel_record_mode(self, ctx, import)
    if not import or not import.Import or type(import.Value) ~= 'string' then
        oper.log(ctx, oper.SEL_MODE, oper.FAILED)
        log:error('sel property [SelRecordMode] import not supported.')
        return common.RET_ERROR
    end

    local sel_mode = import.Value == 'RotateAfterFull' and cc.ROTATE_AFTER_FULL or cc.CLEAR_AFTER_FULL
    if sel_mode ~= self.sel_info.SelMode then
        self.sel_info.SelMode = sel_mode
    end
    log:info('sel property [SelRecordMode] import to %s successfully.', import.Value)
    return common.RET_OK
end

function sel_management:on_import(ctx, data)
    local sel_import_func = {
        ['QuerySelMaxNum'] = import_query_sel_max_num,
        ['SelRecordMode'] = import_sel_record_mode
    }

    local ret = common.RET_OK
    for prop, import in pairs(data) do
        local func = sel_import_func[prop]
        if not func then
            log:notice('import %s has no func to process.', prop)
            goto continue
        end
        if func(self, ctx, import) == common.RET_ERROR then
            ret = common.RET_ERROR
        end
        ::continue::
    end
    return ret
end

function sel_management:on_export(ctx)
    local sel = self.sel_info
    if not sel then
        log:error('there is no sel configuration.')
        return {}
    end
    local sel_mode_str = sel.SelMode == cc.ROTATE_AFTER_FULL and 'RotateAfterFull' or 'ClearAfterFull'
    log:info('sel property [QuerySelMaxNum] and [SelRecordMode] export successfully.')
    return {QuerySelMaxNum = sel.QueryMaxCount, SelRecordMode = sel_mode_str}
end

local SPECIFIC_EVENT_TYPE = 0x6F
local event_code_config = {
    {SensorType = 0x0F, code2_mask = 0xFF, code3_mask = 0xFF},
    {SensorType = 0x1D, code2_mask = 0xFF, code3_mask = 0x02},
    {SensorType = 0xD0, code2_mask = 0xFF, code3_mask = 0x00},
}

local function make_event_code(sel_data)
    local event_type = sel_data.EventType == SPECIFIC_EVENT_TYPE and sel_data.SensorType or sel_data.EventType
    local code1 = sel_data.EventType == SPECIFIC_EVENT_TYPE and 0x00 or 0x40
    local offset = sel_data.EventData1 & 0x0F
    code1 = code1 | offset
    local code2 = 0xFF
    local code3 = 0xFF

    -- 针对个别传感器特殊处理
    if sel_data.EventType == SPECIFIC_EVENT_TYPE then
        for _, config in ipairs(event_code_config) do
            if sel_data.SensorType == config.SensorType then
                code2 = ((1 << offset) & config.code2_mask ~= 0) and sel_data.EventData2 or 0xFF
                code3 = ((1 << sel_data.EventData2) & config.code3_mask ~= 0) and sel_data.EventData3 or 0xFF
                break
            end
        end
    end

    local event_code = (event_type << 24) | (code1 << 16) | (code2 << 8) | code3
    return string.format('0x%08X', event_code)
end

function sel_management:report_sel_before_format(start, count)
    local events = self.db:query_sel(utils.REPORT_SEL_RULE, start, count)

    for _, v in pairs(events) do
        v.EventData1 = v.SelData1
        v.EventData2 = v.SelData2
        v.EventData3 = v.SelData3
        v.SensorName = v.SensorName or self:get_sensor_name(v.SensorId)
        v.EventType = common.get_event_type(v.SelEventType)
        v.Level, v.Description = self:get_sel_level_and_desc(v)
        v.Status = common.get_sel_status(v.SelEventType)
        v.EventCode = make_event_code(v)
        v.SensorType = common.transform_sensor_type(v.SensorType)
        v.TriggeringMode = 1
        v.ReportingChannel = 0xFFFF
        v.EventAction = 0
    end

    return #events, events
end

local report_sel_base = {
    'SensorName',
    'SensorType',
    'Level',
    'Description',
    'SelType',
    'EventType',
    'Status',
    'SubjectName',
    'EventCode',
    'TriggeringMode',
    'ReportingChannel',
    'EventAction',
    'Timestamp',
    'RecordSeq',
}
function sel_management:report_sel(start, count)
    local total, res = self:report_sel_before_format(start, count)
    res = common.format_sel_list(report_sel_base, res)
    return total, res
end

local function update_recordId(org_recordId, start_id, max_count)
    local RecordId = (org_recordId - start_id + max_count ) % max_count
    return RecordId == 0 and max_count or RecordId
end

function sel_management:get_sel_before_format(index, count)
    local query_max_count = self.sel_info.QueryMaxCount or cc.SEL_TOTOAL_NUM
    count = math.min(query_max_count, count)
    local start_id = self.mode_mgr:get_start_id(self.sel_info.CurRecordId, query_max_count, self.max_count)
    local rid = start_id + index
    rid = (rid > self.max_count) and (rid % self.max_count) or rid
    local total, events = self.mode_mgr:query_sel(self.db, rid, count,
        self.sel_info.CurRecordId, query_max_count)
    -- 补充及调整数据字段
    for _, v in pairs(events) do
        v.RecordId =  update_recordId(v.RecordId, start_id, self.max_count)
        v.EventData1 = v.SelData1
        v.EventData2 = v.SelData2
        v.EventData3 = v.SelData3
        v.SensorName = v.SensorName or self:get_sensor_name(v.SensorId)
        v.EventType = common.get_event_type(v.SelEventType)
        v.Level, v.Description = self:get_sel_level_and_desc(v)
        v.Level = common.transform_sel_status(v.Level)
        v.SensorType = common.transform_sensor_type(v.SensorType)
        v.Status = common.get_sel_status(v.SelEventType)
    end
    return total, events
end

local get_sel_base = {
    'RecordId',
    'Timestamp',
    'SensorName',
    'SensorType',
    'Level',
    'Status',
    'Description',
}
function sel_management:get_sel(start, count)
    local total, res = self:get_sel_before_format(start, count)
    res = common.format_sel_list(get_sel_base, res)
    return total, res
end

function sel_management:download_sel(ctx, path, task_id)
    local _, list = self:get_sel_before_format(1, self.db:query_sel_count())
    local is_success = common.pack_sel(list, path)
    if not is_success then
        task_lock = false
        utils:update_task(task_id,
            {State = task_state.Exception, Status = task_status.Warning, MessageId = 'InternalError'})
        oper.log(ctx, oper.SEL_EXPORT, oper.FAILED)
        error(err.sensor_error_map(err.ERR_INVALID_PATH))
    end
end

function sel_management:local_export_sel(ctx, path, task_id)
    utils:update_task(task_id, {State = task_state.Running, Progress = 50})

    skynet.fork_once(function()
        self:download_sel(ctx, path, task_id)
        utils:update_task(task_id, {State = task_state.Completed, Progress = 100, MessageId = 'Success'})
        oper.log(ctx, oper.SEL_COLLECTED, oper.SUCCESS)
        oper.log(ctx, oper.SEL_EXPORT, oper.SUCCESS)
        task_lock = false
    end)
end

function sel_management:export_sel(ctx, bus, path)
    -- 校验 path 的长度
    if string.len(path) > 255 then
        oper.log(ctx, oper.SEL_EXPORT, oper.FAILED)
        error(err.sensor_error_map(err.PROPERTY_VALUE_FORMAT_ERROR))
    end
    -- 校验 path 的合法性
    local header = skynet.getenv('TEST_DATA_DIR') and skynet.getenv('DUMP_FILE_PATH') or '/tmp'
    if file_sec.check_realpath_before_open_s(path, header) == -1 or
         file_sec.check_shell_special_character_s(path) ~= 0 then
        log:error('The path of exporting sensor event log data is invalid.')
        oper.log(ctx, oper.SEL_EXPORT, oper.FAILED)
        error(err.sensor_error_map(err.ERR_INVALID_PATH))
    end
    -- 任务重复性检查
    if task_lock then
        log:error("The task of exporting sensor event log list is running")
        oper.log(ctx, oper.SEL_EXPORT, oper.FAILED)
        error(err.sensor_error_map(err.DUPLICATE_EXPORTING_ERR))
    end
    task_lock = true
    -- 开启任务
    local task_id = utils:start_task(bus, 'Download', '/bmc/kepler/Chassis/1/Sensors', 5)
    self:local_export_sel(ctx, path, task_id)
    return task_id
end

return sel_management