-- 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 utils = require 'mc.utils'
local log = require 'mc.logging'
local context = require 'mc.context'
local defs = require 'unit_manager.class.logic_fw.comm_defs'
local fw_upgrade = require 'unit_manager.class.logic_fw.upgrade.fw_upgrade'
local valid_handle = require 'unit_manager.class.logic_fw.upgrade.valid'
local fpga_signal = require 'unit_manager.class.logic_fw.fpga.fpga_signal'
local upgrade_subject = require 'upgrade_subject.upgrade_subject'
local fw_mgmt_client = require 'general_hardware.client'
local fructl = require 'mcu.upgrade.fructl_handler'
local cmn = require 'common'
local fw_info_collector = require 'upgrade_subject.fw_info_collector'
local comm_method = require 'unit_manager.class.logic_fw.comm_method'

local ret_ok <const> = 0
local ret_err <const> = -1
local signal = {}
local g_upgrade_ret = ret_ok

local VALID_CONDITION_MAP = {
    [defs.VALID_CONDITION.POWER_OFF] = defs.VALID_CONDITION_STR.POWER_OFF,
    [defs.VALID_CONDITION.POWER_CYCLE] = defs.VALID_CONDITION_STR.POWER_CYCLE,
    [defs.VALID_CONDITION.CHASSIS_POWER_OFF] = defs.VALID_CONDITION_STR.CHASSIS_POWER_OFF,
    [defs.VALID_CONDITION.RESET_BMC] = defs.VALID_CONDITION_STR.RESET_BMC
}

local function get_update_component_fw(system_id, fw_name)
    for _, cfg in ipairs(signal.upg_cfg_list) do
        if cfg.name ~= fw_name or not cfg.uid then
            goto continue
        end

        for _, fw in ipairs(signal.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)
                return fw
            end
        end
        ::continue::
    end
end

local function get_active_condition(db, fw)
    if fructl.is_multihost_type(signal.bus) then
        return defs.VALID_CONDITION_STR.CHASSIS_POWER_OFF
    end

    if fw and (fw.valid_condition == defs.VALID_CONDITION.CHASSIS_POWER_OFF or 
        fw.valid_condition == defs.VALID_CONDITION.RESET_BMC) then
        return VALID_CONDITION_MAP[fw.valid_condition]
    end

    local customize_sign = db.CustomizeSign({Id = 'customsize_sign'})
    if customize_sign then
        log:notice('[CPLD]get customize ActiveCondition from db, value = %s', customize_sign.ActiveCondition)
        return customize_sign.ActiveCondition
    end

    return fw and fw.valid_condition and VALID_CONDITION_MAP[fw.valid_condition] or defs.VALID_CONDITION_STR.POWER_OFF
end

local function get_active_mode(fw)
    if fw and fw.valid_mode == defs.VALID_ACTION.SUPPORT_BMC_RESET_ACTIVE then
        return defs.VALID_ACTION_STR.RESET_BMC
    end
    if fw and fw.valid_mode == defs.VALID_ACTION.SUPPORT_NO_RESET_ACTIVE then
        return defs.VALID_ACTION_STR.RESET_NONE
    end
    return defs.VALID_ACTION_STR.RESET_AC
end

local function send_register_active_action(active_condition, active_mode, active_status, firmware_type)
    log:notice('[CPLD]Register Active Action start active_condition = %s active_mode = %s',
        active_condition, active_mode)
    local component_id_list, component_id_ex_list, uid_list = 
        fw_info_collector:get_register_info_from_fw_info_list(firmware_type)
    local hpm_version = fw_info_collector:get_hpm_version(firmware_type)
    local param = {}
    param[#param+1] = {Key = 'FirmwareId', Value = 'CPLD'}
    param[#param+1] = {Key = 'FirmwareType', Value = 'CPLD'}
    -- ActiveCondition如果获取为nil，需要默认值
    param[#param+1] = {Key = 'ActiveCondition', Value = active_condition or defs.VALID_CONDITION_STR.POWER_OFF}
    param[#param+1] = {Key = 'ActiveMode', Value = active_mode}
    param[#param+1] = {Key = 'ActiveStatus', Value = active_status}
    param[#param+1] = {Key = 'TargetVersion', Value = hpm_version}
    param[#param+1] = {Key = 'ComponentID', Value = component_id_list}
    param[#param+1] = {Key = 'SubComponentID', Value = component_id_ex_list}
    param[#param+1] = {Key = 'TargetUIDs', Value = uid_list}
    log:notice('[CPLD]Register Active Action, TargetVersion: %s, ComponentID: %s, SubComponentID: %s, TargetUIDs: %s',
               hpm_version, component_id_list, component_id_ex_list, uid_list)
    local ok, err = pcall(function()
        fw_mgmt_client:FirmwareActiveFirmwareActiveRegisterActiveAction(context.new(), param)
    end)
    if not ok then
        log:error('[CPLD]Register Active Action fail, err = %s', err)
    end
end

local function register_fw_active_info(system_id, db, fw_name, active_status, firmware_type)
    local component_fw = get_update_component_fw(system_id, fw_name)
    local active_condition = get_active_condition(db, component_fw)
    local active_mode = get_active_mode(component_fw)
    if active_mode == defs.VALID_ACTION_STR.RESET_NONE then
        log:notice('[CPLD]No Register Active Action')
        return
    end
    send_register_active_action(active_condition, active_mode, active_status, firmware_type)
end

local function is_immediately_active(system_id, db, fw_name, power_state)
    if power_state ~= 'OFF' then
        return false
    end

    local component_fw = get_update_component_fw(system_id, fw_name)
    local active_condition = get_active_condition(db, component_fw)
    if active_condition == defs.VALID_CONDITION_STR.RESET_BMC then
        return false
    end
    return true
end

local function register_cpld_active_info_for_vaild_file()
    skynet.sleep(3000) -- BMC重启后等待30s,确保db和CPLD加载出来再注册
    local system_id_list = valid_handle:get_system_id_list(false)
    for sys_id, _ in pairs(system_id_list) do
        if valid_handle:get_validating_flag(signal.db, sys_id) then
            log:notice('[CPLD]get valid file sys_id: %s', sys_id)
            -- 存在缓存文件，判断需要生效
            register_fw_active_info(sys_id, signal.db, 'Firmware1', 'Idle', 'CPLD')
            return
        end
    end

    log:notice('[CPLD]do not need valid, remove vaild file')
    utils.remove_file(defs.UPGRADE_CPLD_VALID_FILE_DIR)
end

local function cpld_register_observer(cpld_signal)
    -- Cpld:主板CPLD, BP_Cpld:背板CPLD包含内置和后置，天池除EXU外其他组件都认为是背板
    local cpld_firmware_type = {"Cpld", "BP_Cpld", "EXUCpld", "BCUCpld", "CLUCpld", "SEUCpld", "IEUCpld", "DPUCpld"}
    for i = 1, #cpld_firmware_type do
        upgrade_subject.get_instance():register_upgrade_observer(cpld_signal, cpld_firmware_type[i])
    end

    -- 注册固件生效监测
    upgrade_subject.get_instance():register_active_observer(cpld_signal, 'CPLD')
end

function signal.init(bus, db, fw)
    signal.bus = bus
    signal.db = db
    signal.fw_list = fw
    signal.upg_cfg_list = nil
    signal.upgrade_flag = false -- 目前升级为串行模式，通过upgrade_flag判断任务是否进行中
    signal.support_multifirmware = false
    signal.fpga_upgrade_signal = fpga_signal.init(bus, db, fw)

    -- 存在缓存文件，处理生效注册
    skynet.fork(function ()
        register_cpld_active_info_for_vaild_file()
    end)

    -- 注册固件监测
    cpld_register_observer(signal)

    return signal
end

function signal.upgrade_prepare_callback(ctx, system_id, firmware_type, cfg_path, _, parameters)
    log:notice("[cpld]Start the system[%s] %s upgrade prepare phase", system_id, firmware_type)
    fw_mgmt_client:UpdateServiceUpdateServiceUpdateUpgradeStatus(context.new(),
        system_id, firmware_type, ret_ok, 20, 'Running', parameters)

    local ok, rsp = fw_mgmt_client:PFileFileChown(context.new(), nil, cfg_path, 104, 104)  --设置配置文件属主为secbox
    if not ok then
        log:error('[cpld] chown cfg file failed, error %s', rsp)
        fw_mgmt_client:UpdateServiceUpdateServicePrepareReply(context.new(), system_id, firmware_type, '', ret_err,
            parameters)
        return
    end

    skynet.fork(function ()
        ok, rsp = pcall(fw_upgrade.prepare_upgrade, signal, system_id, firmware_type, cfg_path, parameters)
        if not ok then
            log:error('[cpld] prepare upgrade failed, error: %s', rsp)
            fw_mgmt_client:UpdateServiceUpdateServicePrepareReply(context.new(), system_id, firmware_type, '', ret_err,
                parameters)
        end
    end)
end

function signal.upgrade_process_callback(ctx, system_id, firmware_type, file_path, parameters)
    log:notice("[cpld]Start the system[%s] %s upgrade process phase", system_id, firmware_type)
    local ok, rsp = fw_mgmt_client:PFileFileChown(context.new(), nil, file_path, 104, 104)
    if not ok then
        log:error('[cpld] chownfile failed, error %s', rsp)
        fw_mgmt_client:UpdateServiceUpdateServiceProcessReply(context.new(), system_id, firmware_type, ret_err,
            parameters)
        return
    end
    fw_info_collector:clean(firmware_type)
    fw_info_collector:insert_hpm_version(firmware_type, parameters.Version)
    fw_mgmt_client:UpdateServiceUpdateServiceUpdateUpgradeStatus(context.new(),
        system_id, firmware_type, ret_ok, 40, 'Running', parameters)
    skynet.fork(function ()
        ok, g_upgrade_ret = pcall(fw_upgrade.process_upgrade, signal, system_id, firmware_type, file_path, parameters)
        if not ok then
            log:error('[cpld] process upgrade failed, error: %s', g_upgrade_ret)
            fw_mgmt_client:UpdateServiceUpdateServiceProcessReply(context.new(), system_id, firmware_type, ret_err,
                parameters)
        end
    end)
end

local function start_upgrade_single_host(system_id, firmware_type, valid_flag, parameters)
    local ret
    local ok, error = pcall(function ()
        ret = fw_upgrade.finish_upgrade(signal, system_id, firmware_type)
    end)

    if not ok then
        fw_upgrade.cpld_valid = false
        log:error("finish upgrade system id %s failed, error: %s", error)
        valid_handle:remove_vaild_file_by_system_id(system_id, false)
        valid_handle:remove_vaild_file_by_system_id(system_id, true)
    end
    if (valid_flag and not fw_upgrade.hot_upgrade) or (not ret and fw_upgrade.full_hot_valid) then
        log:notice('[cpld] Register active action of reset AC')
        register_fw_active_info(system_id, signal.db, parameters.FirmwareIndex, 'Ready', firmware_type)
    end
    -- 热升级流程走完后，需要恢复默认值，避免影响到冷升级
    defs.UPGRADING_CPLD = 0
    fw_upgrade.hot_upgrade = false
    fw_mgmt_client:UpdateServiceUpdateServiceFinishReply(context.new(), system_id, firmware_type, g_upgrade_ret,
        parameters)
end

local function upgrade_single_host(system_id, firmware_type, power_state, parameters)
    log:notice("upgrade_single_host system_id[%s] firmware_type[%s] power_state[%s]",
        system_id, firmware_type, power_state)
    local valid_flag = valid_handle:get_validating_flag(signal.db, system_id)
    -- 直接生效条件判断成立或热升级直接生效
    if is_immediately_active(system_id, signal.db, parameters.FirmwareIndex, power_state) or fw_upgrade.hot_upgrade then
        skynet.fork(function ()
            start_upgrade_single_host(system_id, firmware_type, valid_flag, parameters)
        end)
        return
    end

    -- 上电执行生效注册
    if valid_flag then
        defs.UPGRADING_CPLD = 0
        register_fw_active_info(system_id, signal.db, parameters.FirmwareIndex, 'Idle', firmware_type)
        fw_mgmt_client:UpdateServiceUpdateServiceFinishReply(context.new(), system_id, firmware_type, g_upgrade_ret,
            parameters)
    end
end

local function start_upgrade_all_host(system_id, firmware_type, system_id_list, parameters)
    local valid_flag = false
    for sys_id, _ in pairs(system_id_list) do
        -- 存在CPLD升级成功就需执行ACCycle
        valid_flag = valid_handle:get_validating_flag(signal.db, sys_id) and true or valid_flag
        local ok, error = pcall(function ()
            fw_upgrade.finish_upgrade(signal, sys_id, firmware_type)
        end)
        if not ok then
            log:error("system_id %s finish upgrade failed, error: %s", sys_id, error)
        end
    end

    fw_upgrade.cpld_valid = false
    utils.remove_file(defs.UPGRADE_CPLD_VALID_FILE_DIR)
    utils.remove_file(defs.HOT_UPGRADE_CPLD_VALID_FILE_DIR)
    if valid_flag and not fw_upgrade.hot_upgrade then
        log:notice('[cpld] Register active action of reset AC')
        register_fw_active_info(system_id, signal.db, parameters.FirmwareIndex, 'Ready', firmware_type)
    end
    -- 热升级流程走完后，需要恢复默认值，避免影响到冷升级
    defs.UPGRADING_CPLD = 0
    fw_upgrade.hot_upgrade = false
    fw_mgmt_client:UpdateServiceUpdateServiceFinishReply(context.new(), system_id, firmware_type, g_upgrade_ret,
        parameters)
end

local function upgrade_all_host(system_id, firmware_type, power_state, parameters)
    log:notice("upgrade_all_host system_id[%s] firmware_type[%s] power_state[%s]",
        system_id, firmware_type, power_state)
    local system_id_list = valid_handle:get_system_id_list(fw_upgrade.hot_upgrade)
    -- 直接生效条件判断成立或或热升级直接生效
    if is_immediately_active(system_id, signal.db, parameters.FirmwareIndex, power_state) or
        fw_upgrade.hot_upgrade then
        skynet.fork(function ()
            start_upgrade_all_host(system_id, firmware_type, system_id_list, parameters)
        end)
        return
    end

    -- 上电执行生效注册
    for sys_id, _ in pairs(system_id_list) do
        if valid_handle:get_validating_flag(signal.db, sys_id) then
            register_fw_active_info(sys_id, signal.db, parameters.FirmwareIndex, 'Idle', firmware_type)
            break
        end
    end
    defs.UPGRADING_CPLD = 0
    fw_mgmt_client:UpdateServiceUpdateServiceFinishReply(context.new(), system_id, firmware_type, g_upgrade_ret,
        parameters)
end

function signal.upgrade_finish_callback(ctx, system_id, firmware_type, parameters)
    log:notice("[cpld]Start the system[%s] %s upgrade finish phase", system_id, firmware_type)
    fw_mgmt_client:UpdateServiceUpdateServiceUpdateUpgradeStatus(context.new(),
        system_id, firmware_type, ret_ok, 90, 'Running', parameters)

    local ok, err = pcall(function()
        local power_state = comm_method.get_power_state(signal.bus)
        if system_id == defs.ALL_SYSTEM_ID then
            upgrade_all_host(system_id, firmware_type, power_state, parameters)
        else
            upgrade_single_host(system_id, firmware_type, power_state, parameters)
        end
    end)
    if not ok then
        log:error('[cpld] finish upgrade failed, error: %s', err)
        fw_mgmt_client:UpdateServiceUpdateServiceFinishReply(context.new(), system_id, firmware_type, ret_err,
            parameters)
    end
end

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

local function set_smc_accessibility(logic_fw, smc_obj, accessibility, timeout)
    if not smc_obj then
        log:error('Set smc accessibility failed, smc_obj nil')
        return
    end
    
    local ok, error_msg = pcall(smc_obj['bmc.kepler.Chip'].SetAccessibility, smc_obj['bmc.kepler.Chip'], context.new(), accessibility, timeout)
    if ok then
        log:notice('set %s smc chip accessibility %s successfully', logic_fw.device_name, accessibility)
    else
        log:error('set %s smc chip accessibility %s failed, error: %s',
            logic_fw.device_name, accessibility, error_msg)
    end
end

local function set_all_smc_accessibility(system_id, all_logic_fw, accessibility, timeout, valid_condition)
    log:notice('enter set_all_smc_accessibility')
    if not all_logic_fw then
        log:warn('set all smc accessibility failed, all_logic_fw nil')
        return
    end
    for _, v in pairs(all_logic_fw) do
        if (system_id == defs.ALL_SYSTEM_ID or v.system_id == system_id) and
            ((v.valid_condition == defs.VALID_CONDITION.RESET_BMC and
            valid_condition == defs.VALID_CONDITION_STR.RESET_BMC) or
            v.valid_access_forbidden) then
            set_smc_accessibility(v, v.smc_chip, accessibility, timeout)
        end
    end
end

local function active_single_host(system_id, firmware_type, valid_condition)
    log:notice("active_single_host system_id[%s] firmware_type[%s]", system_id, firmware_type)
    local ret = ret_ok
    skynet.fork(function ()
        local ok, value = pcall(function ()
            return fw_upgrade.finish_upgrade(signal, system_id, firmware_type)
        end)
        if not ok or not value then
            fw_upgrade.cpld_valid = false
            log:error("active process finish upgrade failed, error:%s", value)
            ret = ret_err
            utils.remove_file(defs.UPGRADE_CPLD_VALID_FILE_DIR)
        end
        log:notice('[CPLD]----------END Active----------')
        -- 等待10s，防止相应过快firmware_mgmt没有收到回应
        cmn.skynet.sleep(1000)
        -- 恢复CPLD的访问
        set_all_smc_accessibility(system_id, signal.fw_list, true, 1, valid_condition)
        fw_mgmt_client:FirmwareActiveFirmwareActiveActiveProcessReply(context.new(), system_id, firmware_type, ret)
    end)
end

local function active_all_host(system_id, firmware_type, valid_condition)
    log:notice("active_all_host system_id[%s] firmware_type[%s]", system_id, firmware_type)
    local ret = ret_ok
    skynet.fork(function ()
        local system_id_list = valid_handle:get_system_id_list(false)
        for sys_id, _ in pairs(system_id_list) do
            local ok, error = pcall(function ()
                return fw_upgrade.finish_upgrade(signal, sys_id, firmware_type)
            end)
            if not ok then
                log:error("sys_id %s active process finish upgrade failed, error: %s", sys_id, error)
                ret = ret_err
            end
        end
        fw_upgrade.cpld_valid = false
        utils.remove_file(defs.UPGRADE_CPLD_VALID_FILE_DIR)
        log:notice('[CPLD]----------END Active----------')
        -- 等待10s，防止相应过快firmware_mgmt没有收到回应
        cmn.skynet.sleep(1000)
        -- 恢复CPLD的访问
        set_all_smc_accessibility(system_id, signal.fw_list, true, 1, valid_condition)
        fw_mgmt_client:FirmwareActiveFirmwareActiveActiveProcessReply(context.new(), system_id, firmware_type, ret)
    end)
end

function signal.active_callback(ctx, system_id, firmware_type)
    log:notice('get active signal, system_id: %s, firmware_type: %s', system_id, firmware_type)

    if defs.ACTIVING_CPLD == 1 then
        log:notice('[CPLD]system(%s) another CPLD is Activing, wait active finish', system_id)
        return
    end
    defs.ACTIVING_CPLD = 1
    log:notice('[CPLD]----------Start Active----------')
    update_active_status('Apply')

    -- 如果生效条件是复位BMC触发的，则此时需要屏蔽CPLD的访问，首先获取生效条件
    local valid_condition
    local ok, active_info_obj = pcall(function()
        return fw_mgmt_client:GetFirmwareActiveInfoFirmwareActiveInfoObject({Id = 'CPLD'})
    end)
    if not ok then
        log:warn('get active info failed, do not disable cpld accessibility')
        valid_condition = defs.VALID_CONDITION_STR.POWER_OFF
    else
        valid_condition = active_info_obj.ActiveCondition
    end

    -- 屏蔽CPLD的访问
    set_all_smc_accessibility(system_id, signal.fw_list, false, 300, valid_condition)

    local ok, err = pcall(function()
        if system_id == defs.ALL_SYSTEM_ID then
            active_all_host(system_id, firmware_type, valid_condition)
        else
            active_single_host(system_id, firmware_type, valid_condition)
        end
    end)
    if not ok then
        log:error('[cpld] active failed, error: %s', err)
        fw_mgmt_client:FirmwareActiveFirmwareActiveActiveProcessReply(context.new(), system_id, firmware_type, ret_err)
    end

    defs.ACTIVING_CPLD = 0
end

function signal:on_upgrade_prepare(ctx, system_id, firmware_type, cfg_path, _, parameters)
    signal.upgrade_prepare_callback(ctx, system_id, firmware_type, cfg_path, _, parameters)
end

function signal:on_upgrade_process(ctx, system_id, firmware_type, cfg_path, parameters)
    signal.upgrade_process_callback(ctx, system_id, firmware_type, cfg_path, parameters)
end

function signal:on_upgrade_finish(ctx, system_id, firmware_type, parameters)
    signal.upgrade_finish_callback(ctx, system_id, firmware_type, parameters)
end

function signal:on_active_process(ctx, system_id, firmware_type)
    signal.active_callback(ctx, system_id, firmware_type)
end

return signal