-- 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_collection = {}
    self.db = db
end

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

function AlarmPool:get_event_list(system_id)
    return self.deassert_alarm_list_collection[system_id or 1]
end

function AlarmPool:reset(system_id)
    local deassert_alarm_list = self:get_event_list(system_id)
    if not deassert_alarm_list then
        return
    end
    pcall(function()
        for key, _ in pairs(deassert_alarm_list) do
            self:remove_event_from_db(key)
        end
    end)
    self.deassert_alarm_list_collection[system_id or 1] = {}
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)
    local system_id = event.SystemId == '' and 1 or tonumber(event.SystemId)
    local deassert_alarm_list = self:get_event_list(system_id)
    if not deassert_alarm_list then
        deassert_alarm_list = {}
        self.deassert_alarm_list_collection[system_id] = deassert_alarm_list
    end
    event.State = tostring(false)
    local key = event.ComponentName .. event.EventKeyId .. event.MessageArgs .. event.SystemId
    if deassert_alarm_list[key] then
        return
    end
    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 .. event_table.SystemId
        local system_id = event_table.SystemId == '' and 1 or tonumber(event_table.SystemId)
        if not self.deassert_alarm_list_collection[system_id] then
            self.deassert_alarm_list_collection[system_id] = {}
        end
        self.deassert_alarm_list_collection[system_id][key] = event_table
    end
end

function AlarmPool:remove_event(event)
    local system_id = event.SystemId == '' and 1 or tonumber(event.SystemId)
    local deassert_alarm_list = self:get_event_list(system_id)
    local key = event.ComponentName .. event.EventKeyId .. event.MessageArgs .. event.SystemId
    if not deassert_alarm_list or not deassert_alarm_list[key] then
        return
    end
    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_collection = {}
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:get_recover_status(system_id)
    return self.in_recover_collection[system_id or 1]
end

function AlarmManager:set_recover_status(system_id, prop)
    self.in_recover_collection[system_id or 1] = prop
end

function AlarmManager:recover(system_id)
    if self:get_recover_status(system_id) then
        log:error('recover state is true, not allow recover, system id: %s', system_id)
        return
    end
    self:set_recover_status(system_id, true)
    local event_list = self.event_pool:get_event_list(system_id)
    if not event_list then
        log:notice('event_list is nil, no need recover, system id: %s', system_id)
        self:set_recover_status(system_id, false)
        return
    end
    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:error('[bios]clear alarm events fail, event:%s; err:%s', k, err)
            end
        end
        self.event_pool:reset(system_id)
        self:set_recover_status(system_id, false)
    end)
end

return singleton(AlarmManager)