-- 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.
--         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 skynet = require 'skynet'
local smc = require 'protocol.smc'
local utils = require 'mc.utils'
local log = require 'mc.logging'
local add_event = require 'add_event'
local factory = require 'factory'
local event = require 'infrastructure.event'
local signal_monitor = require 'signal_monitor'
local common_interface = require 'common_interface'
local retimer_utils = require 'retimer.agent.retimer_agent_jbog_retimer_utils'

local agent = class()

local RISER_CARD_SUBJECT_TYPE <const> = 0x0F
local EVENT_I2C_ACCESS_FAILED_KEY_ID <const> = 'BMC.BMCAccessI2CDeviceFailure'
local EVENT_I2C_ACCESS_FAILED_NAME <const> = 'BMCAccessI2CDeviceFailure'
local SET_RETRY_TIMES <const> = 60
local DEFAULT_MANAGER_ID = 1
local DEFAULT_SYSTEM_ID = 1
local SMC_REQUEST_SUCCESS_TIMES <const> = 3

local BOX_I2C_ACCESS_STATE = {
    NORMAL = 0,
    NOT_RESPOND = 1,
    I2C_BUS_ERROR = 2,
    UNKNOWN_ERROR = 4
}

local BOX_I2C_ACCESS_STATE_DESC = {
    [BOX_I2C_ACCESS_STATE.NORMAL] = 'normal',
    [BOX_I2C_ACCESS_STATE.NOT_RESPOND] = 'box not respond',
    [BOX_I2C_ACCESS_STATE.I2C_BUS_ERROR] = 'I2C bus error',
    [BOX_I2C_ACCESS_STATE.UNKNOWN_ERROR] = 'unknown error'
}

local BOX_SLOT_ID_MAPPING = {
    [1] = 0,
    [2] = 1,
}

local function get_retimer_prop(chip, param, obj_name, prop_name)
    local ok, msg = pcall(function()
        return smc.chip_blkread(chip, param.cmd, param.length)
    end)

    if not ok then
        return false, msg
    end

    return true, msg
end

local function parse_version(msg)
    return string.format('%02d.%02d.%02d.%02d', string.byte(msg, 1), string.byte(msg, 2), string.byte(msg, 3),
        string.byte(msg, 4))
end

local parameters <const> = {
    -- 获取版本号，func=0x21, command=0xA, ms=0, rw=1, param=0, length=4
    -- 版本号格式: aa.bb.cc.dd
    ['FirmwareVersion'] = { args = { cmd = 0x84002900, length = 4 }, callback = parse_version },
}

local function update_retimer_info(obj)
    for prop_name, params in pairs(parameters) do
        local ok, msg = get_retimer_prop(obj.retimer.retimer_obj.RefChip, params.args, obj.name, prop_name)
        if ok and params.callback then
            obj.retimer.retimer_obj[prop_name] = params.callback(msg)
            log:debug('get retimer: %s, %s: %s', obj.name, prop_name, obj.retimer.retimer_obj[prop_name])
        else
            break
        end
    end
end

local function retimer_monitor_task(obj)
    pcall(function(retimer)
        -- 更新Retimer属性信息
        update_retimer_info(retimer)
        -- 如果retimer版本有更新，则刷新资源树属性
        retimer.retimer:update_firmware_version()
    end, obj)
end

local function general_box_i2c_access_failure_event(obj, assert)
    -- 查询box ipv4地址，func=0x21, command=0x3, ms=0, rw=1, param=0, length=4
    local ok, msg = get_retimer_prop(obj.retimer.retimer_obj.RefChip, { cmd = 0x84000D00, length = 4 },
        obj.name, 'boxIpv4Address')

    local ipv4_address = ''
    if ok then
        ipv4_address = string.format('%d.%d.%d.%d', string.unpack(string.rep('B', 4), msg))
    end

    -- 告警描述参数: %1: JBOG-JbogIP, %2: CPLD
    local param = { 'JBOG-' .. ipv4_address, 'CPLD' }
    return add_event.generate_error_code(assert, EVENT_I2C_ACCESS_FAILED_KEY_ID, obj.device_name, RISER_CARD_SUBJECT_TYPE,
        param, nil, '1,2')
end

local function box_i2c_access_monitor_task(obj, times)
    -- 查询box链路告警，func=0x21, command=0xB, ms=0, rw=1, param=0, length=1
    local ok, msg = get_retimer_prop(obj.retimer.retimer_obj.RefChip, { cmd = 0x84002D00, length = 1 },
        obj.name, 'BoxLinkStatus')
    if not ok then
        return times
    end

    local state = string.byte(msg, 1)
    if not BOX_I2C_ACCESS_STATE_DESC[state] then
        log:debug("Invaild box i2c access state code: %s", state)
        return times
    end

    -- 更新当前是否有box i2c链路异常告警
    local alarm_exist
    ok, alarm_exist = obj:update_box_i2c_access_failure_alarm()
    if not ok then
        return times
    end

    -- 如果读取的结果与当前告警状态一致，就清空告警防抖
    if (state == BOX_I2C_ACCESS_STATE.NORMAL and not alarm_exist) or
        (state ~= BOX_I2C_ACCESS_STATE.NORMAL and alarm_exist) then
        return 0
    end

    -- 如果读取的结果与当前告警状态不一致，就告警防抖，连续5次
    if ((state == BOX_I2C_ACCESS_STATE.NORMAL and alarm_exist) or
            (state ~= BOX_I2C_ACCESS_STATE.NORMAL and not alarm_exist)) and times ~= 4 then
        return times + 1
    end

    local ret
    if state == BOX_I2C_ACCESS_STATE.NORMAL and alarm_exist then
        ret = general_box_i2c_access_failure_event(obj, 'false')
    elseif state ~= BOX_I2C_ACCESS_STATE.NORMAL and not alarm_exist then
        log:notice(
            "add box I2C access failure event, name: %s, key: %s, error code: %d, desciption: %s, ",
            obj.name, EVENT_I2C_ACCESS_FAILED_KEY_ID, state, BOX_I2C_ACCESS_STATE_DESC[state])
        ret = general_box_i2c_access_failure_event(obj, 'true')
    end
    if not ret then
        return times
    end
    return 0
end

local FirmwareLoadStatus <const> = {
    [0] = 'the MCU is not accessible',
    [1] = 'all loaded',
    [2] = 'all unloaded',
    [3] = 'only retimer0 loaded',
    [4] = 'only retimer1 loaded'
}
function agent:get_retimer_firmware_load_status()
    -- 查询retimer固件加载状态，func=0x21, command=0xB, ms=0, rw=1, param=0, length=1
    local retry_times = 3
    local ok, msg
    for i = 1, retry_times do
        ok, msg = get_retimer_prop(self.retimer.retimer_obj.RefChip, { cmd = 0x84004500, length = 1 },
            self.name, 'RetimerFirmwareLoadStatus')
        if ok then
            return msg:byte()
        end
        skynet.sleep(50)
    end
    log:error('get firmware load status failed %s', msg)
end

function agent:get_device_name()
    if self.device_name then
        return true
    end

    for i = 1, 10 do -- 刚启动时可能获取不成功，需要重试
        skynet.sleep(100)
        local unit_manager = factory.get_obj("unit_manager")
        -- 关联的component对象为retimer所在的riser卡
        local riser_obj = unit_manager:get_board_obj_by_position(self.retimer.position)
        if riser_obj then
            self.device_name = riser_obj.mds_obj.DeviceName
            return true
        end
    end
    return false
end

function agent:update_box_i2c_access_failure_alarm()
    if not self:get_device_name() then
        return false, nil
    end

    local ok, alarm_list = event.get_latest_alarm_list(self.bus)
    if not ok then
        log:notice("get latest alarm list failed, error: %s", alarm_list)
        return false, nil
    end

    -- 根据 ComponentName、EventName 匹配告警
    for _, alarm in pairs(alarm_list) do
        if alarm.ComponentName and alarm.ComponentName == self.device_name and
            alarm.EventName == EVENT_I2C_ACCESS_FAILED_NAME then
            log:debug('alarm.ComponentName not match') -- 不是当前对象的告警
            return true, true
        end
    end
    return true, false
end

function agent:set_bmc_ipv4_to_box(manager_id, ip_addr)
    if manager_id ~= DEFAULT_MANAGER_ID then
        log:notice('set bmc ipv4 addr to box failed, manager_id: %s not match', manager_id)
        return
    end
    local retry_times = 1
    local success_times = 0
    local ok
    while retry_times < SET_RETRY_TIMES do
        ok = self.retimer_utils:set_box_bmc_ipv4(ip_addr)
        if ok then
            success_times = success_times + 1
        end
        if success_times >= SMC_REQUEST_SUCCESS_TIMES then
            log:notice('set bmc ipv4 addr %s to box success', ip_addr)
            return true
        end
        retry_times = retry_times + 1
        skynet.sleep(1000)
    end
    log:error('set bmc ipv4 addr to box failed after %s times retry', SET_RETRY_TIMES)
    return false
end

function agent:set_bmc_ipv6_to_box(manager_id, ip_addr)
    if manager_id ~= DEFAULT_MANAGER_ID then
        log:notice('set bmc ipv6 addr to box failed, manager_id: %s not match', manager_id)
        return
    end
    local retry_times = 1
    local success_times = 0
    local ok
    while retry_times < SET_RETRY_TIMES do
        ok = self.retimer_utils:set_box_bmc_ipv6(ip_addr)
        if ok then
            success_times = success_times + 1
        end
        if success_times >= SMC_REQUEST_SUCCESS_TIMES then
            log:notice('set bmc ipv6 addr %s to box success', ip_addr)
            return true
        end
        retry_times = retry_times + 1
        skynet.sleep(1000)
    end
    log:error('set bmc ipv6 addr to box failed after %s times retry', SET_RETRY_TIMES)
    return false
end

function agent:set_bmc_mac_to_box(manager_id, mac_addr)
    if manager_id ~= DEFAULT_MANAGER_ID then
        log:notice('set bmc mac to box failed, manager_id: %s not match', manager_id)
        return
    end
    local retry_times = 1
    local success_times = 0
    local ok
    while retry_times < SET_RETRY_TIMES do
        ok = self.retimer_utils:set_box_bmc_mac(mac_addr)
        if ok then
            success_times = success_times + 1
        end
        if success_times >= SMC_REQUEST_SUCCESS_TIMES then
            log:notice('set bmc mac %s to box success', mac_addr)
            return true
        end
        retry_times = retry_times + 1
        skynet.sleep(1000)
    end
    log:error('set bmc mac to box failed after %s times retry', SET_RETRY_TIMES)
    return false
end

function agent:set_bmc_sn_to_box(system_id, sn)
    if system_id ~= DEFAULT_SYSTEM_ID then
        log:notice('set bmc sn to box failed, system_id: %s not match', system_id)
        return
    end
    local retry_times = 1
    local success_times = 0
    local ok
    while retry_times < SET_RETRY_TIMES do
        ok = self.retimer_utils:set_box_bmc_sn(sn)
        if ok then
            success_times = success_times + 1
        end
        if success_times >= SMC_REQUEST_SUCCESS_TIMES then
            log:notice('set bmc sn %s to box success', sn)
            return true
        end
        retry_times = retry_times + 1
        skynet.sleep(1000)
    end
    log:error('set bmc sn to box failed after %s times retry', SET_RETRY_TIMES)
end

function agent:set_bmc_id_to_box(id)
    local retry_times = 1
    local success_times = 0
    local ok
    while retry_times < SET_RETRY_TIMES do
        ok = self.retimer_utils:set_box_bmc_id(id)
        if ok then
            success_times = success_times + 1
        end
        if success_times >= SMC_REQUEST_SUCCESS_TIMES then
            log:notice('set bmc id %s to box success', id)
            return true
        end
        retry_times = retry_times + 1
        skynet.sleep(1000)
    end
    log:error('set bmc id to box failed after %s times retry', SET_RETRY_TIMES)
    return false
end

function agent:register_jbog_callback_to_common_interface()
    local instance = common_interface.get_instance()
    instance:register_callback(self.set_bmc_ipv4_to_box, self, 'ipv4', 'jbog_retimer_ipv4')
    instance:register_callback(self.set_bmc_ipv6_to_box, self, 'ipv6', 'jbog_retimer_ipv6')
    instance:register_callback(self.set_bmc_mac_to_box, self, 'mac', 'jbog_retimer_mac')
    instance:register_callback(self.set_bmc_sn_to_box, self, 'sn', 'jbog_retimer_sn')
end

function agent:start_monitor_bmc_properties()
    -- 当下仅通过BOX CDFP Cable Id 为1的retimer卡的mcu下发bmc属性
    if self.box_id == 1 then
        log:notice('start monitor bmc properties failed, box_id: %s', self.box_id)
        return
    end
    self:register_jbog_callback_to_common_interface()
    common_interface.get_instance():register_bmc_listening()
end

function agent:start_set_box_id_to_box()
    local unit_manager = factory.get_obj("unit_manager")
    -- 关联的component对象为retimer所在的riser卡
    local riser_obj = unit_manager:get_board_obj_by_position(self.retimer.position)
    self.box_id = riser_obj and riser_obj.mds_obj and riser_obj.mds_obj.Slot and
        BOX_SLOT_ID_MAPPING[riser_obj.mds_obj.Slot] or 0
    log:notice('start set box id to box, box_id: %s, slot: %s', self.box_id,
        riser_obj and riser_obj.mds_obj and riser_obj.mds_obj.Slot or -1)
    self:set_bmc_id_to_box(self.box_id)
end

function agent:start()
    if not self.monitor_task then
        self.monitor_task = skynet.fork_loop({ count = 0 }, function(obj)
            while true do
                -- 执行监控任务
                retimer_monitor_task(obj)

                -- 30秒执行一次
                skynet.sleep(3000)
            end
        end, self)
    end

    local times = 0
    if not self.box_i2c_access_moitor_task then
        self.box_i2c_access_moitor_task = skynet.fork_loop({ count = 0 }, function(obj)
            while true do
                -- 执行告警监控
                times = box_i2c_access_monitor_task(obj, times)

                -- 2秒执行一次
                skynet.sleep(200)
            end
        end, self)
    end

    skynet.fork(function()
        self:start_set_box_id_to_box()
        self:start_monitor_bmc_properties()
    end)

    signal_monitor.get_instance().on_os_state_changed:on(function(power_on)
        if not power_on then
            return
        end
        for _ = 1, 10 do
            skynet.sleep(500) -- 5秒执行一次
            local msg = self:get_retimer_firmware_load_status()
            if msg == 0x01 then
                log:notice('[%s] load finished, message [0x%x]: %s', self.name, msg, FirmwareLoadStatus[msg])
                return
            end
            log:warn('[%s] load failed, message [0x%x]: %s', self.name, msg, FirmwareLoadStatus[msg])
        end
    end)
end

function agent:stop()
    if self.monitor_task then
        skynet.killthread(self.monitor_task)
        self.monitor_task = nil
    end

    if self.box_i2c_access_moitor_task then
        skynet.killthread(self.box_i2c_access_moitor_task)
        self.box_i2c_access_moitor_task = nil
    end
end

function agent:ctor(retimer, bus)
    self.name = retimer.name
    self.retimer = retimer
    self.bus = bus
    self.log_list = {}
    self.common_interface = common_interface.new(self.bus)
    self.retimer_utils = retimer_utils.new(self.retimer.retimer_obj.RefChip)
    self.box_id = 0
end

agent.box_i2c_access_monitor_task = box_i2c_access_monitor_task

return agent
