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

local class = require 'mc.class'
local log = require 'mc.logging'
local singleton = require 'mc.singleton'
local skynet = require 'skynet'
local c_object = require 'mc.orm.object'
local cjson = require 'cjson'
local chassis_service = require 'chassis_service'
local client = require 'chassis.client'
local context = require 'mc.context'
local mdb = require 'mc.mdb'

local NODEID_INVALID <const> = -1
local NODEID_DEFAULT <const> = 0xff
local M2TRANSFERCARD_ADC_VOLTAGE_ABNORMAL = '0x2D000033'
local M2_TRANSFER_CARD_PATH = '/bmc/kepler/Systems/1/Boards/M2TransferCard/M2TransferCard_1_'
local INVALID_MODE <const> = 'invalid value'
local SHORT_CIRCUIT_MODE <const> = 'short circuit'
local DISCONNECT_MODE <const> = 'disconnect'
local NODE_NOT_EQUAL_MODE <const> = 'not equal'

local node_id_identify = class()

local event_table = {
    [INVALID_MODE] = {},
    [SHORT_CIRCUIT_MODE] = {},
    [DISCONNECT_MODE] = {},
    [NODE_NOT_EQUAL_MODE] = {}
}

function node_id_identify:configuration_new(node_id, low, high)
    return {
        NodeId = node_id,
        Low = low,
        High = high
    }
end

function node_id_identify:parse_and_init_thresholdconfig(threshold_config)
    for k, v in pairs(threshold_config) do
        self.thresholdconfig[k] = self:configuration_new(table.unpack(v))
    end
end

function node_id_identify:get_M2transfercard_slot()
    local m2card_obj_path = M2_TRANSFER_CARD_PATH .. self.position
    -- AdcVoltageConfig和M.2 Card对象分别属于chassis和general_hardware组件，为了防止对象获取不到，进行一定延迟
    skynet.sleep(100*10)
    local ok, m2card_obj = pcall(mdb.get_object, self.bus, m2card_obj_path, 'bmc.kepler.Systems.Board')
    if not ok then
        log:notice('AdcVoltageConfig object is not belong to M2TransferCard, err:%s',m2card_obj)
        return
    end
    self.card_slot = m2card_obj.Slot
end

function node_id_identify:prepare_get_voltage(obj)
    local ok, ret = pcall(function ()
        obj.Model = obj.ModelConfig
    end)
    if not ok then
        log:error(ret)
    end
    self.channelregmap = obj.ChannelRegMap
    self:parse_and_init_thresholdconfig(obj.ThresholdConfig)
    self.nodeid_short_circuit = self.thresholdconfig[1].NodeId
    self.nodeid_not_present = self.thresholdconfig[#self.thresholdconfig].NodeId
    skynet.fork_once(function ()
        -- M2转接卡槽位只用于告警参数，不耦合节点识别
        -- 由于需等general_hardware中M2TransferCard对象先识别，此处创建协程进行等待
        self:get_M2transfercard_slot()
    end)
end

function node_id_identify:high_low_byte_conversion(voltage)
    local voltage_high_byte, voltage_low_byte
    voltage_high_byte = (voltage & 0xff) << 8
    voltage_low_byte = (voltage & 0xff00) >> 8
    return voltage_high_byte + voltage_low_byte
end

function node_id_identify:get_nodeid_by_adc_voltage(threshold_config, adc_voltage)
    local node_id
    for _, value in pairs(threshold_config) do
        if adc_voltage >= value.Low and adc_voltage <= value.High then
            node_id = value.NodeId
        end
    end
    if not node_id then
        node_id = NODEID_INVALID
    end
    return node_id
end

function node_id_identify:transfer_nodeid_by_mapping(obj)
    local times = 0
    local temp_id = self.nodeid_not_present
    local adc_voltage
    while times < 3 and (not self:check_nodeid_is_normal(temp_id) or temp_id == self.nodeid_not_present) do
        times = times + 1
        skynet.sleep(100)
        adc_voltage = self:high_low_byte_conversion(obj.Voltage)
        temp_id = self:get_nodeid_by_adc_voltage(self.thresholdconfig, adc_voltage)
    end
    return temp_id, adc_voltage
end

function node_id_identify:update_superpod_label_index()
    local super_pod_label_object = c_object('SuperPodLabel').collection:find({Id = 1})
    if super_pod_label_object == nil then
        log:error('SuperPodLabel is nil.')
        return
    end
    super_pod_label_object.LogicalSlotNumber = (self.node_id == self.nodeid_not_present and NODEID_DEFAULT or self.node_id)
end

function node_id_identify:generate_event(state, card_slot, adc_slot, abnormalmode)
    if not event_table[abnormalmode][adc_slot] and state == false then
        event_table[abnormalmode][adc_slot] = false
        return
    end
    if event_table[abnormalmode][adc_slot] ~= nil and event_table[abnormalmode][adc_slot] == state then
        return
    end
    local m2_card_slot = card_slot == nil and 1 or card_slot
    local message_arg = { m2_card_slot, adc_slot, abnormalmode }
    local params = {}
    params[#params + 1] = { Key = 'State', Value = tostring(state) }
    params[#params + 1] = { Key = 'ComponentName', Value = 'M2TransferCard' .. m2_card_slot }
    params[#params + 1] = { Key = 'EventKeyId', Value = 'M2TransferCard.M2TransferCardADCVoltageAbnormal' }
    params[#params + 1] = { Key = 'MessageArgs', Value = cjson.encode(message_arg) }
    params[#params + 1] = { Key = 'SystemId', Value = '1' }
    params[#params + 1] = { Key = 'ManagerId', Value = '1' }
    params[#params + 1] = { Key = 'ChassisId', Value = '1' }
    params[#params + 1] = { Key = 'AdditionalInfo', Value = '1,2,3' }
    params[#params + 1] = { Key = 'NodeId', Value = '' }
    local res = chassis_service:retry_add_event(params)
    if res == true then
        event_table[abnormalmode][adc_slot] = state
    end
end

function node_id_identify:generate_not_equal_event_msg(nodeids)
    --用于产生id不等的告警事件参数，返回示例 1，2，3 
    local str = ''
    for adc_point, _ in pairs(nodeids) do
        if str == '' then
            str = tostring(adc_point)
        else
            str = str .. ',' .. tostring(adc_point)
        end
    end
    return str
end

function node_id_identify:clear_all_adc_event()
    for abnormalmode, value in pairs(event_table) do
        if value then
            for adc_point, _ in pairs(value) do
                self:generate_event(false, self.card_slot, adc_point, abnormalmode)
            end
        end
    end
end

function node_id_identify:clear_other_adc_event(adc_point, adc_point_not_equal, abnormalmode)
    if not adc_point or not adc_point_not_equal or not abnormalmode then
        return
    end
    for key, value in pairs(event_table) do
        if key == abnormalmode then
            goto continue
        end
        if key == NODE_NOT_EQUAL_MODE then
            if not value[adc_point_not_equal] then
                goto continue
            end
            self:generate_event(false, self.card_slot, adc_point_not_equal, NODE_NOT_EQUAL_MODE)
        elseif not value[adc_point] then
            goto continue
        end
        self:generate_event(false, self.card_slot, adc_point, key)
        ::continue::
    end
end

function node_id_identify:deal_with_short_or_invalid_abnormal(node_ids, adc_point_not_equal)
    local final_id = nil
    local have_invalid_id =false
    local adc_point_not_present = nil
    for adc_point, value in pairs(node_ids) do
        if not self:check_nodeid_is_normal(value) then
            have_invalid_id = true
            --产生id非法对应告警
            if value == NODEID_INVALID and not event_table[INVALID_MODE][adc_point] then
                self:clear_other_adc_event(adc_point, adc_point_not_equal, INVALID_MODE)
                self:generate_event(true, self.card_slot, adc_point, INVALID_MODE)
            end
            --产生链路短路告警
            if value == self.nodeid_short_circuit and not event_table[SHORT_CIRCUIT_MODE][adc_point] then
                self:clear_other_adc_event(adc_point, adc_point_not_equal, SHORT_CIRCUIT_MODE)
                self:generate_event(true, self.card_slot, adc_point, SHORT_CIRCUIT_MODE)
            end
        else
            final_id = final_id == self.nodeid_not_present and self.nodeid_not_present or value
            adc_point_not_present = value == self.nodeid_not_present and adc_point or adc_point_not_present
        end
    end
    return have_invalid_id, final_id, adc_point_not_present
end

function node_id_identify:deal_with_normal_node(final_id)
        self.node_id = final_id
        self:update_superpod_label_index()
        self:clear_all_adc_event()
end

function node_id_identify:deal_with_disconnct_or_unequal_abnormal(node_ids, final_id, slot_not_present, adc_point_not_equal)
    if final_id == self.nodeid_not_present then
        --产生链路断路告警
        if not event_table[DISCONNECT_MODE][slot_not_present] then
            self:clear_other_adc_event(slot_not_present, adc_point_not_equal, DISCONNECT_MODE)
            self:generate_event(true, self.card_slot, slot_not_present, DISCONNECT_MODE)
        end
    else
        --产生id正常但不等对应告警
        if not event_table[NODE_NOT_EQUAL_MODE][adc_point_not_equal] then
            for adc_point, _ in pairs(node_ids) do
                self:clear_other_adc_event(adc_point, adc_point_not_equal, NODE_NOT_EQUAL_MODE)
            end
            self:generate_event(true, self.card_slot, adc_point_not_equal, NODE_NOT_EQUAL_MODE)
        end
    end
end

function node_id_identify:deal_with_nodeids(node_ids)
    local adc_point_not_equal = self:generate_not_equal_event_msg(node_ids)
    --短路和数据非法异常不刷新id，并告警
    local have_invalid_id, final_id, slot_not_present = self:deal_with_short_or_invalid_abnormal(node_ids, adc_point_not_equal)

    if not have_invalid_id then
        if self:check_nodeid_is_equal(node_ids) then --拔出或者链路正常
            self:deal_with_normal_node(final_id)
        else
            self:deal_with_disconnct_or_unequal_abnormal(node_ids, final_id, slot_not_present, adc_point_not_equal)
        end
    end
end

function node_id_identify:check_nodeid_is_normal(node_id)
    if node_id == self.nodeid_short_circuit  or node_id == NODEID_INVALID then
        return false
    end
    return true
end

function node_id_identify:check_nodeid_is_equal(nodeids)
    for _, v in pairs(nodeids) do
        if v ~= nodeids[1] then
            return false
        end
    end
    return true
end

function node_id_identify:get_and_check_nodeid(obj)
    local node_ids = {}
    local node_id, adc_voltage
    for key, value in pairs(self.channelregmap) do
        local ok, ret = pcall(function ()
            obj.ChannelReg = value
        end)
        if not ok then
            log:error(ret)
            return
        end
        node_id, adc_voltage= self:transfer_nodeid_by_mapping(obj)
        table.insert(node_ids, node_id)
        --硬件短路异常或者热插拔场景
        if node_id ~= self.node_id then
            log:notice('The Adc chip channel %d adcvoltage is %s, Node Id is %s', key, adc_voltage, node_id)
        end
    end
    self:deal_with_nodeids(node_ids)
end

function node_id_identify:add_object(mds_obj, position)
    self.node_id_obj = mds_obj
    self.position = position
    log:notice('add object AdcVoltageConfig successful')
    self:prepare_get_voltage(self.node_id_obj)
    skynet.fork_loop({ count = 0 }, function()
        self:get_and_check_nodeid(self.node_id_obj)
        skynet.sleep(100 * 25)
    end)
end

function node_id_identify:ctor(bus)
    log:notice('node_id_identify ctor start')
    self.node_id_obj = nil
    self.position = nil
    self.node_id = nil --最终生效，同步给serverindex的节点id
    self.nodeid_short_circuit = nil --ADC短路对应的节点id
    self.nodeid_not_present = nil --ADC断路对应的节点id
    self.thresholdconfig = {}
    self.channelregmap = nil
    self.card_slot = nil
    self.bus = bus
end

return singleton(node_id_identify)
