-- 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 fw_upgrade = require 'unit_manager.class.logic_fw.upgrade.fw_upgrade'
local log = require 'mc.logging'
local fw_mgmt_client = require 'general_hardware.client'
local context = require 'mc.context'
local fructl = require 'mcu.upgrade.fructl_handler'
local cmn = require 'common'
local defs = require 'unit_manager.class.logic_fw.comm_defs'
local utils = require 'mc.utils'
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 ret_ok <const> = 0
local ret_err <const> = -1
local signal = {}
local g_upgrade_ret = ret_ok

local function register_fw_active_info(firmware_id, db, active_status)
    local customize_sign = db.CustomizeSign({Id = 'customsize_sign'})
    local active_condition = customize_sign and customize_sign.ActiveCondition or 'PowerOff'
    if fructl.is_multihost_type(signal.bus) then
        active_condition = 'ChassisPowerOff'
    end
    log:notice('[CPLD]Register Active Action start firmware_id = %s active_condition = %s',
        firmware_id, active_condition)

    local param = {}
    param[#param+1] = {Key = 'FirmwareId', Value = firmware_id}
    param[#param+1] = {Key = 'FirmwareType', Value = 'CPLD'}
    param[#param+1] = {Key = 'ActiveCondition', Value = active_condition}
    param[#param+1] = {Key = 'ActiveMode', Value = 'ResetAC'}
    param[#param+1] = {Key = 'ActiveStatus', Value = active_status}
    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_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('CPLD_ResetAC', signal.db, 'Idle')
            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"}
    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.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)
    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')

    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)
        return
    end

    skynet.fork(function ()
        ok, rsp = pcall(fw_upgrade.prepare_upgrade, signal, system_id, firmware_type, cfg_path)
        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)
        end
    end)
end

function signal.upgrade_process_callback(ctx, system_id, firmware_type, file_path)
    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)
        return
    end
    fw_mgmt_client:UpdateServiceUpdateServiceUpdateUpgradeStatus(context.new(),
        system_id, firmware_type, ret_ok, 40, 'Running')
    skynet.fork(function ()
        ok, g_upgrade_ret = pcall(fw_upgrade.process_upgrade, signal, system_id, firmware_type, file_path)
        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)
        end
    end)
end

local function start_upgrade_single_host(system_id, firmware_type, valid_flag)
    local ok, error = pcall(function ()
        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 then
        log:notice('[cpld] Register active action of reset AC')
        register_fw_active_info('CPLD_ResetAC', signal.db, 'Ready')
    end
    -- 热升级流程走完后，需要恢复默认值，避免影响到冷升级
    defs.UPGRADING_CPLD = 0
    fw_upgrade.hot_upgrade = false
    fw_mgmt_client:UpdateServiceUpdateServiceFinishReply(context.new(), system_id, firmware_type, g_upgrade_ret)
end

local function upgrade_single_host(system_id, firmware_type, power_state)
    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 power_state == 'OFF' or fw_upgrade.hot_upgrade then
        skynet.fork(function ()
            start_upgrade_single_host(system_id, firmware_type, valid_flag)
        end)
        return
    end

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

local function start_upgrade_all_host(system_id, firmware_type, system_id_list)
    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('CPLD_ResetAC', signal.db, 'Ready')
    end
    -- 热升级流程走完后，需要恢复默认值，避免影响到冷升级
    defs.UPGRADING_CPLD = 0
    fw_upgrade.hot_upgrade = false
    fw_mgmt_client:UpdateServiceUpdateServiceFinishReply(context.new(), system_id, firmware_type, g_upgrade_ret)
end

local function upgrade_all_host(system_id, firmware_type, power_state)
    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 power_state == 'OFF' or fw_upgrade.hot_upgrade then
        skynet.fork(function ()
            start_upgrade_all_host(system_id, firmware_type, system_id_list)
        end)
        return
    end

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

local function get_power_state()
    local power_state
    -- multihost场景，获取整机电源状态
    if fructl.is_multihost_type(signal.bus) then
        power_state = fructl.get_chassis_power_status(signal.bus, 1)
    else
        power_state = fructl.get_power_status(signal.bus, 1)
    end
    return power_state
end

function signal.upgrade_finish_callback(ctx, system_id, firmware_type)
    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')

    local ok, err = pcall(function()
        local power_state = get_power_state()
        if system_id == defs.ALL_SYSTEM_ID then
            upgrade_all_host(system_id, firmware_type, power_state)
        else
            upgrade_single_host(system_id, firmware_type, power_state)
        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)
    end
end

local function update_active_status(firmware_id, firmware_type, status)
    log:notice('[CPLD]update active status ')
    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_client:FirmwareActiveFirmwareActiveUpdateActiveStatus(context.new(), param)
end

local function active_single_host(system_id, firmware_type)
    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)
        fw_mgmt_client:FirmwareActiveFirmwareActiveActiveProcessReply(context.new(), system_id, firmware_type, ret)
    end)
end

local function active_all_host(system_id, firmware_type)
    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)
        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----------')
    local firmware_id = 'CPLD_ResetAC'
    update_active_status(firmware_id, firmware_type, 'Apply')

    local ok, err = pcall(function()
        if system_id == defs.ALL_SYSTEM_ID then
            active_all_host(system_id, firmware_type)
        else
            active_single_host(system_id, firmware_type)
        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)
    signal.upgrade_prepare_callback(ctx, system_id, firmware_type, cfg_path)
end

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

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

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

return signal