-- Copyright (c) 2025 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 log = require 'mc.logging'
local context = require 'mc.context'
local skynet = require 'skynet'
local vos = require 'utils.vos'
local utils = require 'mc.utils'
local client = require 'network_adapter.client'
local fructl = require 'infrastructure.fructl'
local npu_imu_cmd = require 'npu.hdk_cmd'
local dfx_defs = require 'dfx_collect.dfx_defs'
local dfx_collector = require 'dfx_collect.dfx_collect_mgnt'
local ipmi = require 'ipmi'
local comp_code = ipmi.types.Cc
local c_om_event_handler = require 'infrastructure.om_event_handler'
local npu_om_event_debounce_config = require 'event.debounce_config.npu_om'
local prbs = require 'prbs_test.optical_module_prbs_test'

local UNIT_CONVERSION1<const> = 10000 -- 06 8e接口电源、功率精度转换
local UNIT_CONVERSION2<const> = 1000 -- 06 8e接口电流精度转换

local optical_module_npu = {}

local default_npu_optical_module_value<const> = {
    Presence = 0,
    ReadingCelsius = 0xFFFF,
    RxLossState = false,
    TxFaultState = false,
    TXBiasCurrentMilliAmps = {0xFFFF},
    TXOutputPowerMilliWatts = {0xFFFF},
    RXInputPowerMilliWatts = {0xFFFF},
    SupplyVoltage = 0xFFFF,
    Vol_UpperThresholdCritical = 0xFFFF,
    Vol_LowerThresholdCritical = 0xFFFF,
    VoltageLowerThresholdCritical = 0xFFFF,
    VoltageUpperThresholdCritical = 0xFFFF,
    Power_TXUpperThresholdCritical = 0xFFFF,
    Power_TXLowerThresholdCritical = 0xFFFF,
    RXLowerThresholdCritical = 0xFFFF,
    RXUpperThresholdCritical = 0xFFFF,
    Temp_LowerThresholdCritical = 0xFFFF,
    Temp_UpperThresholdCritical = 0xFFFF,
    TemperatureLowerThresholdCritical = 0xFFFF,
    TemperatureUpperThresholdCritical =0xFFFF,
    BC_TXUpperThresholdCritical = 0xFFFF,
    BC_TXLowerThresholdCritical = 0xFFFF,
    RXInputPowerStatus = 0,

    TXOutputPowerStatus = 0,
    TXBiasCurrentStatus = 0,
    TxLoS = 0,
    RxLoS = 0,
    TxFault = 0,
    TxLossOfLock = 0,
    RxLossOfLock = 0,
    TemperatureCelsius = 255,
    Accessible = 0,
    SelfTestStatus = -1,
    MediaInterfaceFaultStatus = 0xFFFF,
    HostInterfaceFaultStatus = 0xFFFF
}

local function register_npu_property_changed_callback(orm_obj)
    local switch = {
        FiberDirtyDetected = 1,
        FiberLoosenessDetected = 2
    }
    orm_obj:connect_signal(orm_obj.on_property_changed, function(name, value)
        if switch[name] then
            local ctx = context.get_context()
            if ctx and not ctx:is_empty() then
                log:notice('%s update %sPort%sOpticalModule %s to %s', ctx.Requestor,
                orm_obj.NetworkAdapterId, orm_obj.PortID + 1, name, value)
            end
        end
    end)
end

-- NPU光模块监听os下电或复位信号后恢复脏污、松动告警
local function npu_register_listen_os_status(orm_obj)
    client:OnFruCtrlPropertiesChanged(function(values, _, _)
        local sys_reset_detected, power_state
        if values.SysResetDetected then
            sys_reset_detected = values.SysResetDetected:value()
        elseif values.PowerState then
            power_state = values.PowerState:value()
        end
        if sys_reset_detected == 1 or power_state == 'OFF' then -- 为1代表OS复位
            orm_obj.FiberDirtyDetected = false
            orm_obj.FiberLoosenessDetected = false
        end
    end)
end

local function reset_npu_om(orm_obj)
    for k, v in pairs(default_npu_optical_module_value) do
        orm_obj[k] = v
    end
end

local function filled_resource_tree_runtime_attributes(orm_obj, runtime_info)
    if runtime_info then
        orm_obj.PowerOnCount = runtime_info.PowerOnCount
        orm_obj.UptimeSeconds = runtime_info.UptimeSeconds
        orm_obj.PowerStatus = runtime_info.PowerStatus
        orm_obj.OdspDieTemperatureCelsius = runtime_info.OdspDieTemperatureCelsius
        orm_obj.OdspHighTempRuntimeSeconds = runtime_info.OdspHighTempRuntimeSeconds
        orm_obj.LaserRuntimeSeconds = runtime_info.LaserRuntimeSeconds
        orm_obj.LaserTemperatureCelsius = runtime_info.LaserTemperatureCelsius
    end
end

local function filled_resource_tree_alarm_attributes(orm_obj, alarm_info)
    if alarm_info then
        orm_obj.SelfTestStatus = alarm_info.HardwareFaultStatus
        orm_obj.HostInterfaceFaultStatus = alarm_info.HostInterfaceFaultStatus
        orm_obj.MediaInterfaceFaultStatus = alarm_info.MediaInterfaceFaultStatus
    end
end

local function filled_resource_tree_statistics_info(orm_obj, statistics_info)
    if not statistics_info then
        return
    end
    orm_obj.CorrectableFECErrors = statistics_info.CorrectableFECErrors
    orm_obj.UnCorrectableFECErrors = statistics_info.UnCorrectableFECErrors
end

local function filled_resource_tree_state_attributes(orm_obj, state_info)
    orm_obj.Presence = 1
    orm_obj.SupplyVoltage = state_info.SupplyVoltage
    orm_obj.TXOutputPowerMilliWatts = state_info.TXOutputPowerMilliWatts
    orm_obj.RXInputPowerMilliWatts = state_info.RXInputPowerMilliWatts
    orm_obj.TXBiasCurrentMilliAmps = state_info.TXBiasCurrentMilliAmps
    orm_obj.TxLoS = state_info.TxLoS
    orm_obj.RxLoS = state_info.RxLoS
    orm_obj.TxFault = state_info.TxFault
    orm_obj.TxLossOfLock = state_info.TxLossOfLock
    orm_obj.RxLossOfLock = state_info.RxLossOfLock
    orm_obj.TemperatureCelsius = state_info.TemperatureCelsius
    orm_obj.ReadingCelsius = state_info.TemperatureCelsius
    orm_obj.Accessible = state_info.Accessible
    orm_obj.HostSNR = state_info.HostSNR
    orm_obj.MediaSNR = state_info.MediaSNR
    orm_obj.RxSNR = state_info.RxSNR
end

local function filled_resource_tree_presence_attributes(orm_obj)
    orm_obj.Presence = 0
    orm_obj.FiberDirtyDetected = false
    orm_obj.FiberLoosenessDetected = false
end

local first_statistics = {}
local optical_max_and_min_info = {}

-- 处理每周期内 第一帧消息作为参考数据
local function process_first_optical_info_in_period(npu_id, state_info, runtime_info)
    optical_max_and_min_info[npu_id].vcc_max = state_info.SupplyVoltage
    optical_max_and_min_info[npu_id].vcc_min = state_info.SupplyVoltage
    optical_max_and_min_info[npu_id].tx_power_max = utils.table_copy(state_info.TXOutputPowerMilliWatts)
    optical_max_and_min_info[npu_id].tx_power_min = utils.table_copy(state_info.TXOutputPowerMilliWatts)
    optical_max_and_min_info[npu_id].rx_power_max = utils.table_copy(state_info.RXInputPowerMilliWatts)
    optical_max_and_min_info[npu_id].rx_power_min = utils.table_copy(state_info.RXInputPowerMilliWatts)
    optical_max_and_min_info[npu_id].tx_bias_max = utils.table_copy(state_info.TXBiasCurrentMilliAmps)
    optical_max_and_min_info[npu_id].tx_bias_min = utils.table_copy(state_info.TXBiasCurrentMilliAmps)
    optical_max_and_min_info[npu_id].tx_los = state_info.TxLoss
    optical_max_and_min_info[npu_id].rx_los = state_info.RxLoss
    optical_max_and_min_info[npu_id].tx_lol = state_info.TxLossOfLock
    optical_max_and_min_info[npu_id].rx_lol = state_info.RxLossOfLock
    optical_max_and_min_info[npu_id].temperature_max = state_info.TemperatureCelsius
    optical_max_and_min_info[npu_id].temperature_min = state_info.TemperatureCelsius
    optical_max_and_min_info[npu_id].odsp_die_temp_max = runtime_info.OdspDieTemperatureCelsius
    optical_max_and_min_info[npu_id].odsp_die_temp_min = runtime_info.OdspDieTemperatureCelsius
    optical_max_and_min_info[npu_id].odsp_high_temp_runtime = runtime_info.OdspHighTempRuntimeSeconds
    optical_max_and_min_info[npu_id].host_snr_max = utils.table_copy(state_info.HostSNR)
    optical_max_and_min_info[npu_id].host_snr_min = utils.table_copy(state_info.HostSNR)
    optical_max_and_min_info[npu_id].media_snr_max = utils.table_copy(state_info.MediaSNR)
    optical_max_and_min_info[npu_id].media_snr_min = utils.table_copy(state_info.MediaSNR)
end

-- 处理每周期内最值
local function process_max_min_optical_info_in_period(npu_id, state_info, runtime_info)
    optical_max_and_min_info[npu_id].vcc_max = math.max(optical_max_and_min_info[npu_id].vcc_max,
        state_info.SupplyVoltage)
    optical_max_and_min_info[npu_id].vcc_min = math.min(optical_max_and_min_info[npu_id].vcc_min,
        state_info.SupplyVoltage)
    for i = 1, #state_info.TXOutputPowerMilliWatts do
        optical_max_and_min_info[npu_id].tx_power_max[i] =
            math.max(optical_max_and_min_info[npu_id].tx_power_max[i],
            state_info.TXOutputPowerMilliWatts[i])
        optical_max_and_min_info[npu_id].tx_power_min[i] =
            math.min(optical_max_and_min_info[npu_id].tx_power_min[i],
            state_info.TXOutputPowerMilliWatts[i])
        optical_max_and_min_info[npu_id].rx_power_max[i] =
            math.max(optical_max_and_min_info[npu_id].rx_power_max[i],
            state_info.RXInputPowerMilliWatts[i])
        optical_max_and_min_info[npu_id].rx_power_min[i] =
            math.min(optical_max_and_min_info[npu_id].rx_power_min[i],
            state_info.RXInputPowerMilliWatts[i])
        optical_max_and_min_info[npu_id].tx_bias_max[i] =
            math.max(optical_max_and_min_info[npu_id].tx_bias_max[i],
            state_info.TXBiasCurrentMilliAmps[i])
        optical_max_and_min_info[npu_id].tx_bias_min[i] =
            math.min(optical_max_and_min_info[npu_id].tx_bias_min[i],
            state_info.TXBiasCurrentMilliAmps[i])
        optical_max_and_min_info[npu_id].host_snr_max[i] =
            math.max(optical_max_and_min_info[npu_id].host_snr_max[i],
            state_info.HostSNR[i])
        optical_max_and_min_info[npu_id].host_snr_min[i] =
            math.min(optical_max_and_min_info[npu_id].host_snr_min[i],
            state_info.HostSNR[i])
        optical_max_and_min_info[npu_id].media_snr_max[i] =
            math.max(optical_max_and_min_info[npu_id].media_snr_max[i],
            state_info.MediaSNR[i])
        optical_max_and_min_info[npu_id].media_snr_min[i] =
            math.min(optical_max_and_min_info[npu_id].media_snr_min[i],
            state_info.MediaSNR[i])
    end
    optical_max_and_min_info[npu_id].tx_los = optical_max_and_min_info[npu_id].tx_los | state_info.TxLoss
    optical_max_and_min_info[npu_id].rx_los = optical_max_and_min_info[npu_id].rx_los | state_info.RxLoss
    optical_max_and_min_info[npu_id].tx_lol = optical_max_and_min_info[npu_id].tx_lol | state_info.TxLossOfLock
    optical_max_and_min_info[npu_id].rx_lol = optical_max_and_min_info[npu_id].rx_lol | state_info.RxLossOfLock
    optical_max_and_min_info[npu_id].temperature_max =
        math.max(optical_max_and_min_info[npu_id].temperature_max, state_info.TemperatureCelsius)
    optical_max_and_min_info[npu_id].temperature_min =
        math.min(optical_max_and_min_info[npu_id].temperature_min, state_info.TemperatureCelsius)
    optical_max_and_min_info[npu_id].odsp_die_temp_max =
        math.max(optical_max_and_min_info[npu_id].odsp_die_temp_max, runtime_info.OdspDieTemperatureCelsius)
    optical_max_and_min_info[npu_id].odsp_die_temp_min =
        math.min(optical_max_and_min_info[npu_id].odsp_die_temp_min, runtime_info.OdspDieTemperatureCelsius)
    optical_max_and_min_info[npu_id].odsp_high_temp_runtime = runtime_info.OdspHighTempRuntimeSeconds
end

-- 获取某NPU获取到的光模块最值信息
local function get_max_and_min_optical_info(npu_id, state_info, runtime_info)
    if first_statistics[npu_id] == nil then
        first_statistics[npu_id] = true
    end
    -- 周期内第一帧消息处理
    if first_statistics[npu_id] then
        optical_max_and_min_info[npu_id] = {
            log_time = vos.vos_tick_get() // 1000,
            npu_id = npu_id,
            component = dfx_defs.component.OPTICAL_MODULE,
            msg_type = dfx_defs.optical_module.PERIOD,
        }
        process_first_optical_info_in_period(npu_id, state_info, runtime_info)
        first_statistics[npu_id] = false
        return nil
    end
    -- 周期内每帧最值比较
    process_max_min_optical_info_in_period(npu_id, state_info, runtime_info)
    return optical_max_and_min_info[npu_id]
end

-- 资源树上光模块电压、tx\rx功率、偏置电流与日志记录的单位不一致，这里做转换
local function unit_conversion(dfx_msg)
    dfx_msg.vcc_max = math.floor(dfx_msg.vcc_max * UNIT_CONVERSION1)
    dfx_msg.vcc_min = math.floor(dfx_msg.vcc_min * UNIT_CONVERSION1)
    for i = 1, #dfx_msg.tx_power_max do
        dfx_msg.tx_power_max[i] = math.floor(dfx_msg.tx_power_max[i] * UNIT_CONVERSION1)
        dfx_msg.tx_power_min[i] = math.floor(dfx_msg.tx_power_min[i] * UNIT_CONVERSION1)
        dfx_msg.rx_power_max[i] = math.floor(dfx_msg.rx_power_max[i] * UNIT_CONVERSION1)
        dfx_msg.rx_power_min[i] = math.floor(dfx_msg.rx_power_min[i] * UNIT_CONVERSION1)
        dfx_msg.tx_bias_max[i] = math.floor(dfx_msg.tx_bias_max[i] * UNIT_CONVERSION2)
        dfx_msg.tx_bias_min[i] = math.floor(dfx_msg.tx_bias_min[i] * UNIT_CONVERSION2)
    end
    return dfx_msg
end

local function process_period_dfx_msg(id, state_info, runtime_info)
    -- 部分空表，表示属性捞取异常，这里不做处理
    if #state_info.TXOutputPowerMilliWatts == 0 or
        #state_info.RXInputPowerMilliWatts == 0 or
        #state_info.TXBiasCurrentMilliAmps == 0 or
        #state_info.HostSNR == 0 or
        #state_info.MediaSNR == 0 then
        return
    end
    local cur_time = vos.vos_tick_get() // 1000
    local dfx_msg = get_max_and_min_optical_info(id, state_info, runtime_info)
    -- 每45min记录一条极值dfx记录
    if dfx_msg and (cur_time - optical_max_and_min_info[id].log_time) > 45 * 60 then
        dfx_msg = unit_conversion(dfx_msg)
        first_statistics[id] = true
        dfx_collector.get_instance():msg_productor(dfx_msg)
    end
end

local function set_thresholds(orm_obj, state_info, result)
    if state_info and result then
        orm_obj.TemperatureLowerThresholdCritical = state_info.TemperatureLowerThresholdCritical
        orm_obj.TemperatureUpperThresholdCritical = state_info.TemperatureUpperThresholdCritical
        orm_obj.VoltageLowerThresholdCritical = state_info.VoltageLowerThresholdCritical
        orm_obj.VoltageUpperThresholdCritical = state_info.VoltageUpperThresholdCritical
        orm_obj.Power_TXLowerThresholdCritical = state_info.Power_TXLowerThresholdCritical
        orm_obj.Power_TXUpperThresholdCritical = state_info.Power_TXUpperThresholdCritical
        orm_obj.RXLowerThresholdCritical = state_info.RXLowerThresholdCritical
        orm_obj.RXUpperThresholdCritical = state_info.RXUpperThresholdCritical
        orm_obj.BC_TXLowerThresholdCritical = state_info.BC_TXLowerThresholdCritical
        orm_obj.BC_TXUpperThresholdCritical = state_info.BC_TXUpperThresholdCritical
        orm_obj.PowerRXLowerThresholdWarning = state_info.PowerRXLowerThresholdWarning
    end
end

-- 更新光模块门限值
local function check_update_thresholds(orm_obj, id)
    local err, result, state_info = pcall(npu_imu_cmd.get_optical_thresholds, id)
    if not err then
        if log:getLevel() >= log.DEBUG then
            log:debug(state_info)
        end
        return
    end
    set_thresholds(orm_obj, state_info, result)
end

-- 更新各通道偏置电流读数
local function update_bias_current(orm_obj)
    local prop_name
    local ok, err = pcall(function()
        for k, v in ipairs(orm_obj.TXBiasCurrentMilliAmps) do
            prop_name = "TXBiasCurrent" .. k .. "MilliAmps"
            orm_obj[prop_name] = v
        end
    end)
    if not ok then
        if log:getLevel() >= log.DEBUG then
            log:debug("update_bias_current failed, err=%s", err)
        end
    end
end

-- 更新PRBS测试相关信息
local function update_prbs_test_info(orm_obj, id)
    -- 1、更新光模块列表信息：位置校正 和 不在位光模块位置信息清除
    prbs.get_instance():update_test_object(id, orm_obj.SerialNumber, orm_obj.Presence)
    if orm_obj.Presence == 0 then
        orm_obj.PRBSTestSupported = false
        return
    end
    -- 2、获取第一步更新后prbs_test对象
    local prbs_test_object = prbs.get_instance():get_test_object_by_object_id(orm_obj.SerialNumber, false)
    -- 说明光模块索引在第一步函数就已经清除了
    if prbs_test_object == nil then
        orm_obj.PRBSTestSupported = false
        return
    end
    log:debug('npu id %s, prbs test object id %s, support prbs test %s, presence:%s',
        prbs_test_object.npu_id, prbs_test_object.test_object_id,
        prbs_test_object.support_prbs_test, prbs_test_object.presence)
    -- 光模块在位且PRBS Test属性未初始化，需要查询
    if prbs_test_object.support_prbs_test == nil then
        local prbs_test_result = prbs_test_object:get_prbs_test_supported()
        if prbs_test_result == nil or prbs_test_result == false then
            orm_obj.PRBSTestSupported = false
            return
        end
    end
    orm_obj.PRBSTestSupported = prbs_test_object.support_prbs_test
end

local function init_npu_optical_module_info(orm_obj, id)
    local runtime_info = npu_imu_cmd.get_op_runtime_info(id)
    filled_resource_tree_runtime_attributes(orm_obj, runtime_info)
    local alarm_info = npu_imu_cmd.get_op_alarm_info(id)
    filled_resource_tree_alarm_attributes(orm_obj, alarm_info)
    local statistics_info = npu_imu_cmd.get_op_statistics_info(id)
    filled_resource_tree_statistics_info(orm_obj, statistics_info)
    -- 首次获取，或距离上次获取超过600s才更新op base info, 否则沿用之前的数据
    if (orm_obj.last_record_time == 0) or
        (skynet.now() - orm_obj.last_record_time > 60000)then
        log:debug('npu op_base_info is time to update, id:%s', id)
        local err1, result, base_info_table = pcall(npu_imu_cmd.get_op_base_info, id)
        orm_obj.last_record_time = skynet.now()
        -- 获取信息不成功时不刷新静态信息
        if result == comp_code.Success then
            orm_obj:filled_resource_tree_base_attributes(base_info_table)
        else
            log:debug('failed to call get npu%s op base info:%s ,error:%s', id, result, err1)
        end
    end
    local err2, result, state_info = pcall(npu_imu_cmd.get_optical_info_from_imu, id, orm_obj.ChannelNum)
    if not err2 then
        log:debug('failed to get npu%s op state info', id)
        return
    end
    if result then
        filled_resource_tree_state_attributes(orm_obj, state_info)
    else
        filled_resource_tree_presence_attributes(orm_obj)
    end

    -- 光模块日志记录
    if runtime_info and state_info then
        process_period_dfx_msg(id, state_info, runtime_info)
    end
    -- 光模块闪断信息日志记录
    npu_imu_cmd.get_linkdown_optical_info(id)
    -- 光模块门限更新
    check_update_thresholds(orm_obj, id)
    -- 光模块告警状态值更新
    update_bias_current(orm_obj)
    -- 更新prbs测试信息
    update_prbs_test_info(orm_obj, id)
end

local function check_info_from_imu(orm_obj, id, parent)
    if orm_obj.is_first_get_info_from_imu then
        if orm_obj.is_npu_heartbeat_loss or fructl.get_power_status() ~= 'ON' or parent.PowerOn ~= 1 then
            orm_obj.Presence = 0
            orm_obj.FiberDirtyDetected = false
            orm_obj.FiberLoosenessDetected = false
        end
        orm_obj.is_first_get_info_from_imu = false
        return
    end

    if fructl.get_power_status() ~= 'ON' or parent.PowerOn ~= 1 then
        if log:getLevel() >= log.DEBUG then
            log:debug('[NPU] npu is power off')
        end
        reset_npu_om(orm_obj)
        return
    end
    if orm_obj.is_npu_heartbeat_loss then
        if log:getLevel() >= log.DEBUG then
            log:debug('[NPU] npu %s heartbeat loss, pause get npu op info', id)
        end
        return
    end

    local ok, err = pcall(function()
        return init_npu_optical_module_info(orm_obj, id)
    end)
    if not ok then
        if log:getLevel() >= log.DEBUG then
            log:debug('[NPU] init npu optical module info fail, err: %s', err)
        end
    end
end

local function get_info_from_imu_task(orm_obj, id)
    local parent = orm_obj.parent
    while true do
        -- 每15s更新从npu获取的光模块信息
        skynet.sleep(1500)
        check_info_from_imu(orm_obj, id, parent)
        -- 更新npu om告警事件
        orm_obj.om_event_handler:update_om_event()
    end
end

local function get_info_from_imu(orm_obj, id)
    log:notice('[NPU] get_info_from_imu task start, id = %s', id)
    skynet.fork_loop({count = 0}, function()
        get_info_from_imu_task(orm_obj, id)
    end)
end

function optical_module_npu.init(orm_obj, npu_id)
    register_npu_property_changed_callback(orm_obj)
    npu_register_listen_os_status(orm_obj)
    orm_obj.om_event_handler = c_om_event_handler.new(orm_obj, npu_om_event_debounce_config)
    orm_obj:next_tick(function ()
        get_info_from_imu(orm_obj, npu_id)
    end)
end

return optical_module_npu