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

local class = require 'mc.class'
local log = require 'mc.logging'
local utils = require 'types.utils'
local c_accycle = require 'ac_cycle'
local c_multihost = require 'multi_host'
local c_fructrl_capabilities = require 'fructrl_capabilities'
local skynet = require 'skynet'
local m_enums = require 'types.enums'
local m_error_custom = require 'messages.custom'
local m_error_base = require 'messages.base'
local WAIT_DPU_OS_START_REASON<const> = "DPU"

local cpower_api = class()

function cpower_api:ctor(ctrlstate, fructrl, pwr_cycle, id)
    log:debug('class power_api constructor')
    self.ctrlstate = ctrlstate
    self.fructrl = fructrl
    self.pwr_cycle = pwr_cycle
    self.system_id = id
end

local function execute_poweron(fruid, ctrlstate, fructrl, p_cycle, ctx)
    if fructrl:get_PwrOnLocked() then
        for _, reason in ipairs(fructrl:get_Reasons()) do
            if string.find(reason, WAIT_DPU_OS_START_REASON) then
                log:notice('wait DPU os start, Power-On return ok')
                return m_enums.RetValue.OK
            end
        end
    end
    if not fructrl:get_PwrOnLocked() or utils.is_permit_requestor(ctx.Requestor) then
        -- 打断cycle
        p_cycle:set_cycle_interrupt_flag(true)
        ctrlstate:send_powerctrl_event(tostring(m_enums.CtrlEventTypes.CtrlPowerOn), fruid)
        return m_enums.RetValue.OK
    end
    if fructrl:get_PwrOnLocked() and fructrl.power_on_lock and (ctx.Interface == 'CLI' or ctx.Interface == 'WEB') then
        local ret, reason = fructrl.power_on_lock:is_power_locked_by_upgrade()
        if ret then
            log:error_easy('[System:%s]The firmware is being upgraded. Power-on is not supported', fructrl.system_id)
            return m_enums.RetValue.ERR_UPGRADE, reason
        end
    end
    log:error_easy('[System:%s]The firmware is being upgraded. Power-on is not supported', fructrl.system_id)
    return m_enums.RetValue.ERR
end

local function execute_poweroff(fruid, ctrlstate, fructrl, p_cycle)
    -- 判断是否有下电锁
    if fructrl:get_PowerOffLocked() then
        return m_enums.RetValue.ERR_OFF
    end

    -- 打断cycle
    p_cycle:set_cycle_interrupt_flag(true)
    ctrlstate:send_powerctrl_event(tostring(m_enums.CtrlEventTypes.CtrlPowerOff), fruid)
    return m_enums.RetValue.OK
end

local function execute_powerforceoff(_, _, fructrl, p_cycle)
    -- 判断是否有下电锁
    if fructrl:get_PowerOffLocked() then
        return m_enums.RetValue.ERR_OFF
    end

    -- 打断cycle
    p_cycle:set_cycle_interrupt_flag(true)

    -- 强制下电没有状态机保护，创建个任务执行，防止有延时卡死
    skynet.fork_once(function()
        fructrl:poweroff_force()
    end)
    return m_enums.RetValue.OK
end

local function execute_powercycle(fruid, _, fructrl, p_cycle)
    -- 判断是否有下电锁
    if fructrl:get_PowerOffLocked() then
        return m_enums.RetValue.ERR_OFF
    end

    local flag = p_cycle:meet_cycle_condition(false, fruid)
    if not flag then
        return m_enums.RetValue.ERR
    end

    -- 先记录到数据库
    p_cycle:set_PwrCycleType(1)
    skynet.fork_once(function()
        p_cycle:pp_execute_power_cycle(1, fruid)
    end)
    return m_enums.RetValue.OK
end

local function execute_forcepowercycle(fruid, _, fructrl, p_cycle)
    -- 判断是否有下电锁
    if fructrl:get_PowerOffLocked() then
        return m_enums.RetValue.ERR_OFF
    end

    local flag = p_cycle:meet_cycle_condition(false, fruid)
    if not flag then
        return m_enums.RetValue.ERR
    end

    -- 先记录到数据库
    p_cycle:set_PwrCycleType(2)
    skynet.fork_once(function()
        p_cycle:pp_execute_power_cycle(2, fruid)
    end)
    return m_enums.RetValue.OK
end

local function execute_accycle()
    local ac_cycle = c_accycle:get_instance()
    ac_cycle:ac_down()
    return 0
end

local function execute_hardreset(fruid, _, fructrl)
    -- 先判断条件，返回是否可以继续执行
    if m_enums.PGSignalState[fructrl:get_FruPowerState(fruid)] ~= m_enums.PowerStatus.ON then
        log:error('[System:%s]Power state is not ON, cannot execute force_restart!', fructrl.system_id)
        return m_enums.RetValue.ERR
    end
    if fructrl.force_reset:force_restart(fruid) == m_enums.RetValue.ERR then
        return m_enums.RetValue.ERR
    end
    return m_enums.RetValue.OK
end

local function execute_nmi(fruid, _, fructrl)
    if m_enums.PGSignalState[fructrl:get_FruPowerState(fruid)] ~= m_enums.PowerStatus.ON then
        log:error('[System:%s]Power state is not ON, cannot execute nmi!', fructrl.system_id)
        return m_enums.RetValue.ERR
    end
    local fructrlcap = c_fructrl_capabilities:get_instance()
    if not fructrlcap:is_nmi_supported() then
        log:error('[System:%s]Nmi is not supported!', fructrl.system_id)
        return m_enums.RetValue.ERR
    end
    return fructrl.nmi:nmi_interrupt()
end

local ctrl_str = {
    [m_enums.PowerCtrlType.On] = 'power on ',
    [m_enums.PowerCtrlType.GracefulShutdown] = 'normal power off ',
    [m_enums.PowerCtrlType.ForceOff] = 'forced power off ',
    [m_enums.PowerCtrlType.PowerCycle] = 'normal power off the service system, and then power it on ',
    [m_enums.PowerCtrlType.ForcePowerCycle] = 'forced power off the service system, and then power it on ',
    [m_enums.PowerCtrlType.ForceRestart] = 'forced system reset ',
    [m_enums.PowerCtrlType.Nmi] = 'nmi '
}

local powerctrl_api = {
    [tostring(m_enums.PowerCtrlType.On)] = execute_poweron,
    [tostring(m_enums.PowerCtrlType.GracefulShutdown)] = execute_poweroff,
    [tostring(m_enums.PowerCtrlType.ForceOff)] = execute_powerforceoff,
    [tostring(m_enums.PowerCtrlType.PowerCycle)] = execute_powercycle,
    [tostring(m_enums.PowerCtrlType.ACCycle)] = execute_accycle,
    [tostring(m_enums.PowerCtrlType.ForcePowerCycle)] = execute_forcepowercycle,
    [tostring(m_enums.PowerCtrlType.ForceRestart)] = execute_hardreset,
    [tostring(m_enums.PowerCtrlType.Nmi)] = execute_nmi
}

local function record_operation_log(self, ctx, type, ret, reason, fruid)
    local result = 'successfully'
    if ret ~= 0 then
        result = 'failed'
    end
    if ctrl_str[type] then
        local multihost = c_multihost:get_instance()
        local id = multihost:is_multihost_type() and self.system_id or nil
        log:system(id):operation(ctx:get_initiator(),
            'fructrl', 'Set FRU' .. fruid .. ' to '  .. ctrl_str[type] .. result)
    end

    -- 记录操作日志后再触发错误引擎
    if ret == m_enums.RetValue.ERR then
        error(m_error_custom.OperationFailed())
    elseif ret == m_enums.RetValue.ERR_UPGRADE then
        error(m_error_custom.PowerOnNotAllowed(reason .. ' is upgrading!'))
    elseif ret == m_enums.RetValue.ERR_OFF then
        error(m_error_custom.PowerOffNotAllowed('system power off lock exists'))
    end
end

function cpower_api:system_reset(ctx, pctrl_type, restart_cause)
    if ctx.Requestor then
        log:notice('[System:%s]PowerCtrl is called by %s', self.system_id, ctx.Requestor)
    end
    local ret
    local id = ctx['fru_id'] or 0
    log:notice('[System:%s]pctrl_type=%s, restart_cause=%s, fruid=%s', self.system_id, pctrl_type, restart_cause, id)

    -- 入参判断
    if m_enums.PowerCtrlType[pctrl_type] == nil or
        m_enums.RestartCause[restart_cause] == nil then
        log:error('[System:%s]Wrong input parameter when call PowerCtrl!', self.system_id)
        error(m_error_base.PropertyValueNotInList('%PowerCtrlType:' .. pctrl_type, '%PowerCtrlType'))
    end

    if id ~= m_enums.FruId.GlobalDomain and id ~= m_enums.FruId.OS then
        log:error('FruID is out of range')
        record_operation_log(self, ctx, pctrl_type, m_enums.RetValue.ERR, 0)
        return m_enums.RetValue.ERR
    end

    skynet.fork_once(function()
        -- 设置当前复位初始化id
        if m_enums.PowerCtrlType[pctrl_type] == 'On' then
            self.fructrl:set_CurrentRestartType(m_enums.RestartInitiateId['InitiatedByPowerUp'])
        elseif m_enums.PowerCtrlType[pctrl_type] == 'ForceRestart' then
            self.fructrl:set_CurrentRestartType(m_enums.RestartInitiateId['InitiatedByWarmReset'])
        elseif m_enums.PowerCtrlType[pctrl_type] == 'PowerCycle' then
            self.fructrl:set_CurrentRestartType(m_enums.RestartInitiateId['InitiatedByHardReset'])
        elseif m_enums.PowerCtrlType[pctrl_type] == 'ForcePowerCycle' then
            self.fructrl:set_CurrentRestartType(m_enums.RestartInitiateId['InitiatedByHardReset'])
        end

        log:notice('CurrentRestartType=%d', self.fructrl:get_CurrentRestartType())

        skynet.sleep(200) -- 等待2s，等待传感器事件生成后恢复默认值
        self.fructrl:set_CurrentRestartType(m_enums.RestartInitiateId['None'])
    end)

    local reason
    if id == m_enums.FruId.GlobalDomain and ((pctrl_type ~= m_enums.PowerCtrlType.ForceRestart and
        pctrl_type ~= m_enums.PowerCtrlType.GracefulShutdown and
        pctrl_type ~= m_enums.PowerCtrlType.PowerCycle) or (not self.fructrl.power_button.objs[id])) then
        -- 北向传参FruID为全域, 只允许操作重启/安全下电/先下电再上电
        -- 北向传参FruID为全域，但无全域短按长按对象须报错(复位对象不走控制状态机，能将err返回至调用处)
        log:error('GlobalDomain operation not supported')
        ret = m_enums.RetValue.ERR
    else
        -- 设置当前复位原因id
        self.fructrl:set_CurrentRestartCauseId(m_enums.RestartCauseId[restart_cause])
        -- 记录复位原因
        self.fructrl:add_RestartCauseRecords(restart_cause)
        ret, reason = powerctrl_api[pctrl_type](id, self.ctrlstate, self.fructrl, self.pwr_cycle, ctx)
    end
    record_operation_log(self, ctx, pctrl_type, ret, reason, id)
    return ret
end

return cpower_api
