-- 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 c_object = require 'mc.orm.object'
local c_object_manage = require 'mc.orm.object_manage'
local log = require 'mc.logging'
local ctx = require 'mc.context'
local client = require 'power_mgmt.client'
local psu_def = require 'macros.psu_def'
local utils = require 'power_mgmt_utils'
local c_psu_slot_object = require 'device.psu_slot'
local system_power = require 'system_power'
local mdb = require 'mc.mdb'
local FWINVENTORY_PATH = '/bmc/kepler/UpdateService/FirmwareInventory/'
local FWINFO_INTERFACE = 'bmc.kepler.UpdateService.FirmwareInfo'

local E_OK <const> = nil -- 函数执行成功返回nil
local E_FAILED <const> = '' -- 空错误信息

local INPUT_ERROR_BIT <const> = 6

local psu_manager = c_object('OnePower')

function psu_manager:ctor()
    self.ps_id = 0
    self.psu_slot = c_psu_slot_object.collection:find({SlotNumber = self.SlotNumber})
    self.firmware_id = ''
    self.system_power_instance = system_power.get_instance()
end

function psu_manager:dtor()
    self:unmount_fw_info()
end

function psu_manager:unmount_fw_info()
    local ok, err = client:FirmwareInventoryFirmwareInventoryDelete(
        ctx.new(),
        self.firmware_id
    )
    if not ok then
        log:error('unmount PSU%d firmware failed, err = %s', self.ps_id, err)
    else
        log:notice('unmount PSU%d firmware successfully', self.ps_id)
    end
end

function psu_manager:register_fw_info()
    -- 前向兼容，电源砖固件资源路径与普通电源保持一致
    local device_locator = string.gsub(self.DeviceLocator, "PowerConverter", "PSU")
    self.firmware_id = string.format('%s_%s', self.Position, device_locator)
    local ok, err = client:PFirmwareInventoryFirmwareInventoryAdd(
        ctx.new(),
        {
            Id = self.firmware_id,
            Name = self.DeviceLocator, Version = self.FirmwareVersion or '',
            BuildNum = '', ReleaseDate = '', LowestSupportedVersion = '',
            SoftwareId = 'PSU-' .. self.Model, Manufacturer = self.Manufacturer,
            Location = self.Position, State = 'Enabled', Severity = 'Informational'
        },
        true, 1, 10 -- 电源固件包当前最大10MB
    )
    if not ok then
        log:error('register PSU%d firmware failed, err = %s', self.ps_id, err)
    else
        log:notice('register PSU%d firmware successfully', self.ps_id)
    end
end

function psu_manager:update_firmware_info()
    local path = FWINVENTORY_PATH .. self.firmware_id
    local bus = c_object_manage.get_instance().bus
    local ok, obj = pcall(mdb.get_cached_object, bus, path, FWINFO_INTERFACE)
    if not ok then
        log:error('[PowerUpgrade] get firmware object fail, path: %s, error: %s', path, obj)
        return
    end
    if obj.Version ~= self.FirmwareVersion then
        obj.Version = self.FirmwareVersion
        log:notice('[PowerUpgrade] updated firmware version %s to FirmwareInventory, id: %s',
            obj.Version, self.firmware_id)
    end
end

function psu_manager:get_black_box_data()
    local ok, black_box_max_length, black_box_data = pcall(function ()
        return self.BlackBox.methods.GetBlackBoxData()
    end)
    if not ok then
        log:error('get_black_box_data failed, ps%s', self.ps_id)
        return E_FAILED, black_box_max_length
    end
    return E_OK, black_box_max_length, black_box_data
end

function psu_manager:set_work_mode(work_mode)
    local ok, resp = pcall(function ()
        self.PowerMode.methods.SetWorkMode(work_mode)
    end)
    if not ok then
        log:error('set ps%u work_mode to %s failed: %s', self.ps_id, work_mode, resp)
        return E_FAILED
    end
    return E_OK
end

function psu_manager:set_sleep_mode(sleep_mode)
    local ok, resp = pcall(function ()
        return self.PowerMode.methods.SetSleepMode(sleep_mode)
    end)
    if not ok then
        log:error('set ps%u sleep_mode to %s failed: %s', self.ps_id, sleep_mode, resp)
        return E_FAILED
    end
    return E_OK
end

function psu_manager:set_psu_fan_rpm(rpm)
    local ok, resp = pcall(function ()
        self.Cooling.methods.SetFanRPM(rpm)
    end)
    if not ok then
        log:error('set ps%u psu_fan_rpm to %s failed: %s', self.ps_id, rpm, resp)
        return E_FAILED
    end
    return E_OK
end

function psu_manager:get(prop, ...)
    local ok, resp = pcall(self.psu_slot.get, self.psu_slot, prop, ...)
    if not ok then
        return nil, string.format('get ps%u %s failed: %s', self.ps_id, prop, resp)
    end
    return resp, E_OK
end

function psu_manager:is_healthy()
    return self.Health == psu_def.IS_HEALTH and self.OutputState ~= 0 and self.AlarmStatus & INPUT_ERROR_BIT == 0
end

function psu_manager:power_upgrade(upgrade_path, upgrade_process)
    local ok, resp = pcall(function()
        return self.Upgrade.methods.Upgrade(upgrade_path, upgrade_process)
    end)
    self:next_tick(function()
        self:update_firmware_info()
    end)
    -- 电源升级完成后刷新一次电子标签
    self:next_tick(function()
        local config_value = self.system_power_instance:get_psu_fru_config()
        self.Power.methods.GetFruData(config_value)
    end)
    if ok then
        return resp
    end
    log:error('ps%d upgrade failed, resp = %s', self.ps_id, resp)
    return E_FAILED
end

function psu_manager:psm_monitor_stop()
    local ok, resp = pcall(function()
        return self.Power.methods.PowerAccessControl(false)
    end)
    if not ok then
        log:error('ps%d contorl stop failed, ret: %s', self.ps_id, resp)
        return resp
    end
    return E_OK
end

function psu_manager:psm_monitor_start()
    local ok, resp = pcall(function()
        return self.Power.methods.PowerAccessControl(true)
    end)
    if not ok then
        log:error('ps%d contorl start failed, ret: %s', self.ps_id, resp)
        return resp
    end
    return E_OK
end

function psu_manager.create_mdb_object()
end

function psu_manager:init()
    self.ps_id = self.SlotNumber
    self:connect_signal(self.on_add_object_complete, function()
        self:next_tick(function()
            self:register_fw_info()
        end)
        -- 检测到OnePower对象分发时要刷新一次电子标签
        self:next_tick(function()
            local config_value = self.system_power_instance:get_psu_fru_config()
            self.Power.methods.GetFruData(config_value)
        end)
    end)
    psu_manager.super.init(self)
end

return psu_manager