-- 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 class_mgnt = require 'mc.class_mgnt'
local c_service = require 'power_mgmt.service'
local psu_service = require 'psu_service'
local power_upgrade = require 'power_upgrade'
local power_supplies = require 'power_supplies_service'
local config_server = require 'config_mgmt.server'
local log = require 'mc.logging'
local cpower_mgmt = class(c_service)
local initiator = require 'mc.initiator'
local object_manage = require 'mc.mdb.object_manage'
local c_object_manage = require 'mc.orm.object_manage'
local signal = require 'signal'
local reboot_manage = require 'mc.mdb.micro_component.reboot'
local log_service = require 'log_service'
local system_power = require 'system_power'
local power_ipmi = require 'power_ipmi'
local metric_collect = require 'metric_collect'
local mc_admin = require 'mc.mc_admin'
local power_mgmt_bus = require 'power_mgmt_bus'
local utils_core = require 'utils.core'
local external_interface = require 'external_interface.handler'
local intf_debug = require 'mc.mdb.micro_component.debug'
local skynet = require 'skynet'
local utils = require 'power_mgmt_utils'
local mc_utils = require 'mc.utils'
local split = utils.split
local c_psu_object = require 'device.psu'
local debug_client_ok, debug_client = pcall(require, 'debug.client')
local lock_chip = require 'lock_chip'
local should_use_debug_service, debug_service = pcall(require, 'power_mgmt.debug.service')
local power_mgmt_ok, power_mgmt_mock = pcall(require, 'power_mgmt.lualib.power_mgmt_mock')
local power_mgmt = require 'device.power_mgmt'
local power_mgmt_metric = require 'power_mgmt_metric'
local capacitor_mgmt = require 'capacitor_mgmt'
local base_service = require 'power_mgmt.service'
local enums = require 'macros.power_mgmt_enums'
local custom_msg = require 'messages.custom'
local base_msg = require 'messages.base'
local circuit_mgmt = require 'circuit_mgmt'
local power_distribution_mgmt = require 'power_distribution_mgmt'
local circuit = require 'device.circuit'
local starter_ok, starter = pcall(require, 'mc.mem_collect.starter')

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

function cpower_mgmt:register_rpc_methods()
    self:ImplOnePowerOnePowerSetPowerWorkMode(function(obj, ctx, ...)
        return self:set_power_work_mode(ctx, obj, ...)
    end)
    self:ImplOnePowerStatusSetSleepMode(function(obj, ctx, ...)
        return self:set_power_sleep_mode(ctx, obj, ...)
    end)
    self:ImplOnePowerMetricGetData(function(obj, ctx, ...)
        return self.metric_collect:get_psu_data_collection_data(obj, ...)
    end)
    self:ImplOnePowerMetricGetItems(function(obj, ctx, ...)
        return self.metric_collect:get_psu_data_collection_items(obj, ...)
    end)
    self:ImplPowerSuppliesPowerSuppliesSetPsusFanMinPWM(function(obj, ctx, ...)
        return self.psu_service:set_psus_fan_min_pwm(...)
    end)
    self:ImplOnePowerCollectorGetFanSpeed(function(obj, ctx, ...)
        return utils.get_instance():get_psu_fan_rpm(c_psu_object, obj)
    end)
    self:ImplOnePowerCollectorGetRegisterValue(function(obj, ctx, cmd, length, ...)
        return utils.get_instance():get_psu_register_info(c_psu_object, obj, cmd, length)
    end)
    self:ImplOnePowerOnePowerReset(function(obj, ctx, ...)
        return self:set_power_reset_type(ctx, obj, ...)
    end)
    self:ImplOnePowerStatusSetAlarmLatch(function(obj, ctx, ...)
        return self:set_psu_alarm_latch(ctx, obj, ...)
    end)
    self:ImplOnePowerOnePowerSetPowerSupplyCircuit(function(obj, ctx, ...)
        return self:set_power_supply_circuit(ctx, obj, ...)
    end)
    self:ImplOnePowerCapacitorReset(function(obj, ctx, ...)
        return self:set_power_capacitor_enable(ctx, obj, ...)
    end)
    self:ImplOnePowerCapacitorSetVOUTDebounceMilliseconds(function(obj, ctx, ...)
        return self:set_vout_debounce_milliseconds(ctx, obj, ...)
    end)
    self:ImplOnePowerCapacitorSetDepthOfDischargeVolts(function(obj, ctx, ...)
        return self:set_depth_of_discharge_volts(ctx, obj, ...)
    end)
    self:ImplOnePowerOutputControlSetOutputPowerLimitWatts(function(obj, ctx, ...)
        return self:set_output_limit_point_watts(ctx, obj, ...)
    end)
    self:ImplPowerDistributionIdTransferConfigTransferControl(function(obj, ctx, ...)
        return self:set_power_distribution_transfer_control(ctx, obj, ...)
    end)
    self:ImplPowerDistributionIdPowerDistributionMetricsResetMetrics(function(obj, ctx, ...)
        return self:reset_power_distribution_metrics(ctx, obj, ...)
    end)
    self:ImplOnePowerPowerSupplyMetricsReset(function(obj, ctx, ...)
        return self.metric_collect:reset_psu_metrics(ctx, obj, ...)
    end)
end

function cpower_mgmt:create_circuit_object(class_name, csr_obj, position)
    self.circuit_mgmt:object_add_callback(class_name, base_service:CreateCircuit(1, csr_obj.Id, function(object)
        object.Id = csr_obj.Id
        object.CircuitType = csr_obj.CircuitType
        object.ObjectName = csr_obj.ObjectName
    end), position)
end

function cpower_mgmt:create_pdu_object()
    local software_type = utils.get_instance():get_chassis_type()
    if software_type == enums.SOFTWARE_TYPE.CABINET then
        self.power_distribution_mgmt:object_add_callback(base_service:CreatePowerDistributionId(1, function(object)
            object.EquipmentType = "PowerShelf"
            object.ObjectName = "PowerShelf1"
        end))
    end
end

function cpower_mgmt:on_add_object(class_name, object, position)
    (({
        ['OnePower'] = function()
            self.psu_service:add_psu_object(class_name, object)
            self:create_debug_service(object)
        end,
        ['PsuSlot'] = function()
            self.psu_service:add_psu_object(class_name, object)
        end,
        ['DftPowerSupply'] = function()
            object.Slot = tonumber(string.sub(position, -2)) -- 截取position后两位做为slot
        end,
        ['DftCanChannel'] = function()
        end,
        ['PowerConfiguration'] = function()
            self.psu_service:add_psu_object(class_name, object)
        end,
        ['Circuit'] = function()
            self:create_circuit_object(class_name, object, position)
        end
    })[class_name] or function() end)()
end

function cpower_mgmt:on_delete_object(class_name, object, position)
    (({
        ['OnePower'] = function()
            self:delete_debug_service(class_name, object)
        end,
        ['PsuSlot'] = function()
            self.psu_service:delete_psu_slot_object(class_name, object)
        end
    })[class_name] or function() end)()
end

function cpower_mgmt:change_db_journal_mode()
    local ok, err = pcall(function()
        -- qemu 虚拟文件系统无法应用wal模式
        if not mc_utils.is_qemu or mc_utils.is_qemu() then
            return
        end
        -- 切换数据库工作模式, 1048576： 单位字节, 设置为1MB
        self.local_db:exec('PRAGMA journal_mode=WAL; PRAGMA journal_size_limit=1048576;')
    end)
    if not ok then
        log:warn('change db journal mode failed, err: %s', err)
    end
end

function cpower_mgmt:ctor()
    utils.new()
    self.debug_service_enable = false
    self.debug_service_list = {}
    -- 类初始化
    self.system_power = system_power.new(self.local_db)
    self.psu_service = psu_service.get_instance(self.bus)
    self.log_service = log_service.new(self.bus, self.db)
    self.metric_collect = metric_collect.new(self.bus)
    self.power_mgmt_bus = power_mgmt_bus.new(self.bus)
    self.power_mgmt = power_mgmt.new()
    self.power_supplies = power_supplies.new(self.db)
    self.power_mgmt_metric = power_mgmt_metric.new()
    self.capacitor_mgmt = capacitor_mgmt.new()
    self.power_distribution_mgmt = power_distribution_mgmt.new()
    self.circuit_mgmt = circuit_mgmt.new()
    self.power_mgmt:set_obj(base_service:CreatePowerMgmt(1, function(object)
        object.ObjectName = 'PowerMgmt_0'
        object.IsManufacturerMismatch = false
        object.IsModelMismatch = false
    end))
end

function cpower_mgmt:check_dependencies()
    local admin = mc_admin.new()
    admin:parse_dependency(utils_core.getcwd() .. '/mds/service.json')
    admin:check_dependency(self.bus)
end

function cpower_mgmt:init()
    log:debug('===== power service init =======')
    self:change_db_journal_mode()
    skynet.fork(function()
        self.object_manage = c_object_manage.new(self.db, self.bus, true)
        self.object_manage:start()
        -- 注册对象响应回调函数
        -- 添加对象回调
        object_manage.on_add_object(self.bus, function(class_name, object, position)
            self:on_add_object(class_name, object, position)
            log:notice('[power_mgmt] add object, class_name: %s, position: %s', class_name, position)
        end)
        -- 删除对象回调
        object_manage.on_delete_object(self.bus, function(class_name, object, position)
            self:on_delete_object(class_name, object, position)
        end)
        -- 添加对象完成回调
        object_manage.on_add_object_complete(self.bus, function(position)
            log:notice('[power_mgmt] add object complete, position: %s', position)
        end)
        -- 删除对象完成回调
        object_manage.on_delete_object_complete(self.bus, function(position)
        end)
        self.power_upgrade = power_upgrade.new()
        self.external_interface = external_interface.get_instance(self.bus)
        lock_chip.new()
        cpower_mgmt.super.init(self)
        self:init_debug_service(self.bus)
        self:check_dependencies()
        signal.init(self.db)
        self.psu_service:init()
        self.log_service:init()
        self:register_reboot_methods()
        self:register_rpc_methods()
        power_ipmi.register(function(ipmi_req, local_func)
            self:register_ipmi_cmd(ipmi_req, local_func)
        end)
        intf_debug.on_dump(function(...)
            self.log_service:dump_log(...)
            self.capacitor_mgmt:dump_log()
        end)
        config_server.new(self.db, self.bus)
        if starter_ok and debug_client_ok then
            -- 拉起malloc_info采集工具，每个进程只能一个组件拉起一次
            local starter_obj = starter.new(debug_client, debug_client.GetPerformanceObjects,
                debug_client.OnPerformancePropertiesChanged)
            pcall(starter_obj.start, starter_obj, 'energy')
        end

        if self.debug_service_enable then
            self:register_debug_rpc_methods()
        end
        self:create_pdu_object()
        -- 创建PowerSupplies资源树,当前用于psu风扇转速下发
        self:CreatePowerSupplies(1)
        self:main()
    end)
end

function cpower_mgmt:init_debug_service(bus)
    if should_use_debug_service == true and power_mgmt_ok == true then
        power_mgmt_mock.new(bus)
        self.power_debug_service_class = class(debug_service)
        self.power_mgmt_debug_service = self.power_debug_service_class.new(bus)
        self.debug_service_enable = true
        return
    end
    self.debug_service_enable = false
end

local function get_debug_object_name(psu_object_name)
    local obj_name = split(psu_object_name, '_')
    obj_name[1] = obj_name[1] .. 'Debug'
    return table.concat(obj_name, '_')
end

function cpower_mgmt:create_debug_service(psu_object)
    if not self.debug_service_enable then
        return
    end

    local name = get_debug_object_name(psu_object.ObjectName)
    local obj = self.power_mgmt_debug_service:CreateOnePowerDebug(1, name, function(object)
        object.ObjectName = name
    end)
    self.debug_service_list[name] = obj
    log:notice('[power_mgmt] create debug service, debug_name: %s', name)
end

function cpower_mgmt:delete_debug_service(class_name, psu_object)
    if not self.debug_service_enable then
        return
    end

    local name = get_debug_object_name(psu_object.ObjectName)
    local debug_obj = self.debug_service_list[name]
    if debug_obj then
        class_mgnt(class_name):remove(debug_obj)
        self.debug_service_list[name] = nil
        log:notice('[power_mgmt] delete debug service, debug_name: %s', name)
    end
end

function cpower_mgmt:register_debug_rpc_methods(bus)
    function self.power_debug_service_class:implement_methods()
        self:ImplOnePowerDebugBlackBoxDumpPsuBlackbox(function(obj, ctx, ...)
            return utils.get_instance():dump_psu_blackbox(c_psu_object, obj, power_mgmt_mock)
        end)
    end

    self.power_mgmt_debug_service:implement_methods()
end

function cpower_mgmt:main()
    skynet.fork(
        function()
            -- 如果组件异常重启，启动后直接置位未初始化完成
            if self:get_bmc_reset_type() == 3 then
                self.external_interface:set_init_ok_flag(true)
                return
            end
            -- 启动120s后，cpu开始可以常驻线程访问
            skynet.sleep(12000)
            self.external_interface:set_init_ok_flag(true)
            log:notice("[power_mgmt] init OK")
        end
    )
end

-- 获取组件重启的原因
function cpower_mgmt:get_bmc_reset_type()
    return 0
end

function cpower_mgmt:on_reboot_prepare()
    log:notice("[power_mgmt] reboot prepare, stop psm monitor")
    self.psu_service:psm_monitor_stop()
end

function cpower_mgmt:on_reboot_cancel()
    log:notice("[power_mgmt] reboot cancel, restart psm monitor")
    self.psu_service:psm_monitor_start()
end

function cpower_mgmt:on_reboot_action()
    log:notice("[power_mgmt] reboot action")
end

function cpower_mgmt:register_reboot_methods()
    -- 注册平滑重启回调函数
    -- Prepare准备重启回调
    reboot_manage.on_prepare(function()
        self:on_reboot_prepare()
        return 0
    end)
    -- Action执行重启回调
    reboot_manage.on_action(function()
        self:on_reboot_action()
        return 0
    end)
    -- Cancel取消重启回调
    reboot_manage.on_cancel(function()
        self:on_reboot_cancel()
    end)
end

local function log_operation(ctx, fmt, ...)
    local intf = 'N/A'
    local user_name = 'N/A'
    local client_addr = 'N/A'
    if ctx then
        intf = ctx.Interface and ctx.Interface or 'N/A'
        user_name = ctx.UserName and ctx.UserName or 'N/A'
        client_addr = ctx.ClientAddr and ctx.ClientAddr or 'N/A'
    end

    log:operation(initiator.new(intf, user_name, client_addr), 'power_mgmt', fmt, ...)
end

function cpower_mgmt:set_power_work_mode(ctx, obj, work_mode)
    local psu_obj = c_psu_object.collection:find(function(object)
        return object.path == obj.path
    end)
    log:maintenance(log.MLOG_INFO, log.FC__PUBLIC_OK, 'set ps%d mode %s',
        psu_obj.ps_id, work_mode == 0 and 'active' or 'standby')
    if self.psu_service:set_power_work_mode(psu_obj.ps_id, work_mode) == E_OK then
        log:maintenance(log.MLOG_INFO, log.FC__PUBLIC_OK, 'set ps%d mode %s success',
            psu_obj.ps_id, work_mode == 0 and 'active' or 'standby')
        return 0 -- 成功，返回0
    end
    log:maintenance(log.MLOG_INFO, log.FC__PUBLIC_OK, 'set ps%d mode %s failed',
        psu_obj.ps_id, work_mode == 0 and 'active' or 'standby')
    return 1 -- 失败，返回1
end

function cpower_mgmt:set_power_sleep_mode(ctx, obj, sleep_mode)
    local psu_obj = c_psu_object.collection:find(function(object)
        return object.path == obj.path
    end)
    log:maintenance(log.MLOG_INFO, log.FC__PUBLIC_OK, 'set ps%d sleep mode %s', psu_obj.ps_id, sleep_mode)
    local ret = self.psu_service:set_power_sleep_mode(psu_obj.ps_id, sleep_mode)
    if ret == E_OK then
        log:maintenance(log.MLOG_INFO, log.FC__PUBLIC_OK, 'set ps%d sleep mode %s success', psu_obj.ps_id, sleep_mode)
        log_operation(ctx, 'Set ps%s sleep mode to %s successfully', psu_obj.ps_id, sleep_mode)
        return 0 -- 成功，返回0
    end
    log:maintenance(log.MLOG_INFO, log.FC__PUBLIC_OK, 'set slot%d sleep mode %s failed', psu_obj.ps_id, sleep_mode)
    log_operation(ctx, 'Set ps%s sleep mode failed', psu_obj.ps_id)
    if ret == enums.SLEEP_MODE_RELATED_ERROR.OPERATION_FAILED then
        error(custom_msg.OperationFailed())
    elseif ret == enums.SLEEP_MODE_RELATED_ERROR.ACTION_NOT_SUPPORTED then
        error(base_msg.ActionNotSupported('%SetSleepMode'))
    elseif ret == enums.SLEEP_MODE_RELATED_ERROR.PROPERTY_VALUE_OUT_OF_RANGE then
        error(custom_msg.PropertyValueOutOfRange(sleep_mode, "%SleepMode"))
    end
end

function cpower_mgmt:set_power_reset_type(ctx, obj, reset_type)
    if reset_type ~= "On" and reset_type ~= "ForceOff" and reset_type ~= "ForceRestart" then
        error(base_msg.PropertyValueNotInList(reset_type, "%ResetType"))
    end
    local err_info = self.psu_service:set_power_reset_type(obj.SlotNumber, reset_type)
    if err_info then
        log_operation(ctx, 'Set psu%s reset failed', obj.SlotNumber)
        error(err_info)
    end
    log_operation(ctx, 'Set psu%s reset(reset type: %s) successfully', obj.SlotNumber, reset_type)
end

function cpower_mgmt:set_psu_alarm_latch(ctx, obj, switch_status)
    local psu_obj = c_psu_object.collection:find(function(object)
        return object.path == obj.path
    end)
    local err_info = self.psu_service:set_psu_alarm_latch(psu_obj.SlotNumber, switch_status)
    if err_info then
        log_operation(ctx, '%s power supply %s alarm latch failed', switch_status == 1 and 'Enable' or
            'Disable', psu_obj.SlotNumber)
        error(err_info)
    end
    log_operation(ctx, '%s power supply %s alarm latch successfully', switch_status == 1 and 'Enable' or
        'Disable', psu_obj.SlotNumber)
end

function cpower_mgmt:set_power_supply_circuit(ctx, obj, circuit)
    local err_info = self.psu_service:set_power_supply_circuit(obj.SlotNumber, circuit)
    if err_info then
        error(err_info)
    end
end

function cpower_mgmt:set_power_capacitor_enable(ctx, obj, enable)
    local psu_obj = c_psu_object.collection:find(function(object)
        return object.path == obj.path
    end)
    local err_info = self.psu_service:set_power_capacitor_enable(psu_obj.SlotNumber, enable)
    if err_info then
        log_operation(ctx, 'Set power supply %s capacitor state to %s failed',
            psu_obj.SlotNumber, enable)
        error(err_info)
    end
    log_operation(ctx, 'Set power supply %s capacitor state to %s successfully',
        psu_obj.SlotNumber, enable)
end

function cpower_mgmt:set_vout_debounce_milliseconds(ctx, obj, debounce_milliseconds)
    local psu_obj = c_psu_object.collection:find(function(object)
        return object.path == obj.path
    end)
    local err_info = self.psu_service:set_vout_debounce_milliseconds(psu_obj.SlotNumber, debounce_milliseconds)
    if err_info then
        log_operation(ctx, 'Set power supply %s output voltage debounce millisecond to %s failed',
            psu_obj.SlotNumber, debounce_milliseconds)
        error(err_info)
    end
    log_operation(ctx, 'Set power supply %s output voltage debounce millisecond to %s successfully',
        psu_obj.SlotNumber, debounce_milliseconds)
end

function cpower_mgmt:set_output_limit_point_watts(ctx, obj, output_power_limit_watts)
    local psu_obj = c_psu_object.collection:find(function(object)
        return object.path == obj.path
    end)
    local err_info = self.psu_service:set_output_limit_point_watts(psu_obj.SlotNumber, output_power_limit_watts)
    if err_info then
        log_operation(ctx, 'Set power supply %s output limit watts to %s failed',
            psu_obj.SlotNumber, output_power_limit_watts)
        error(err_info)
    end
    log_operation(ctx, 'Set power supply %s output limit watts to %s successfully',
        psu_obj.SlotNumber, output_power_limit_watts)
end

function cpower_mgmt:set_depth_of_discharge_volts(ctx, obj, depth_of_discharge_volts)
    local psu_obj = c_psu_object.collection:find(function(object)
        return object.path == obj.path
    end)
    local err_info = self.psu_service:set_depth_of_discharge_volts(psu_obj.SlotNumber, depth_of_discharge_volts)
    if err_info then
        log_operation(ctx, 'Set power supply %s depth of discharge to %s failed',
            psu_obj.SlotNumber, depth_of_discharge_volts)
        error(err_info)
    end
    log_operation(ctx, 'Set power supply %s depth of discharge to %s successfully',
        psu_obj.SlotNumber, depth_of_discharge_volts)
end

function cpower_mgmt:set_power_distribution_transfer_control(ctx, obj)
    if not obj.ActiveMainsId or obj.ActiveMainsId == "" or (obj.ActiveMainsId ~= "0" and obj.ActiveMainsId ~= "1") then
        log:error("Invalid ActiveMainsId value:%s", obj.ActiveMainsId)
        return
    end
    local ActiveMainsId = 1 - tonumber(obj.ActiveMainsId)
    c_psu_object.collection:fold(function(_, psu_obj)
        if psu_obj and psu_obj.set_power_supply_circuit then
            local err_info = psu_obj:set_power_supply_circuit(ActiveMainsId)
            if err_info then
                log_operation(ctx, 'Set power distribution transfer control failed')
                error(err_info)
            end
        end
    end)
    log_operation(ctx, 'Set power distribution transfer control to mains %s successfully', ActiveMainsId)
end

function cpower_mgmt:reset_power_distribution_metrics(ctx, obj)
    if obj then
        obj.EnergykWh = 0
        local current_time = mc_utils.time()
        local time = mc_utils.date_format(current_time.tv_sec, '%Y-%m-%dT%H:%M:%SZ', false) -- 提取 tv_sec
        obj.EnergySensorResetTime = time
    end
end

return cpower_mgmt
