-- Copyright (c) 2026 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: 组件软件告警管理
local class = require 'mc.class'
local singleton = require 'mc.singleton'
local log = require 'mc.logging'
local cjson = require 'cjson'
local cmn = require 'common'
local client = require 'pcie_device.client'
local event = require 'infrastructure.event'
local skynet = require 'skynet'

local SMBIOS_CHECK_COUNT = 3
local CONNECTOR_INTF = 'bmc.kepler.Connector'
local DEFAULT_SMBIOS_PATH = '/bmc/kepler/Systems/1/SmBios'

local software_event_mgmt = class()

local CSR_FAILURE_EVENT_COMPONENT = 'BMC'
local CSR_FAILURE_EVENT_KEY_ID = 'BMC.AccessCSRFailure'
local CSR_FAILURE_EVENT_CODE = '0x1A000055'
local CSR_FAILURE_EVENT_ARGS_NUM = 2
local BMC_SUBJECT_TYPE = 0x1A

-- 返回值1：boolean，是否匹配到告警  返回值2：匹配到的告警列表 or nil
local function get_alarm_list_matched(bus, component_name, event_name, event_code, msg_args)
    log:debug('[pcie_device] get_alarm_list_matched: component name %s, event name %s, event_code %s, msg_args %s',
        component_name, event_name, event_code, msg_args)

    local ok, alarm_list = pcall(event.get_latest_alarm_list, bus)
    if ok and alarm_list then
        log:debug('[pcie_device] get_latest_alarm_list successfully.')
    else
        log:debug('[pcie_device] get_latest_alarm_list failed, err: %s', alarm_list)
        return false, nil
    end

    -- 可选地根据 ComponentName、EventName、EventCode、MessageArgs匹配告警，传参为空则跳过匹配
    local alarm_list_matched = {}
    for _, alarm in pairs(alarm_list) do
        if component_name and (not alarm.ComponentName or alarm.ComponentName ~= component_name) then
            log:debug('[pcie_device] alarm.ComponentName %s not match', alarm.ComponentName)
            goto continue
        end
        if event_code and (not alarm.EventCode or alarm.EventCode ~= event_code) then
            log:debug('[pcie_device] alarm.EventCode %s not match', alarm.EventCode)
            goto continue
        end
        if event_name and (not alarm.EventName or alarm.EventName ~= event_name) then
            log:debug('[pcie_device] alarm.EventName %s not match', alarm.EventName)
            goto continue
        end
        if msg_args and (not alarm.MessageArgs or alarm.MessageArgs ~= msg_args) then
            log:debug('[pcie_device] alarm.MessageArgs %s not match', alarm.MessageArgs)
            goto continue
        end
        table.insert(alarm_list_matched, alarm)
        ::continue::
    end
    return #alarm_list_matched > 0, #alarm_list_matched > 0 and alarm_list_matched or nil
end

local function parse_tc_csr_failure_args(tc_conn, pai)
    if not pai or pai == '' then
        log:debug('[pcie_device] parse_tc_csr_failure_args failed, pai is empty')
        return false, {}
    end

    local slot = string.match(pai.Location or '', '^RiserCard(%d+)$')
    if slot then
        return true, {'PCIeRiser' .. slot, string.format('%s%s', tc_conn.Type, tc_conn.Slot)}
    end
    return false, {}
end

function software_event_mgmt:generate_tc_conn_csr_failure_event(state, tc_conn, pai)
    log:debug('[pcie_device] generate tc_conn (%s) csr failure event start', tc_conn.path)
    -- state 为空时，告警状态不变
    if state == nil then
        return
    end

    local ok, msg_args = parse_tc_csr_failure_args(tc_conn, pai)
    if not ok or not msg_args or not next(msg_args) or #msg_args ~= CSR_FAILURE_EVENT_ARGS_NUM then
        log:debug('[pcie_device] generate_tc_conn_csr_failure_event failed, args (%s) invalid', cjson.encode(msg_args))
        return
    end

    local ret, alarm_exist

    alarm_exist, _ = get_alarm_list_matched(self.bus, CSR_FAILURE_EVENT_COMPONENT, nil, CSR_FAILURE_EVENT_CODE,
        cjson.encode(msg_args))

    if state == false and alarm_exist then
        -- 消除告警
        ok, ret = pcall(cmn.generate_event, CSR_FAILURE_EVENT_KEY_ID, msg_args, 'false',
            CSR_FAILURE_EVENT_COMPONENT, BMC_SUBJECT_TYPE)

        if not ok then
            log:error('deassert csr_failure_event failed, error: %s', ret)
        else
            log:notice('deassert csr_failure_event successfully, args: %s', cjson.encode(msg_args))
        end
    end
    if state == true and not alarm_exist then
        -- 产生告警
        ok, ret = pcall(cmn.generate_event, CSR_FAILURE_EVENT_KEY_ID, msg_args, 'true',
            CSR_FAILURE_EVENT_COMPONENT, BMC_SUBJECT_TYPE)

        if not ok then
            log:error('assert csr_failure_event failed, error: %s', ret)
        else
            log:notice('assert csr_failure_event successfully, args: %s', cjson.encode(msg_args))
        end
    end
    log:debug('[pcie_device] generate tc_conn (%s) csr failure event finished', tc_conn.path)
end

local function tc_conn_load_abnormal(tc_conn)
    return tc_conn.Presence == 1 and tc_conn.LoadStatus ~= 0 and tc_conn.LoadStatus ~= 255
end

function software_event_mgmt:update_state_task(ref_tc_conn, ref_conn)
    log:debug('[pcie_device] update tc_conn (%s) csr failure state start', ref_tc_conn.path)
    local state
    -- 天池Connector 加载正常，预期不告警
    if not ref_tc_conn or not tc_conn_load_abnormal(ref_tc_conn) then
        return false
    end
    -- 天池加载异常场景：如果标卡未加载，则告警；标卡加载、则不告警； + 如果smbios未准备好，则不做告警判断，
    if self.smbios_ready == true then
        if ref_conn and ref_conn.Presence == 0 then
            state = true
        else
            state = false
        end
    end
    log:debug('[pcie_device] update tc_conn (%s) csr failure state finished, state %s', ref_tc_conn.path, state)
    return state
end

function software_event_mgmt:update_conn_tc_csr_failure_event(ref_tc_conn, ref_conn, ref_pai)
    -- 先监听天池connector在位和加载状态变化
    client:OnConnectorPropertiesChanged(function(values, path, interface)
        if interface ~= CONNECTOR_INTF or path ~= ref_tc_conn.path then
            log:debug('[pcie_device] listen tc_conn prop: %s (%s) mismatch', path, interface)
            return
        end

        if not values['Presence'] and not values['LoadStatus'] then
            log:debug('[pcie_device] listen tc_conn (%s) prop: presence no change', ref_tc_conn.path)
            return
        end
        local s = self:update_state_task(ref_tc_conn, ref_conn)
        self:generate_tc_conn_csr_failure_event(s, ref_tc_conn, ref_pai)
    end)

    -- 等待天池Connector在位状态稳定
    cmn.skynet.sleep(60 * 100)
    local state = self:update_state_task(ref_tc_conn, ref_conn)
    self:generate_tc_conn_csr_failure_event(state, ref_tc_conn, ref_pai)
end

-- 连续3次扫描到 SmBiosStatus 为3时，表示smbios就绪，并判断1次告警
function software_event_mgmt:update_smbios_ready_task()
    self.smbios_obj = client:GetSmBiosObjects()[DEFAULT_SMBIOS_PATH]
    if not self.smbios_obj then
        log:debug('[pcie_device] smbios_obj not ready')
        return
    end

    for __ = 1, SMBIOS_CHECK_COUNT do
        if self.smbios_obj.SmBiosStatus ~= 3 then
            self.smbios_ready = false
            log:debug('[pcie_device] smbios not ready, status %s', self.smbios_obj.SmBiosStatus)
            return
        end
        cmn.skynet.sleep(60 * 100)
    end
    self.smbios_ready = true
    log:debug('[pcie_device] smbios ready, update tc_conn csr failure event')

    local state
    for _, conn_pair in pairs(self.conn_pair_list) do
        state = self:update_state_task(conn_pair.tc_conn, conn_pair.conn)
        self:generate_tc_conn_csr_failure_event(state, conn_pair.tc_conn, conn_pair.pai)
    end
end

function software_event_mgmt:register_smbios_signal()
    -- 监听 SmBiosStatus 变化
    client:OnSmBiosPropertiesChanged(function(values, path, _)
        if path ~= DEFAULT_SMBIOS_PATH then
            return
        end
        if not values['SmBiosStatus'] then
            return
        end
        self:update_smbios_ready_task()
    end)
    -- 监听对象上树
    client:OnSmBiosInterfacesAdded(function(_, path, _)
        if path ~= DEFAULT_SMBIOS_PATH then
            return
        end
        self:update_smbios_ready_task()
    end)
    -- 获取一次对象
    client:ForeachSmBiosObjects(function(obj)
        if obj.path ~= DEFAULT_SMBIOS_PATH then
            return
        end
        self:update_smbios_ready_task()
    end)
end

function software_event_mgmt:init()
    skynet.fork_once(function ()
        self:register_smbios_signal()
    end)
end

local update_event_task_list = {
    ['tc_connector_csr_failure'] = software_event_mgmt.update_conn_tc_csr_failure_event
}

function software_event_mgmt:ctor(bus)
    self.bus = bus
    self.update_event_task_list = update_event_task_list
    self.conn_pair_list = {}
    self.smbios_ready = false
end

-- test
software_event_mgmt.get_alarm_list_matched = get_alarm_list_matched
software_event_mgmt.parse_tc_csr_failure_args = parse_tc_csr_failure_args

return singleton(software_event_mgmt)