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

-- Description: 升级公共实现
local skynet = require 'skynet'
local log = require 'mc.logging'
local fw_mgmt = require 'general_hardware.client'
local upgrade_object = require 'mcu.upgrade.upgrade_object'
local MCU_ENUMS = require 'mcu.enum.mcu_enums'
local defs = require 'mcu.upgrade.defs'
local fructl = require 'mcu.upgrade.fructl_handler'
local subcomp = require 'mcu.upgrade.sub_component'
local context = require 'mc.context'
local cmn = require 'common'
local factory = require 'factory'
local prog_stats = require 'progress_statistics'

local upgarde_flag_code = {['None'] = 0, ['Mcu'] = 1, ['Cpld'] = 2, ['Power'] = 3}

local upgrade_service_comm = {}

-- 根据子件类型匹配情况将升级对象插入list
local function insert_upgrade_list(bus, upgrade_list, type, mcu_obj, mcu_system_id)
    local mcu_powers_state = fructl.get_power_status(bus, mcu_system_id)
    if not next(mcu_obj.mcu.SubCompList) then
        local upgrade_obj = upgrade_object.new(mcu_obj, mcu_obj:get_system_id(), mcu_powers_state)
        upgrade_obj:set_ref_mcu(mcu_obj)
        upgrade_obj:set_ref_subcomp(nil)
        table.insert(upgrade_list, upgrade_obj)
    else
        for _, comp in pairs(mcu_obj.mcu.SubCompList) do
            log:notice("-------comp.Type=%s, type=%s", comp.Type, type)
            if comp.Type == type then
                -- 基于firmware创建升级对象并插入待升级列表
                local upgrade_obj = upgrade_object.new(mcu_obj, mcu_obj:get_system_id(), mcu_powers_state)
                upgrade_obj:set_ref_mcu(mcu_obj)
                upgrade_obj:set_ref_subcomp(comp)
                table.insert(upgrade_list, upgrade_obj)
            end
        end
    end

    return upgrade_list
end

-- 通过升级文件路径获取升级固件列表
function upgrade_service_comm.get_upgrade_list(bus, mcu_collection, cfg, firmware_type, system_id)
    local upgrade_list = {}
    if not cfg then
        return nil
    end

    local sub_type = MCU_ENUMS.SUB_COMPONENT_TYPE.T_MCU
    if defs.convert_component[firmware_type] ~= nil then
        sub_type = defs.convert_component[firmware_type].type
    end
    local mcu_uid, mcu_system_id
    for _, uid in pairs(cfg.uid_list) do
        for _, obj in pairs(mcu_collection) do
            mcu_uid = obj:get_uid()
            mcu_system_id = obj:get_system_id()
            log:notice("-------system_id = %s cfg_uid = %s, mcu.SystemId = %s mcu.UID = %s",
                system_id, uid, mcu_system_id, mcu_uid)
            if (system_id == defs.ALL_SYSTEM_ID or system_id == mcu_system_id) and uid == mcu_uid then
                log:notice("-------uid = %s, mcu.Id = %s", uid, obj.mcu.Id)
                upgrade_list = insert_upgrade_list(bus, upgrade_list, sub_type, obj, mcu_system_id)
            end
        end
    end
    log:notice("-------upgrade_list cnt = %s", #upgrade_list)
    return upgrade_list
end

local function is_list(cfg, firmware_type)
    if defs.convert_component[firmware_type] ~= nil then
        if not defs.convert_component[firmware_type].id and firmware_type == 'Mcu' and
        cfg.component_idex ~= MCU_ENUMS.COMPONENT_IDEX_SDI_5_0_MCU and
        cfg.component_idex ~= MCU_ENUMS.COMPONENT_IDEX_SDI_GD_MCU then
                return true
        end
        if cfg.component_id == defs.convert_component[firmware_type].id and
            cfg.component_idex == defs.convert_component[firmware_type].idex then
                return true
        end
    end
    return false
end

function upgrade_service_comm.is_component_concerned(cfgs, firmware_type)
    for _, cfg in pairs(cfgs) do
        if not cfg or not cfg.uid_list or next(cfg.uid_list) == nil  then
            cfg = nil
        end
        if cfg then
            if not is_list(cfg, firmware_type) then
                return defs.RET.ERR
            end
        end
    end
    if not next(cfgs) then
        return defs.RET.ERR
    end
    return defs.RET.OK
end

function upgrade_service_comm.upgrade_file_path_chown(cfg_path, hpm_path)
    local ok, rsp = fw_mgmt:PFileFileChown(context.new(), nil, cfg_path, 104, 104)  --设置配置文件属主为secbox
    if not ok then
        log:error('[UpgradeService] chown cfg file failed, error %s', rsp)
        return false
    end

    ok, rsp = fw_mgmt:PFileFileChown(context.new(), nil, hpm_path, 104, 104)  --设置升级文件属主为secbox
    if not ok then
        log:error('[UpgradeService] chown hpm file failed, error %s', rsp)
        return false
    end
    return true
end

-- 填充升级详细信息
function upgrade_service_comm.upgrade_fill_details(system_id, firmware_type, dir, upg_list)
    local upgrade_details = {
        firmware_type = firmware_type, -- 升级固件类型
        dir = dir, -- 升级固件目录
        system_id = system_id, -- 系统ID
        upgrade_list = {}, -- 升级固件映射列表
        upgrade_list_cnt = #upg_list, -- 升级固件数量
        upgraded_cnt = 0, -- 升级完成数量
        upgrade_status = 20, -- 升级状态
        upgrade_ret_code = defs.RET.OK,	-- 升级完成返回值
        ever_send_file_success = 0 -- 传输文件成功标识
    }
    upgrade_details.prog_stats_i = prog_stats.new(20, 85, upgrade_details.upgrade_list_cnt)

    for fw_index, fw_obj in pairs(upg_list) do
        table.insert(upgrade_details.upgrade_list, {
            fw_index = fw_index,
            fw_obj = fw_obj
        })
    end
    return upgrade_details
end

local function get_single_vrd_version(obj)
    local component = obj:get_ref_subcomp()
    local ok, version = pcall(function ()
        return component:ge_component_major_version()
    end)
    if ok and version and version ~= "" then
        return version
    end

    return '255'
end

-- 升级单个mcu
function upgrade_service_comm.upgrade_mcu(bus, obj, fw_info, upgrade_details, process_callback)
    log:notice("[McuUpgrade] system_id(%s) mcu/vrd/vdm upgrading", obj:get_system_id())
    local ret = obj:upgrade_process(upgrade_details, fw_info, process_callback)

    if ret == defs.RET.OK then
        log:notice("[McuUpgrade] upgrade successfully")
        local mcu = obj:get_ref_mcu()
        if not mcu then
            log:error('get ref mcu obj failed')
            return defs.RET.ERR
        end

        local sub_comp = obj:get_ref_subcomp()
        if sub_comp then
            -- 更新sub_comp_object版本号
            sub_comp:set_major_version(obj.major_version)
            sub_comp:set_minor_version(obj.minor_version)
            sub_comp:set_revision(obj.revision)
            -- MCU和VRD与其他子件命名规则不一致，需要做区分
            if sub_comp.Type ~= MCU_ENUMS.SUB_COMPONENT_TYPE.T_MCU and
                sub_comp.Type ~= MCU_ENUMS.SUB_COMPONENT_TYPE.T_VRD and
                sub_comp.Type ~= MCU_ENUMS.SUB_COMPONENT_TYPE.T_VDM then
                sub_comp:update_firmware_version(bus)
            end
        end

        if not sub_comp or sub_comp.Type == MCU_ENUMS.SUB_COMPONENT_TYPE.T_MCU then
            -- 更新mcu版本号
            mcu:set_mcu_major_version(obj.major_version)
            mcu:set_mcu_minor_version(obj.minor_version)
            mcu:set_mcu_revision(obj.revision)
            mcu:update_firmware_version(bus)
        end
    end
    return ret
end

-- 单个固件升级更新升级详细信息
local function upgrade_one_update_upg_details(ret, upg_details)
    -- 单个固件升级成功返回上一次升级结果，否则返回ERR
    upg_details.upgrade_ret_code = ret ~= defs.RET.OK and defs.RET.ERR or upg_details.upgrade_ret_code
    -- 单个固件升级完成，则固件升级数加1
    upg_details.upgraded_cnt = upg_details.upgraded_cnt + 1
end

function upgrade_service_comm.record_upgrade_log(firmware_type, ret, upg_obj, old_version)
    if firmware_type ~= 'Vrd' and firmware_type ~= 'Independent_Vrd' then
        if ret ~= defs.RET.OK then
            log:running(log.RLOG_ERROR, 'Upgrade %s %s Firmware failed',
                        upg_obj.device_name, string.upper(firmware_type))
            return
        end
        log:running(log.RLOG_INFO, 'Upgrade %s %s Firmware successfully',
                    upg_obj.device_name, string.upper(firmware_type))
        return
    end
    local chip_name = upg_obj.path or 'Unknow'
    log:maintenance(log.MLOG_INFO, log.FC__PUBLIC_OK, 'Upgrade VRD chip %s start', chip_name)
    if ret == defs.RET.OK then
        local new_version = get_single_vrd_version(upg_obj)
        log:maintenance(log.MLOG_INFO, log.FC__PUBLIC_OK,
            'Upgrade VRD chip %s from version %s to version %s successfully',
             chip_name, old_version, new_version)
        log:running(log.RLOG_INFO, 'Upgrade %s VRD chip %s Firmware (From: %s) successfully',
            upg_obj.device_name, chip_name, old_version)
    else
        log:maintenance(log.MLOG_INFO, log.FC__PUBLIC_OK, 'Upgrade VRD chip %s fail', chip_name)
        log:running(log.RLOG_ERROR, 'Upgrade %s VRD chip %s Firmware (From: %s) failed',
            upg_obj.device_name, chip_name, old_version)
    end
end

-- 获取当前要处理的升级对象
function upgrade_service_comm.get_cur_upgrade_obj_info(upgrade_details)
    local obj = table.remove(upgrade_details.upgrade_list, 1)
    if not obj then
        log:debug("dont upgrade obj info to upgrade")
        return
    end
    return obj, obj.fw_obj, obj.fw_index
end

function upgrade_service_comm.upgrade_one_fw(bus, upg_obj_info, upgrade_details)
    local upg_obj = upg_obj_info.fw_obj
    local upg_index = upg_obj_info.fw_index

    log:notice("firmware to be upgraded index:%s", upg_index)
    local ok, ret_code = pcall(function ()
        -- 获取历史版本号
        local old_version = get_single_vrd_version(upg_obj)

        -- 执行升级
        local _, ret = pcall(function ()
            return upgrade_service_comm.upgrade_mcu(bus, upg_obj, upg_obj_info, upgrade_details,
                function (percentage)
                    upgrade_details.prog_stats_i:set_sub_task_progress(upg_index, percentage * 100)
                end)
        end)

        -- 版本过低打印操作日志
        if ret == MCU_ENUMS.MCU_UPGRADE_STATUS.LOW_VERSION then
            log:operation(context:get_initiator(), log.FC__PUBLIC_OK, 'upgrade file version low')
        end

        -- 记录升级日志
        upgrade_service_comm.record_upgrade_log(upgrade_details.firmware_type, ret, upg_obj, old_version)
        return ret
    end)
    if not ok or ret_code ~= defs.RET.OK then
        log:error("[vrd upgrade task] firmware upgrade failed, index:%s, err:%s", upg_index, ret_code)
    end

    -- 更新升级详细信息
    upgrade_one_update_upg_details(ret_code, upgrade_details)

    -- 硬件总线解锁
    ok, ret_code = upg_obj:chip_unlock(require 'mc.context'.new())
    if not ok or ret_code ~= 0 then
        log:error("firmware(index:%s) set lock status to 0 failed, ret code:%s", upg_index, ret_code)
    end

    log:notice("firmware upgraded index:%s", upg_index)
end

-- 所有升级对象处理完成
function upgrade_service_comm.is_upgrade_obj_proc_finish(upgrade_details)
    if upgrade_details.upgraded_cnt == upgrade_details.upgrade_list_cnt then
        log:notice("[%s update] all firmware upgraded, cnt:%s",
            upgrade_details.firmware_type, upgrade_details.upgraded_cnt)
        return true
    end
    return false
end

-- 更新串行升级任务进度
function upgrade_service_comm.update_serial_upgrade_progress(upgrade_details, parameters)
    -- 后台创建一个进度更新任务
    skynet.fork(function ()
        log:notice("start to update mcu/vrd upgrade status")
        while true do
            skynet.sleep(100)
            fw_mgmt:UpdateServiceUpdateServiceUpdateUpgradeStatus(context.new(), upgrade_details.system_id,
                upgrade_details.firmware_type, 0, upgrade_details.prog_stats_i:get_current_progress(), 'Running',
                parameters)
            -- 所有升级对象处理完成
            if upgrade_service_comm.is_upgrade_obj_proc_finish(upgrade_details) then
                break
            end
        end
    end)
end

function upgrade_service_comm.update_vrd_component_version(bus, mcu_collection, system_id)
    for _, obj in pairs(mcu_collection) do
        if system_id == defs.ALL_SYSTEM_ID or system_id == obj:get_system_id() then
            subcomp.update_vrd_firmware_version(bus, obj)
        end
    end
end

function upgrade_service_comm.record_upgrade_maint_log(firmware_type, upgrade_ret_code, cur_version,
    before_upgrade_version)
    if before_upgrade_version == nil or before_upgrade_version == '' then
        if upgrade_ret_code == defs.RET.OK then
            log:maintenance(log.MLOG_INFO, log.FC__PUBLIC_OK, 'Upgrade %s to version %s successfully',
                firmware_type, cur_version)
            return
        end
        log:maintenance(log.MLOG_INFO, log.FC__PUBLIC_OK, 'Upgrade %s failed', firmware_type)
        return
    end
    if upgrade_ret_code == defs.RET.OK then
        log:maintenance(log.MLOG_INFO, log.FC__PUBLIC_OK, 'Upgrade %s from version %s to version %s successfully',
            firmware_type, before_upgrade_version, cur_version)
        return
    end
    log:maintenance(log.MLOG_INFO, log.FC__PUBLIC_OK, 'Upgrade %s from version %s failed',
        firmware_type, before_upgrade_version)
end

local function set_upgarde_flag(firmware_type, upgrade_list)
    local unit_manager = factory.get_obj("unit_manager")
    for _, obj in pairs(upgrade_list) do
        local position = obj:get_ref_mcu():get_position()
        if position then
            local board_obj = unit_manager:get_board_obj_by_position(position)
            if board_obj then
                board_obj:set_upgarde_flag(upgarde_flag_code[firmware_type])
                log:notice("set upgarde flag to %s", firmware_type)
            end
        end
    end
end

function upgrade_service_comm.upgrade_finish(upgrade_list)
    -- 设置升级完成标志位
    -- MCU风扇在升级生效后，需要10s稳定转速，bmc在升级完成15s内认为仍然处于升级生效状态中
    cmn.skynet.sleep(1500)
    set_upgarde_flag("None", upgrade_list)
end

function upgrade_service_comm.upgrade_prepare(firmware_type, upgrade_list)
    set_upgarde_flag(firmware_type, upgrade_list)
end

function upgrade_service_comm.update_active_status(firmware_id, firmware_type, status)
    log:notice('[%s] update active status', firmware_type)
    local param = {}
    param[#param+1] = {Key = 'FirmwareId', Value = firmware_id}
    param[#param+1] = {Key = 'FirmwareType', Value = firmware_type}
    param[#param+1] = {Key = 'ActiveStatus', Value = status}
    fw_mgmt:FirmwareActiveFirmwareActiveUpdateActiveStatus(context.new(), param)
end

return upgrade_service_comm