-- 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 singleton = require 'mc.singleton'
local event_util = require 'infrastructure.event'
local skynet = require 'skynet'
local log = require 'mc.logging'
local AlarmPool = class()

function AlarmPool:ctor(db)
    self.deassert_alarm_list = {}
    self.db = db
end

function AlarmPool:init()
    self:find_all_event()
end

function AlarmPool:get_event_list()
    return self.deassert_alarm_list
end

function AlarmPool:reset()
    self.deassert_alarm_list = {}
    pcall(function()
        self.db:delete(self.db.EventMsg):exec()
    end)
end

local function db_prop(val)
    if not val then
        return 'NA'
    end
    return val
end

function AlarmPool:add_event_to_db(event, id)
    local row_data = self.db.EventMsg({
        Id = id,
        ComponentName = db_prop(event.ComponentName),
        State = db_prop(event.State),
        EventKeyId = db_prop(event.EventKeyId),
        MessageArgs = db_prop(event.MessageArgs),
        SystemId = db_prop(event.SystemId),
        ManagerId = db_prop(event.ManagerId),
        ChassisId = db_prop(event.ChassisId),
        NodeId = db_prop(event.NodeId),
        SubjectType = db_prop(event.SubjectType)
    })
    row_data:save()
end

function AlarmPool:add_event(event)
    event.State = tostring(false)
    local key = event.ComponentName .. event.EventKeyId .. event.MessageArgs
    if self.deassert_alarm_list[key] then
        return
    end
    self.deassert_alarm_list[key] = event
    self:add_event_to_db(event, key)
end

local function memory_prop(val)
    if val == 'NA' then
        return nil
    end
    return val
end

function AlarmPool:remove_event_from_db(id)
    self.db:delete(self.db.EventMsg):where({Id = id}):exec()
end

function AlarmPool:find_all_event()
    local all_datas = self.db:select(self.db.EventMsg):all()
    for _, row_data in pairs(all_datas) do
        local event_table = {
            ComponentName = memory_prop(row_data.ComponentName),
            State = memory_prop(row_data.State),
            EventKeyId = memory_prop(row_data.EventKeyId),
            MessageArgs = memory_prop(row_data.MessageArgs),
            SystemId = memory_prop(row_data.SystemId),
            ManagerId = memory_prop(row_data.ManagerId),
            ChassisId = memory_prop(row_data.ChassisId),
            NodeId = memory_prop(row_data.NodeId),
            SubjectType = memory_prop(row_data.SubjectType)
        }
        local key = event_table.ComponentName .. event_table.EventKeyId .. event_table.MessageArgs
        self.deassert_alarm_list[key] = event_table
    end
end

function AlarmPool:remove_event(event)
    local key = event.ComponentName .. event.EventKeyId .. event.MessageArgs
    if not self.deassert_alarm_list[key] then
        return
    end
    self.deassert_alarm_list[key] = nil
    self:remove_event_from_db(key)
end

local AlarmManager = class()

function AlarmManager:ctor(db)
    self.coroutine_num = 0
    self.event_pool = AlarmPool.new(db)
    self.db = db
    self.in_recover = false
end

function AlarmManager:manager_event(event)
    if event.State == tostring(true) then
        self.event_pool:add_event(event)
    else
        self.event_pool:remove_event(event)
    end
end

function AlarmManager:recover()
    if self.in_recover then
        return
    end
    self.in_recover = true
    local event_list = self.event_pool:get_event_list()
    skynet.fork_once(function()
        for k, v in pairs(event_list) do
            local ok, err = pcall(function()
                event_util.generate_event(v)
            end)
            if ok then
                log:notice('[bios]clear alarm events success, event:%s', k)
            else
                log:notice('[bios]clear alarm events fail, event:%s; err:%s', k, err)
            end
        end
        self.event_pool:reset()
        self.in_recover = false
    end)
end

return singleton(AlarmManager)