-- 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 class = require 'mc.class'
local singleton = require 'mc.singleton'
local fw_mgmt = require 'power_mgmt.client'
local ctx = require 'mc.context'
local psu_service = require 'psu_service'
local c_psu_object = require 'device.psu'
local c_power_configuration = require 'device.power_configuration'
local fw_def = require 'macros.fw_def'
local c_tasks = require 'mc.orm.tasks'
local log = require 'mc.logging'
local signal = require 'signal'
local client = require 'power_mgmt.client'
local context = require 'mc.context'
local utils = require 'power_mgmt_utils'

local RET_ERR<const> = -1

local UPGRADE_PERCENT_INIT<const> = 8
local UPGRADE_PERCENT_AFTER_LOAD <const> = 98 -- 电源升级完成的总进度设置为98%,防止在100%时长时间等待
local COMP_POWER_TYPE <const> = 'Power'

local COMPONENT_IDEX<const> = {
    ['pmbus'] = 0,
    ['canbus'] = 2
}

-- upgrade_process 电源固件升级进度回调函数
local upgrade_process = {
    step_progress = 0,
    cur_progress = 0,
    start_process = 0,
    init = function(self, init_progress)
        self.cur_progress = init_progress
    end,
    get_comp_percent = function(self)
        return self.cur_progress
    end,
    set_comp_percent = function(self, progress)
        progress = math.floor(progress)
        if progress > 100 then
            progress = 100
        end
        if self.cur_progress == progress then
            return
        end
        self.cur_progress = progress
        fw_mgmt:UpdateServiceUpdateServiceUpdateUpgradeStatus(ctx.new(), 1, 'Power', 0, progress, 'Upgrading')
    end,
    set_step_progress = function(self, progress)
        -- 升级进度不超过100
        if progress > 100 then
            progress = 100
        end
        self.start_process = self.step_progress
        self.step_progress = progress
    end,
    get_step_progress = function(self)
        return self.step_progress
    end
}

local function extract_package(file_path)
    local path_split = #file_path - file_path:reverse():find('/') + 1 -- 执行失败，认为是升级失败，在上层pcall处返回
    local upgrade_path = file_path:sub(1, path_split)
    local upgrade_ps_packet = file_path:sub(path_split + 1, -1)
    local ok = utils.secure_tar_unzip(
        upgrade_path .. upgrade_ps_packet,
        upgrade_path,
        0x6400000, -- 最大解压限制100M
        1024
    ) -- 解压tar包
    if not ok then
        log:error('Secure unzip failed.')
        return nil
    end
    return upgrade_path
end

-- 获取是否支持单电源升级
local function is_singal_psu_support()
    local power_configuration = c_power_configuration.collection:find({})
    if not power_configuration then
        return false
    end
    return power_configuration:get_single_ps_support_flag() == 1
end

local function is_support_upgrade(psu_obj)
    -- 单电源环境支持升级 或者 整机功耗支持升级
    return is_singal_psu_support() or psu_service.get_instance():check_ps_num_exclude_ps({[psu_obj.ps_id] = 1})
end

-- 依次升级每个psu
local function upgrade_each_psu(file_path, psu_objs, pro_percent)
    local upgrade_result = fw_def.ERROR_CODE.CommonFinish
    local upgrade_path = extract_package(file_path)
    if not upgrade_path then
        upgrade_result = fw_def.ERROR_CODE.InvalidFirmwarePackage
        return upgrade_result
    end
    for _, psu_obj in ipairs(psu_objs) do
        upgrade_process:set_step_progress(upgrade_process:get_step_progress() + pro_percent)
        log:notice('[power_mgmt] upgrade ps%d', psu_obj.ps_id)

        if not is_support_upgrade(psu_obj) then
            log:error('[power_mgmt] power supply is less than required and not support single power supply')
            upgrade_result = fw_def.ERROR_CODE.InsufficientPowerSupply
            break
        end
        local ret = psu_obj:power_upgrade(upgrade_path, upgrade_process)
        if ret ~= fw_def.SUPPLY_0 then
            upgrade_result = fw_def.ERROR_CODE.FirmwareUpgradeError
            if ret ~= fw_def.SUPPLY_3 and ret ~= fw_def.SUPPLY_6 then
                log:error('[power_mgmt] upgrade ps%d failed, ret = %s', psu_obj.ps_id, ret)
                break
            end
            log:warn('[power_mgmt] ps%d upgrading unsupported, ret = %s', psu_obj.ps_id, ret)
        else
            log:notice('[power_mgmt] ps%d upgrade successfully', psu_obj.ps_id)
        end
    end
    return upgrade_result
end

local function upgrade_power_process(system_id, firmware_type, file_path, component_idex)
    -- 计算电源升级进度
    local psu_objs = c_psu_object.collection:fetch(function (object)
        -- 获取协议和升级包匹配的电源
        return COMPONENT_IDEX[object.Protocol] == component_idex
    end)

    local count = #psu_objs
    log:notice('upgrade_process psu count %d', count)
    local upgrade_result
    if count == 0 then
        log:error('no psu need upgrade')
        upgrade_result = fw_def.ERROR_CODE.FirmwareUpgradeError
        return upgrade_result
    end
    local pro_percent = (UPGRADE_PERCENT_AFTER_LOAD - upgrade_process:get_comp_percent()) // count
    -- 升级过程停止数据监测
    psu_service.get_instance().upgrade_flag = 1
    c_psu_object.collection:fold(function (_, obj)
        -- 升级切换到主用，防止掉电；升级结束，能效会切主备
        obj:set_work_mode(0)
    end)
    for _, psu_obj in pairs(psu_objs) do
        psu_obj.IsUpgrading = true
    end

    c_tasks.sleep_ms(3000) -- 等待任务队列清空,电压拉偏完成

    -- 优先升级不健康的电源，保证供电
    table.sort(
        psu_objs,
        function (obj1, obj2)
            return not obj1:is_healthy() and obj2:is_healthy()
        end
    )
    upgrade_result = upgrade_each_psu(file_path, psu_objs, pro_percent)
    psu_service.get_instance().upgrade_flag = 0
    for _, psu_obj in pairs(psu_objs) do
        psu_obj.IsUpgrading = false
    end
    return upgrade_result
end

local function prepare_upgrade(system_id, firmware_type, file_path)
    if firmware_type ~= COMP_POWER_TYPE then
        return
    end
    local ok, rsp = client:PFileFileChown(context.new(), nil, file_path, 104, 104)  --设置配置文件属主为secbox
    if not ok then
        log:error('[power_mgmt] chown cfg file failed, error %s', rsp)
        client:UpdateServiceUpdateServicePrepareReply(context.new(), system_id, firmware_type, '', RET_ERR)
        return
    end

    -- 检查ex_id
    utils.cfgs:get_cfgs(file_path)
    if utils.cfgs:check_idex(0x7D) then
        return
    end
    log:notice('power upgrade prepare')
    -- prepare阶段升级进度为8%
    upgrade_process:init(UPGRADE_PERCENT_INIT)
    fw_mgmt:UpdateServiceUpdateServicePrepareReply(ctx.new(), system_id, firmware_type, '', 0)
end

local function process_upgrade(db, system_id, firmware_type, file_path)
    if firmware_type ~= COMP_POWER_TYPE then
        return
    end
    if utils.cfgs:check_idex(0x7D) then
        return
    end
    local component_idex = utils.cfgs:get_component_idex()
    local ok, rsp = client:PFileFileChown(context.new(), nil, file_path, 104, 104)  --设置配置文件属主为secbox
    if not ok then
        log:error('[power_mgmt] chown file failed, error %s', rsp)
        client:PUpdateServiceUpdateServiceProcessReply(context.new(), system_id, firmware_type, RET_ERR)
        return
    end
    log:notice('power upgrade process')
    local ret = upgrade_power_process(system_id, firmware_type, file_path, component_idex)
    fw_mgmt:UpdateServiceUpdateServiceProcessReply(ctx.new(), system_id, firmware_type, ret)
end

local function finish_upgrade(system_id, firmware_type)
    if firmware_type ~= COMP_POWER_TYPE then
        return
    end
    if utils.cfgs:check_idex(0x7D) then
        return
    end
    log:notice('power upgrade finish')
    -- power升级当前没有需要特殊处理的Finish动作，直接返回
    fw_mgmt:UpdateServiceUpdateServiceFinishReply(ctx.new(), system_id, firmware_type, 0)
end

local power_upgrade = class()
power_upgrade.prepare_upgrade = prepare_upgrade
power_upgrade.process_upgrade = process_upgrade
power_upgrade.finish_upgrade = finish_upgrade
power_upgrade.upgrade_power_process = upgrade_power_process

function power_upgrade:ctor()
    signal.register_callback('upgrade_prepare_callback', nil, prepare_upgrade)
    signal.register_callback('upgrade_process_callback', nil, process_upgrade)
    signal.register_callback('upgrade_finish_callback', nil, finish_upgrade)
end

return singleton(power_upgrade)
