-- 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 custom_msg = require 'messages.custom'
local context = require 'mc.context'
local cjson = require 'cjson'
local log = require 'mc.logging'
local mdb_service = require 'mc.mdb.mdb_service'
local ENERGY_SAVING_SCENE_INTERFACE <const> = 'bmc.kepler.Chassis.EnergySavingScene'
local POWER_STRATEGY_INTERFACE <const> = 'bmc.kepler.Systems.PowerStrategy'
local COOLING_CONFIG_INTERFACE <const> = 'bmc.kepler.Systems.CoolingConfig'
local BIOS_INTERFACE <const> = 'bmc.kepler.Systems.Bios'

local m = {}

function m.get_expected_activePSU(chassis_id, is_active_standby_supported, mode, expected_active_psu, power_paths)
    local ret = {}

    if is_active_standby_supported == false or mode == 'LoadBalancing' then
        for _, v in ipairs(power_paths) do
            local obj = mdb.get_object(bus, v, 'bmc.kepler.Systems.PowerMgmt.OnePower')
            local psu = {}
            local odata_id = string.format('/redfish/v1/Chassis/%s/Power#/PowerSupplies/%d', tostring(chassis_id),
                obj.SlotNumber - 1)
            psu['@odata.id'] = odata_id
            ret[#ret + 1] = psu
        end

        return ret
    end

    for _, slot_id in ipairs(expected_active_psu) do
        local psu = {}
        local odata_id = string.format('/redfish/v1/Chassis/%s/Power#/PowerSupplies/%d', tostring(chassis_id),
            slot_id - 1)
        psu['@odata.id'] = odata_id
        ret[#ret + 1] = psu
    end

    return ret
end

function m.set_power_limit_value(limit_value, current_limit_enable)
    local MDB_SERVICE = 'bmc.kepler.power_strategy'
    local MDB_PATH = '/bmc/kepler/Systems/1/PowerStrategy'
    local MDB_INTERFACE = 'bmc.kepler.Systems.PowerStrategy'
    local limit_enable = limit_value ~= cjson.null
    if limit_enable then
        local err, _ = bus:pcall(MDB_SERVICE, MDB_PATH, MDB_INTERFACE, 'SetPowerLimitValue', 'a{ss}u',
        context.get_context() or context.new(), limit_value)
        if err then
            if err.name == "PropertyValueNotInList" then
                err.params[2] = 'PowerControl/0/PowerLimit/LimitInWatts'
            end
            err.RelatedProperties = { '#/PowerControl/0/PowerLimit/LimitInWatts' }
            error(err)
        end
    end
    return {limit_enable ~= current_limit_enable, limit_enable}
end

function m.get_power_working_mode_from_req(chassis_id, expected_redundancy)
    local prefix_str = "/redfish/v1/Chassis/" .. chassis_id .. "/Power#/Redundancy/"
    if string.sub(expected_redundancy['@odata.id'], 1, #expected_redundancy['@odata.id'] - 1) ~= prefix_str then
        local args = 'PowerControl/0/Oem/{{OemIdentifier}}/ExpectedRedundancy/@odata.id'
        error(custom_msg.InvalidOdataId(args, expected_redundancy['@odata.id']))
    end
    if expected_redundancy['@odata.id'] == prefix_str .. '0' then
        return 'LoadBalancing'
    elseif expected_redundancy['@odata.id'] == prefix_str .. '1' then
        return 'Active/Standby'
    else
        return 'Unknown'
    end
end

function m.get_expected_activePSU_from_req(chassis_id, expected_active_psu_table)
    local ret = {}
    if not expected_active_psu_table then
        return ret
    end
    local prefix_str = "/redfish/v1/Chassis/" .. chassis_id .. "/Power#/PowerSupplies/"
    local args = 'PowerControl/0/Oem/{{OemIdentifier}}/ExpectedActivePSU/0/@odata.id'
    local odata_id
    for _, obj_active_psu in pairs(expected_active_psu_table) do
        if not obj_active_psu['@odata.id'] then
            goto continue
        end
        if prefix_str ~= string.sub(obj_active_psu['@odata.id'], 1, #obj_active_psu['@odata.id'] - 1) then
            error(custom_msg.InvalidOdataId(args, obj_active_psu['@odata.id']))
        end
        odata_id = string.sub(obj_active_psu['@odata.id'], #obj_active_psu['@odata.id'])
        ret[#ret + 1] = odata_id - '0' + 1
        ::continue::
    end
    return ret
end

function m.get_voltages_sensor_health(voltages_sensor_health)
    if voltages_sensor_health == 'OK' then
        return 'OK'
    elseif voltages_sensor_health == 'Minor' or voltages_sensor_health == 'Major' then
        return 'Warning'
    elseif voltages_sensor_health == 'Critical' then
        return 'Critical'
    end

    return nil
end

function m.get_voltages_item(sensor, chassis_id, member_id)
    local voltage = cjson.json_object_new_object()
    voltage["@odata.id"] = string.format('/redfish/v1/Chassis/%s/Power#/Voltages/%s', chassis_id, member_id)
    voltage["MemberId"] = tostring(member_id)
    voltage["Name"] = sensor.SensorName
    voltage["SensorNumber"] = sensor.SensorNumber
    voltage["Status"] = cjson.json_object_new_object()
    voltage["Status"]["State"] = sensor.Status
    voltage["Status"]["Oem"] = cjson.json_object_new_object()
    voltage["Status"]["Oem"]["{{OemIdentifier}}"] = cjson.json_object_new_object()
    voltage["Status"]["Oem"]["{{OemIdentifier}}"]["Severity"] = sensor.Health == 'OK' and
        'Informational' or sensor.Health
    voltage["Status"]["Health"] = m.get_voltages_sensor_health(sensor.Health)
    voltage["ReadingVolts"] = sensor.SensorReading ~= 'na' and tonumber(sensor.SensorReading) or null
    voltage["UpperThresholdNonCritical"] = sensor.UpperThresholdNonCritical ~= 'null' and
        tonumber(sensor.UpperThresholdNonCritical) or null
    voltage["UpperThresholdCritical"] = sensor.UpperThresholdCritical ~= 'null' and
        tonumber(sensor.UpperThresholdCritical) or null
    voltage["UpperThresholdFatal"] = sensor.UpperThresholdFatal ~= 'null' and
        tonumber(sensor.UpperThresholdFatal) or null
    voltage["LowerThresholdNonCritical"] = sensor.LowerThresholdNonCritical ~= 'null' and
        tonumber(sensor.LowerThresholdNonCritical) or null
    voltage["LowerThresholdCritical"] = sensor.LowerThresholdCritical ~= 'null' and
        tonumber(sensor.LowerThresholdCritical) or null
    voltage["LowerThresholdFatal"] = sensor.LowerThresholdFatal ~= 'null' and
        tonumber(sensor.LowerThresholdFatal) or null
    voltage["MinReadingRange"] = null
    voltage["MaxReadingRange"] = null
    return voltage
end

function m.get_voltages_array(sensor_list, chassis_id)
    local member_id = 0

    if sensor_list == nil then
        return nil
    end
    local voltages = cjson.json_object_new_array()
    for _, sensor in ipairs(sensor_list) do
        -- 电压传感器类型为2
        if sensor.SensorType == 2 then
            voltages[#voltages + 1] = m.get_voltages_item(sensor, chassis_id, member_id)
            member_id = member_id + 1
        end
    end

    return voltages
end

-- lua以string为key的第二维table转换成有序json
function m.order_power_history_data(data_src)
    local data_dest = cjson.json_object_new_array()
    for _, data in pairs(data_src) do
        local ordered_json_data = cjson.json_object_new_object()
        ordered_json_data.Time = data.Time
        ordered_json_data.PowerWatts = data.PowerWatts
        ordered_json_data.PowerAverageWatts = data.PowerAverageWatts
        ordered_json_data.PowerPeakWatts = data.PowerPeakWatts
        data_dest[#data_dest + 1] = ordered_json_data
    end
    return data_dest
end

-- 预期主备,返回预期的主用电源, 负载均衡返回所有电源
function m.get_active_psu(power_work_mode, expected_active_psu, actual_psu)
    local psu_list = {}
    if power_work_mode == 'Active/Standby' then
        for _, ps_id in ipairs(expected_active_psu) do
            table.insert(psu_list, 'PSU' .. ps_id)
        end
    else
        local ps_tab = cjson.json_object_to_table(actual_psu)
        for _, ps in pairs(ps_tab) do
            table.insert(psu_list, 'PSU' .. ps.SlotNumber)
        end
    end
    table.sort(psu_list)
    return psu_list
end

-- 若输入为PSU1,PSU2, 返回预期设置的主用电源列表{1,2}
function m.get_expected_active_psu_list(ps_list)
    local ps_tab = cjson.json_object_to_table(ps_list)
    local res = {}
    for _, ps in pairs(ps_tab) do
        local ps_id = ps:sub(4)
        ps_id = tonumber(ps_id)
        table.insert(res, ps_id)
    end
    table.sort(res)
    return res
end

-- 将输入的对象格式cpu频率转换为数组
function m.get_cpu_frequency(cpu_frequency)
    local res = {}
    local cpu_fre_tab = cjson.json_object_to_table(cpu_frequency)
    for numa, fre_arr in pairs(cpu_fre_tab) do
        local numa_id = numa:sub(5)  -- 如NUMA10取第5位开始的数字id
        table.insert(res, {
            tonumber(numa_id),
            fre_arr
        })
    end
    return res
end

-- 将资源树中的cpu频率数组转换为对象格式
function m.get_converted_cpu_frequency(cpu_frequency)
    local rsp = cjson.json_object_new_object() -- 创建object对象
    local cpu_fre_tab = cjson.json_object_to_table(cpu_frequency)
    table.sort(cpu_fre_tab, function(a, b)
        return a[1] < b[1]                                  -- 根据numa id从小到大排序
    end)
    for _, fre_obj in pairs(cpu_fre_tab) do                 -- 向object对象中插入对象
        local key = 'NUMA' .. fre_obj[1]
        rsp[key] = cjson.json_object_from_table(fre_obj[2]) -- cpu核频率数组
    end
    return rsp
end

function m.get_ps_component_health(slot)
    local supply_paths = mdb_service.get_sub_paths(bus, "/bmc/kepler/Systems/1/Components", 1,
        { 'bmc.kepler.Systems.Component' }).SubPaths
    for _, supply_path in pairs(supply_paths) do
        local component_item = mdb.get_object(bus, supply_path, 'bmc.kepler.Systems.Component')
        if component_item.Name == 'PS' and component_item.Instance == slot then
            return component_item.Health
        end
    end
    return nil
end

function m.get_power_converter_list(Input, chassis_id)
    local power_converter_list = {}
    for _, psu in pairs(Input) do
        if psu.SlotNumber > 0 and string.match(psu.DeviceLocator, 'PowerConverter') then
            local member_id = psu.SlotNumber - 1
            power_converter_list[#power_converter_list + 1] = {
                ['@odata.id'] = '/redfish/v1/Chassis/' .. chassis_id ..
                    '/PowerSubsystem/Oem/{{OemIdentifier}}/PowerConverters/' .. member_id
            }
        end
    end

    table.sort(power_converter_list, function(a, b) return a['@odata.id'] < b['@odata.id'] end)
    return power_converter_list
end

function m.is_valid_power_converter_id(Input, powerconverter_id)
    local id = tonumber(powerconverter_id)
    for _, psu in pairs(Input) do
        if string.match(psu.DeviceLocator, "PowerConverter") and psu.SlotNumber - 1 == id then
            return true
        end
    end
    return false
end

function m.get_power_converter_by_id(Input, powerconverter_id)
    local id = tonumber(powerconverter_id)
    for _, supplies in pairs(Input) do
        if supplies.SlotNumber - 1 == id then
            return supplies.Path
        end
    end
    return ''
end

function m.get_chassis_ps_component_health()
    local health = 0
    local supply_paths = mdb_service.get_sub_paths(bus, "/bmc/kepler/Systems/1/Components", 1,
        { 'bmc.kepler.Systems.Component' }).SubPaths
    for _, supply_path in pairs(supply_paths) do
        local component_obj = mdb.get_object(bus, supply_path, 'bmc.kepler.Systems.Component')
        if component_obj.Name == 'PowerSupply' and component_obj.Health > health then
            health = component_obj.Health
        end
    end
    return health
end

local severity = {
    [0] = 'Informational',
    [1] = 'Minor',
    [2] = 'Major',
    [3] = 'Critical'
}

function m.get_redundancy_severity(health_code, mode, expect_mode)
    if mode ~= expect_mode then
        return cjson.null
    end
    if not severity[health_code] then
        return cjson.null
    end

    return severity[health_code]
end

local health = {
    [0] = 'OK',
    [1] = 'Warning',
    [2] = 'Warning',
    [3] = 'Critical'
}

function m.get_redundancy_health(health_code, mode, expect_mode)
    if mode ~= expect_mode then
        return cjson.null
    end
    if not health[health_code] then
        return cjson.null
    end

    return health[health_code]
end

function m.get_redundancy_set(max_psu_num, mode, expect_mode, expected_active_psu)
    local redundancy_set = cjson.json_object_new_array()
    if mode ~= 'Active/Standby' or mode ~= expect_mode then
        for i = 1, max_psu_num, 1 do
            redundancy_set[#redundancy_set + 1] = cjson.json_object_new_object()
            redundancy_set[#redundancy_set]["@odata.id"] =
                string.format("/redfish/v1/Chassis/1/Power#/PowerSupplies/%s", i - 1)
        end
        return redundancy_set
    end
    -- 主备模式，只显示备用电源
    for i = 1, max_psu_num, 1 do
        for _, slot in pairs(expected_active_psu) do
            if i ~= slot then
                redundancy_set[#redundancy_set + 1] = cjson.json_object_new_object()
                redundancy_set[#redundancy_set]["@odata.id"] =
                    string.format("/redfish/v1/Chassis/1/Power#/PowerSupplies/%s", i - 1)
            end
        end
    end
    return redundancy_set
end

function m.get_redundancy(device_specication, expect_mode, expected_active_psu)
    local max_psu_num = 0
    for _, tab in ipairs(device_specication) do
        if tab[1] == 'Psu' then
            max_psu_num = tab[2]
        end
    end
    local health_code = m.get_chassis_ps_component_health()
    local redundancy = cjson.json_object_new_array()
    redundancy[1] = cjson.json_object_new_object()
    redundancy[1]["@odata.id"] = "/redfish/v1/Chassis/1/Power#/Redundancy/0"
    redundancy[1]["@odata.type"] = "#Redundancy.v1_2_1.Redundancy"
    redundancy[1]["MemberId"] = "0"
    redundancy[1]["Name"] = "PowerSupply Redundancy Group 1"
    redundancy[1]["Mode"] = "Sharing"
    redundancy[1]["MaxNumSupported"] = max_psu_num
    redundancy[1]["MinNumNeeded"] = 2
    redundancy[1]["RedundancySet"] = cjson.json_object_new_array()
    redundancy[1]["RedundancySet"] = m.get_redundancy_set(max_psu_num, 'LoadBalancing', expect_mode,
        expected_active_psu)
    redundancy[1]["Status"] = cjson.json_object_new_object()
    redundancy[1]["Status"]["State"] = "Enabled"
    redundancy[1]["Status"]["Oem"] = cjson.json_object_new_object()
    redundancy[1]["Status"]["Oem"]["{{OemIdentifier}}"] = cjson.json_object_new_object()
    redundancy[1]["Status"]["Oem"]["{{OemIdentifier}}"]["Severity"] =
        m.get_redundancy_severity(health_code, 'LoadBalancing', expect_mode)
    redundancy[1]["Status"]["Health"] = m.get_redundancy_health(health_code, 'LoadBalancing', expect_mode)

    redundancy[2] = cjson.json_object_new_object()
    redundancy[2]["@odata.id"] = "/redfish/v1/Chassis/1/Power#/Redundancy/0"
    redundancy[2]["@odata.type"] = "#Redundancy.v1_2_1.Redundancy"
    redundancy[2]["MemberId"] = "1"
    redundancy[2]["Name"] = "PowerSupply Redundancy Group 2"
    redundancy[2]["Mode"] = "Failover"
    redundancy[2]["MaxNumSupported"] = max_psu_num
    redundancy[2]["MinNumNeeded"] = 2
    redundancy[2]["RedundancySet"] = cjson.json_object_new_array()
    redundancy[2]["RedundancySet"] = m.get_redundancy_set(max_psu_num, 'Active/Standby', expect_mode,
        expected_active_psu)
    redundancy[2]["Status"] = cjson.json_object_new_object()
    redundancy[2]["Status"]["State"] = "Enabled"
    redundancy[2]["Status"]["Oem"] = cjson.json_object_new_object()
    redundancy[2]["Status"]["Oem"]["{{OemIdentifier}}"] = cjson.json_object_new_object()
    redundancy[2]["Status"]["Oem"]["{{OemIdentifier}}"]["Severity"] =
        m.get_redundancy_severity(health_code, 'Active/Standby', expect_mode)
    redundancy[2]["Status"]["Health"] = m.get_redundancy_health(health_code, 'Active/Standby', expect_mode)

    return redundancy
end

function m.is_power_presence(slot_number)
    local id = tonumber(slot_number)
    local onepower_path = bus:call('bmc.kepler.maca', '/bmc/kepler/MdbService', 'bmc.kepler.Mdb', 'GetPath',
    'a{ss}ssb', context.new(), "bmc.kepler.Systems.PowerMgmt.OnePower",
    cjson.encode({SlotNumber = id}), false)
    if onepower_path == '' then
        return false
    end
    return true
end

function m.get_redundancy_group(max_psu_num)
    local redundancy_set = cjson.json_object_new_array()
    for i = 1, max_psu_num, 1 do
        if m.is_power_presence(i) then
            redundancy_set[#redundancy_set + 1] = cjson.json_object_new_object()
            redundancy_set[#redundancy_set]["@odata.id"] =
                string.format("/redfish/v1/Chassis/1/Power#/PowerSupplies/%s", i)
        end
    end
    return redundancy_set
end

function m.get_powersupply_redundancy(device_specication, expect_mode, expected_active_psu)
    local max_psu_num = 0
    for _, tab in ipairs(device_specication) do
        if tab[1] == 'Psu' then
            max_psu_num = tab[2]
        end
    end
    local health_code = m.get_chassis_ps_component_health()
    local redundancy = cjson.json_object_new_array()
    redundancy[1] = cjson.json_object_new_object()
    if expect_mode == "LoadBalancing" then
        redundancy[1]["RedundancyType"] = "Sharing"
        redundancy[1]["MaxSupportedInGroup"] = max_psu_num
        redundancy[1]["MinNeededInGroup"] = 2
        redundancy[1]["RedundancyGroup"] = cjson.json_object_new_array()
        redundancy[1]["RedundancyGroup"] = m.get_redundancy_group(max_psu_num)
        redundancy[1]["Status"] = cjson.json_object_new_object()
        redundancy[1]["Status"]["State"] = health_code ~= 0 and "Disabled" or "Enabled"
        redundancy[1]["Status"]["Health"] = health[health_code]
    elseif expect_mode == "Active/Standby" then
        redundancy[1] = cjson.json_object_new_object()
        redundancy[1]["RedundancyType"] = "Failover"
        redundancy[1]["MaxSupportedInGroup"] = max_psu_num
        redundancy[1]["MinNeededInGroup"] = 2
        redundancy[1]["RedundancyGroup"] = cjson.json_object_new_array()
        redundancy[1]["RedundancyGroup"] = m.get_redundancy_group(max_psu_num)
        redundancy[1]["Status"] = cjson.json_object_new_object()
        redundancy[1]["Status"]["State"] = health_code ~= 0 and "Disabled" or "Enabled"
        redundancy[1]["Status"]["Health"] = health[health_code]
    end

    return redundancy
end

function m.get_status(expect_mode)
    local health_code = m.get_chassis_ps_component_health()
    local State = health_code ~= 0 and "Disabled" or "Enabled"
    local Health = health[health_code]
    return {State, Health}
end

local function get_object(mdb, bus, path, interface)
    local ok, obj = pcall(function()
        return mdb.get_object(bus, path, interface)
    end)
    if not ok then
        return nil
    end
    return obj
end

local function get_energy_saving_config_by_scene(scene)
    local MDB_SERVICE = 'bmc.kepler.power_strategy'
    local MDB_PATH = '/bmc/kepler/Chassis/1/EnergySavingScene'
    local err, energy_saving_config = bus:pcall(MDB_SERVICE, MDB_PATH, ENERGY_SAVING_SCENE_INTERFACE,
        'GetModeByScene', 'a{ss}s', context.get_context() or context.new(), scene)
    if err or not next(energy_saving_config) then
        return nil
    end
    return energy_saving_config
end

local function encode_attributes(attributes)
    local data = {}
    data['Attributes'] = attributes
    return cjson.encode(data)
end

function m.set_energy_saving_mode(scene, expected_active_psu)
    -- 校验节能场景配置
    local energy_saving_scene_path = '/bmc/kepler/Chassis/1/EnergySavingScene'
    local energy_saving_scene = get_object(mdb, bus, energy_saving_scene_path, ENERGY_SAVING_SCENE_INTERFACE)
    if not energy_saving_scene then
        error(base_messages.ActionNotSupported("DynamicEnergySavingScene"))
    end

    -- 校验电源模式设置
    local power_strategy_path = '/bmc/kepler/Systems/1/PowerStrategy'
    local power_strategy_obj = mdb.get_object(bus, power_strategy_path, POWER_STRATEGY_INTERFACE)
    if not power_strategy_obj then
        error(base_messages.InternalError())
    end

    -- 校验散热模式设置
    local cooling_config_path = '/bmc/kepler/Systems/1/CoolingConfig'
    local cooling_config_obj = get_object(mdb, bus, cooling_config_path, COOLING_CONFIG_INTERFACE)
    if not cooling_config_obj then
        error(base_messages.InternalError())
    end

    -- 校验Bios设置
    local supply_paths = mdb_service.get_sub_paths(bus, "/bmc/kepler/Systems", 2, { BIOS_INTERFACE }).SubPaths
    if not next(supply_paths) then
        error(base_messages.InternalError())
    end

    -- 根据场景获取需要修改的智能节能配置信息
    local energy_saving_config = get_energy_saving_config_by_scene(scene)
    if not energy_saving_config then
        error(base_messages.InternalError())
    end

    -- 设置供电
    local expected_psu = {}
    if expected_active_psu ~= nil then
        for _, psu_id in pairs(expected_active_psu) do
            expected_psu[#expected_psu+1] = psu_id
        end
    end

    power_strategy_obj:SetPowerWorkingMode(context.get_context() or context.new(),
        energy_saving_config.Power.PowerWorkingMode, expected_psu)

    -- 设置散热
    pcall(cooling_config_obj.SetSmartCoolingMode, cooling_config_obj, context.get_context() or context.new(),
        energy_saving_config.Thermal.SmartCoolingMode)

    -- 设置Bios
    for _, bios_path in pairs(supply_paths) do
        local bios_obj = mdb.get_object(bus, bios_path, BIOS_INTERFACE)
        pcall(bios_obj.ImportBiosSetup, bios_obj, context.get_context() or context.new(), "Setting",
            encode_attributes(energy_saving_config.Bios))
    end

    return true
end

function m.get_power_mode_by_redundancy_mode(redundancy_mode)
    if redundancy_mode == 'Sharing' then
        return 'LoadBalancing'
    elseif redundancy_mode == 'Failover' then
        return 'Active/Standby'
    end

    return 'Unknown'
end

function m.get_activePSU_by_redundancy_set(chassis_id, power_paths, redundancy_mode, expected_standby_psu_table)
    local ret = {}
    if (redundancy_mode == 'Sharing') or (not power_paths) then
        return ret
    end
    if not expected_standby_psu_table then
        error(custom_msg.ModifyFailedMissingReqProperties('Redundancy/0/RedundancySet', 'Redundancy/0/RedundancySet'))
    end

    local prefix_str = "/redfish/v1/Chassis/" .. chassis_id .. "/Power#/PowerSupplies/"
    local args = 'Redundancy/0/RedundancySet/0/@odata.id'
    local odata_id
    local expected_standby_psu_id_set = {}
    for _, obj_standby_psu in pairs(expected_standby_psu_table) do
        if not obj_standby_psu['@odata.id'] then
            goto continue
        end
        if (#prefix_str >= #obj_standby_psu['@odata.id']) or
            (prefix_str ~= string.sub(obj_standby_psu['@odata.id'], 1, #prefix_str)) then
            error(custom_msg.InvalidOdataId(args, obj_standby_psu['@odata.id']))
        end
        odata_id = string.sub(obj_standby_psu['@odata.id'], #prefix_str + 1)
        if odata_id:match("^%d+$") == nil then
            error(custom_msg.InvalidOdataId(args, obj_standby_psu['@odata.id']))
        end
        expected_standby_psu_id_set[odata_id - '0' + 1] = true
        ::continue::
    end

    for _, v in ipairs(power_paths) do
        local obj = mdb.get_object(bus, v, 'bmc.kepler.Systems.PowerMgmt.OnePower')
        if not expected_standby_psu_id_set[obj.SlotNumber] then
            ret[#ret + 1] = obj.SlotNumber
        end
    end
    
    return ret
end

function m.set_power_limit_fail_action(fail_action)
    local MDB_SERVICE = 'bmc.kepler.power_strategy'
    local MDB_PATH = '/bmc/kepler/Systems/1/PowerStrategy'
    local MDB_INTERFACE = 'bmc.kepler.Systems.PowerStrategy'
    local err, _ = bus:pcall(MDB_SERVICE, MDB_PATH, MDB_INTERFACE, 'SetPowerLimitException', 'a{ss}s',
    context.get_context() or context.new(), fail_action)
    if err and err.name == "org.freedesktop.DBus.Error.UnknownMethod" then
        if fail_action == "LogEventOnly" then
            local err_info = base_messages.PropertyValueNotInList("LogEventOnly", "PowerControl/0/PowerLimit/LimitException")
            err_info.RelatedProperties = {'#/PowerControl/0/PowerLimit/LimitException'}
            error(err_info)
        end
        return false
    end
    if err then
        err.RelatedProperties = {'#/PowerControl/0/PowerLimit/LimitException'}
        error(err)
    end
    return true
end

function m.get_power_limit_fail_action()
    local MDB_SERVICE = 'bmc.kepler.power_strategy'
    local MDB_PATH = '/bmc/kepler/Systems/1/PowerStrategy'
    local MDB_INTERFACE = 'bmc.kepler.Systems.PowerStrategy'
    local err, fail_action = bus:pcall(MDB_SERVICE, MDB_PATH, MDB_INTERFACE, 'GetPowerLimitException', 'a{ss}',
    context.get_context() or context.new())
    if err and err.name == "org.freedesktop.DBus.Error.UnknownMethod" then
        return nil
    end
    if err then
        err.RelatedProperties = {'#/PowerControl/0/PowerLimit/LimitException'}
        error(err)
    end
    return fail_action
end

return m
