-- Copyright (c) 2025 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 log = require 'mc.logging'
local mdb = require 'mc.mdb'
local factory = require 'factory'
local client = require 'general_hardware.client'

local METRIC_NAME = {
    tacore = "cpu.tacore_voltage",
    ddrvdd = "cpu.ddrvdd_voltage",
    tbcore = "cpu.tbcore_voltage",
    uncore = "cpu.uncore_voltage",
    nadvdd = "cpu.nadvdd_voltage",
    nbdvdd = "cpu.nbdvdd_voltage",
    ddrvddq = "cpu.ddrvddq_voltage"
}

local metric_collect = {}
metric_collect.__index = metric_collect

local function get_cpu_obj_with_cpuid(id)
    local objs = client:GetCPUObjects()
    if not objs then
        return
    end
    for _, obj in pairs(objs) do
        -- CpuId匹配对象
        if id == obj.PhysicalId then
            return obj
        end
    end
end

local function get_processor_obj_with_cpu_path(path)
    local objs = client:GetSystemsProcessorObjects()
    if not objs then
        return
    end
    for _, obj in pairs(objs) do
        -- CpuId匹配对象
        if path == obj.path then
            return obj
        end
    end
end

local function get_vrdmgmt_obj(bus, path)
    local ok, vrdmgmt = pcall(mdb.get_object, bus, path, 'bmc.kepler.Systems.VrdMgmt')
    if not ok then
        return
    end
    return vrdmgmt
end

local function get_valid_voltage(bus, path, prop)
    local obj = get_vrdmgmt_obj(bus, path)
    if not obj then
        return {}
    end
    local val = obj[prop]
    if not val then
        val = 255 -- 获取不到数值返回无效值
    end
    return { tostring(val) }
end

local function is_bcu_position(pos)
    local unit_manager = factory.get_obj("unit_manager")
    for _, bcu_pos in pairs(unit_manager.cpu_board_position) do
        if bcu_pos == pos then
            return true
        end
    end
    return false
end

local metric_tab = {
    [METRIC_NAME.tacore] = 'Cpu0v9TACore',
    [METRIC_NAME.ddrvdd] = 'Cpu0v75DDRVDD',
    [METRIC_NAME.tbcore] = 'Cpu0v9TBCore',
    [METRIC_NAME.uncore] = 'Cpu0v9Uncore',
    [METRIC_NAME.nadvdd] = 'Cpu0v8NADVDD',
    [METRIC_NAME.nbdvdd] = 'Cpu0v8NBDVDD',
    [METRIC_NAME.ddrvddq] = 'Cpu1v1DDRVddq'
}

local function get_metric_date(bus, obj, name)
    if metric_tab[name] then
        return get_valid_voltage(bus, obj.path, metric_tab[name])
    end
    return {}
end

function metric_collect:get_cpu_voltage_items(bus, obj)
    if not is_bcu_position(obj.path:match('_(%d+)$')) then
        log:info('not bcu position, path: %s', obj.path)
        return '', {}, {}, {}
    end
    local vrd = get_vrdmgmt_obj(bus, obj.path)
    local sys_id = string.match(obj.path, '%d')
    if not vrd or not sys_id then
        log:info('vrd obj is invalid')
        return '', {}, {}, {}
    end

    local cpu = get_cpu_obj_with_cpuid(vrd.CpuId)
    if not cpu then
        log:info('cpu obj is invalid')
        return '', {}, {}, {}
    end

    local processor = get_processor_obj_with_cpu_path(cpu.path)
    if not processor or processor.Presence ~= 1 or processor.SN == '' then
        log:info('processor prop is invalid, Presence: %s', processor and processor.Presence)
        return '', {}, {}, {}
    end

    local classfication = {
        { PropName = 'Manufacturer', PropVal = processor.Manufacturer },
        { PropName = 'Version',     PropVal = processor.Model },
        { PropName = 'CoreCount',   PropVal = tostring(cpu.TotalCores) },
        { PropName = 'ThreadCount', PropVal = tostring(cpu.TotalThreads) },
        { PropName = 'MaxSpeed',    PropVal = tostring(cpu.MaxSpeedMHz) },
        { PropName = 'SystemId',    PropVal = sys_id },
    }

    local identification = {
        { PropName = 'SN',         PropVal = processor.SN },
        { PropName = 'DeviceName', PropVal = processor.Name },
        { PropName = 'ComNodeId',  PropVal = tostring(cpu.PhysicalId) }
    }

    local metric_name = {
        METRIC_NAME.tacore,
        METRIC_NAME.ddrvdd,
        METRIC_NAME.tbcore,
        METRIC_NAME.uncore,
        METRIC_NAME.nadvdd,
        METRIC_NAME.nbdvdd,
        METRIC_NAME.ddrvddq
    }

    return 'CPU', classfication, identification, metric_name
end

function metric_collect:get_cpu_voltage_data(bus, obj, metric_name)
    local tab = {}
    if not is_bcu_position(obj.path:match('_(%d+)$')) then
        log:info('not bcu position, path: %s', obj.path)
        return tab
    end

    for _, val in ipairs(metric_name) do
        local data = get_metric_date(bus, obj, val)
        if #data ~= 0 then
            table.insert(tab, {MetricName = val, Data = data})
        end
    end
    return tab
end

return metric_collect