-- 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 skynet = require 'skynet'
local log = require 'mc.logging'
local context = require 'mc.context'
local utils = require 'mc.utils'
local defs = require 'unit_manager.class.logic_fw.comm_defs'
local fructl = require 'mcu.upgrade.fructl_handler'
local cpld_space_test = require 'cpld_space_test'
local cpld_space_test_iic = require 'cpld_space_test_iic'
local gen_hw_bus = require 'general_hardware_bus'
local fw_mgmt_client = require 'general_hardware.client'

local cpld_test_upgrade = {}
local g_upgrade_ret = 0
local g_test_upgrade = false

local LOCK_FOREVER = 0xFFFF --永久锁

local function set_powerctrl_type(bus, operate_type, reason)
    local ok, ret
    if fructl.is_multihost_type(bus) then
        -- cpld全检过程中multihost机型需要对整机下电
        ok, ret = fructl.set_chassis_power_state(bus, 1, operate_type, reason)
    else
        ok, ret = fructl.set_power_state(bus, 1, operate_type, reason)
    end

    if not ok then
        log:error('set power state %s failed, ret = %s', operate_type, ret)
    end
end

local function set_power_state_for_test(bus, operate_type, reason)
    set_powerctrl_type(bus, operate_type, reason)
    local power_state
    for _ = 1, 30 do
        if fructl.is_multihost_type(bus) then
            power_state = fructl.get_chassis_power_status(bus, 1)
        else
            power_state = fructl.get_power_status(bus, 1)
        end
        if (operate_type == 'ForceOff' and power_state == 'OFF') or (operate_type == 'ON' and power_state == 'ON') then
            return defs.RET.OK
        end
        skynet.sleep(500)
    end
    log:error('powere status not change as as expected')
    return defs.RET.ERR
end

-- 设置上电锁状态
local function set_power_lock_state(bus, flag)
    if fructl.is_multihost_type(bus) then
        fructl.set_chassis_power_on_lock(bus, 1, flag, LOCK_FOREVER, 'test_cpld', 'general_hardware')
    else
        fructl.set_power_on_lock(bus, 1, flag, LOCK_FOREVER, 'test_cpld', 'general_hardware')
    end
end

local function set_test_upgrade()
    local bus = gen_hw_bus.get_instance().bus
    -- 强制下电
    local ret = set_power_state_for_test(bus, 'ForceOff', 'Unknown')
    -- 下电成功需要加上电锁
    if ret == defs.RET.OK then
        set_power_lock_state(bus, true)
    end
    if g_test_upgrade == false then
        cpld_space_test.app_db:update(cpld_space_test.app_db.CpldSpaceTest):value({ CheckResult = 255 }):exec()
        g_test_upgrade = true
    end
    return ret
end

local function check_cfg_list(signal, cfg_path)
    signal.upg_cfg_list = signal.get_cfg_list(cfg_path)
    if not next(signal.upg_cfg_list) then
        return defs.RET.ERR
    end

    return defs.RET.OK
end

-- 根据多固件支持情况和参数确定固件索引
local function get_firmware_index(signal, parameters)
    if parameters.FirmwareIndex then
        return tonumber(string.match(parameters.FirmwareIndex, 'Firmware(%d)')) or 1
    end
    return 1
end

function cpld_test_upgrade.prepare_upgrade(signal, system_id, firmware_type, cfg_path, parameters)
    local version = ""
    local ret

    -- 检测是否FPAG或者cpld正在升级
    if defs.UPGRADING_FPGA == 1 or defs.UPGRADING_CPLD == 1 then
        log:error('[cpld] fpga or cpld is Upgrading, please do cpld test upgrade after they have completed')
        fw_mgmt_client:UpdateServiceUpdateServicePrepareReply(context.new(), system_id, firmware_type,
            version, defs.RET.IN_UPGRADING, parameters)
        ret = defs.RET.ERR
        goto exit
    end

    -- 完成框架下发的update.cfg解析
    ret = check_cfg_list(signal, cfg_path)
    if ret == defs.RET.ERR then
        goto exit
    end

    --存在TEstcase 配置， 则进入CPLD自检升级流程
    ret = set_test_upgrade()
    if ret == defs.RET.ERR then
        goto exit
    end

    --  设置正在升级CPLD
    defs.UPGRADING_CPLD = 1
    ::exit::

    parameters.MultipleSupported = 'Support' -- 支持多firmware标志
    fw_mgmt_client:UpdateServiceUpdateServicePrepareReply(context.new(), system_id, firmware_type, version, ret,
        parameters)

    return ret
end

local function extract_upgrade_file(file_path)
    local ok, release_path, fw_name = pcall(function()
        local s, e = string.find(file_path, '/.+/')
        local release_path = string.sub(file_path, s, e)                    -- 提取路径/dev/shm/upgrade/xxx
        local fw_name = string.sub(file_path, e + 1, string.len(file_path)) -- 提取文件名Firmware1
        return release_path, fw_name
    end)
    if ok then
        return release_path, fw_name
    end
    log:error('extract file path failed')
end

local function get_applicable_component_fw(system_id, cfg, component_fw_list)
    local update_obj_list = {}
    if not cfg.uid then
        log:error('unsupport firmware: uid not exist')
        return update_obj_list
    end
    -- 比较所有组件的UId是否跟该升级包支持的UId匹配
    for _, fw in ipairs(component_fw_list) do
        if (system_id == defs.ALL_SYSTEM_ID or system_id == fw.system_id) and
            cfg:check_fw_uid_exist(fw) then
            log:notice('cpld fw matched, system_id = %s, fw.uid = %s, fw.csr.Name = %s',
                system_id, fw.uid, fw.csr.Name)
            table.insert(update_obj_list, fw)
        end
    end
    return update_obj_list
end

local function upgrade_component_cpld(system_id, fw_list, cfg_list, file_path)
    -- 输入路径/dev/shm/upgrade/Firmware
    local release_path, fw_name = extract_upgrade_file(file_path) --加载升级文件
    for _, cfg in ipairs(cfg_list) do
        if fw_name and cfg.name ~= fw_name then
            goto continue
        end
        -- 找到与文件名匹配cfg的节点,通过Uid找到适用的组件对象
        local component_fw_list = get_applicable_component_fw(system_id, cfg, fw_list)
        if #component_fw_list == 0 then
            log:error('no match component: firmware uid = %s', cfg.uid)
            return defs.RET.OK
        end
        -- uid适用情况下，解压在与Firmware同级文件夹, 最大解压限制100M
        utils.secure_tar_unzip(file_path, release_path, 0x6400000, 1024)
        log:notice('[cpld]get cpld packages successful')

        for _, fw in ipairs(component_fw_list) do
            -- 启动  cpld 全资源检测
            fw:switch_to_firmware_route()
            fw.test_chip_nums = cfg.chip_num
            cpld_space_test.save_cplds_test_result(fw, cfg.chip_num, cpld_space_test.STATUS.TESTING)
            cpld_space_test.load_test_cpld_with_lock(fw, cfg, release_path, fw_list)
            fw:switch_to_default_route()
        end
        -- 目前框架一次只发一个Firmware，使用完就可以退出了
        break
        ::continue::
    end
    return defs.RET.OK
end

local function iic_upgrade_cpld(system_id, fw_list, cfg_list, file_path, firmware_type, parameters)
    -- 输入路径/dev/shm/upgrade/Firmware
    local release_path, fw_name = extract_upgrade_file(file_path) --加载升级文件
    for _, cfg in ipairs(cfg_list) do
        -- 找到与文件名匹配cfg的节点
        if cfg.name ~= fw_name then
            goto continue
        end
        -- 通过Uid找到适用的组件对象
        local component_fw_list = get_applicable_component_fw(system_id, cfg, fw_list)
        if #component_fw_list == 0 then
            log:error('no match component: firmware uid = %s', cfg.uid)
            return defs.RET.OK
        end
        -- uid适用情况下，解压在与Firmware同级文件夹, 最大解压限制100M
        utils.secure_tar_unzip(file_path, release_path, 0x6400000, 1024)
        log:notice('[cpld]get cpld packages successful')
        for _, fw in ipairs(component_fw_list) do
            fw.test_chip_nums = cfg.chip_num
            cpld_space_test.save_cplds_test_result(fw, cfg.chip_num, cpld_space_test.STATUS.TESTING)
            cpld_space_test_iic.load_test_cpld_with_lock_iic(fw, cfg, release_path, system_id, firmware_type, parameters)
        end
        -- 目前框架一次只发一个Firmware，使用完就可以退出了
        break
        ::continue::
    end
    return defs.RET.OK
end

function cpld_test_upgrade.process_upgrade(signal, system_id, firmware_type, file_path, parameters)
    local i = get_firmware_index(signal, parameters)
    if signal.upg_cfg_list[i].update_link and signal.upg_cfg_list[i].update_link == '1' then
        g_upgrade_ret = iic_upgrade_cpld(system_id, signal.fw_list, signal.upg_cfg_list, file_path, firmware_type, parameters)
    else
        g_upgrade_ret = upgrade_component_cpld(system_id, signal.fw_list, signal.upg_cfg_list, file_path)
    end
    -- 升级失败清CPLD升级标记
    defs.UPGRADING_CPLD = 0
    -- cpld资源全检process阶段不返回失败，需要走进finish阶段进行AC恢复
    fw_mgmt_client:UpdateServiceUpdateServiceProcessReply(context.new(), system_id, firmware_type,
        defs.RET.OK, parameters)
    return g_upgrade_ret
end

-- 修改通电开机策略
local function set_power_on_strategy(bus, id, strategy)
    local obj
    if fructl.is_multihost_type(bus) then
        -- multihost机型单个host通电开机策略与整机保持一致
        obj = fructl.get_chassis_fructl_obj(bus, id)
    else
        obj = fructl.get_fructl_obj(bus, id)
    end
    if not obj then
        log:error('set power on strategy: get power object failed')
        return
    end
    local ret, err = pcall(function()
        obj.PowerOnStrategy = strategy
    end)
    if ret then
        return
    end
    log:error('set power on strategy err:%s', err)
end

function cpld_test_upgrade.finish_stage(fw_client, signal, system_id, firmware_type, parameters)
    log:notice("upgrade_single_host system_id[%s] firmware_type[%s]", system_id, firmware_type)
    -- CPLD测试在process阶段已完成生效原逻辑动作
    if g_test_upgrade then
        defs.UPGRADING_CPLD = 0
        fw_client:UpdateServiceUpdateServiceFinishReply(context.new(), system_id, firmware_type, g_upgrade_ret,
            parameters)
        local fw_index = string.match(parameters.FirmwareIndex, "Firmware(%d+)")
        if tonumber(fw_index) == #signal.upg_cfg_list then
            log:notice('[CPLD] wait 30s ,then do ACCycle')
            skynet.fork(function()
                skynet.sleep(3000)
                set_power_lock_state(gen_hw_bus.get_instance().bus, false)
                set_power_on_strategy(gen_hw_bus.get_instance().bus, 1, 'AlwaysPowerOn')
                set_powerctrl_type(gen_hw_bus.get_instance().bus, 'ACCycle', 'Unknown')
            end)
            g_test_upgrade = false
            return
        end
    end
end

return cpld_test_upgrade
