-- 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 log = require 'mc.logging'
local utils_file = require 'utils.file'
local cjson = require 'cjson'
local context = require 'mc.context'

local CONTROLLER_INTERFACE = 'bmc.kepler.Systems.Storage.Controller'
local PROCESSOR_CPU_METRICS_MDB_INTF <const> = "bmc.kepler.Systems.Processor.ProcessorMetrics"

local m = {}

local function get_file_content(path)
    local file = utils_file.open_s(path, 'r')
    if not file then
        log:error('Open file failed')
        return nil
    end
    local content = file:read('*a')
    file:close()
    return content
end

local function get_bios_object(mdb, bus, systemid)
    local path = '/bmc/kepler/Systems/' .. tostring(systemid) .. '/Bios'
    local interface = 'bmc.kepler.Systems.Bios'
    local ok, bios_obj = pcall(function ()
        return mdb.get_object(bus, path, interface)
    end)
    if not ok then
        log:error('Get current value file failed, ret = %s', bios_obj)
        return nil
    end

    return bios_obj
end

function m.if_unknown_to_null(str)
    local unknown = {
        ['N/A'] = true,
        ['NA'] = true,
        ['Unknown'] = true,
    }
    if unknown[str] or str == nil or string.len(str) == 0 then
        return cjson.null
    end

    return str
end

function m.get_trust_module_self_test_result(health)
    if health == 255 then -- 无效数据
        return cjson.null
    end

    if health == 1 then -- Enable
        return 'OK'
    end

    return 'Critical'
end

function m.get_memory_summary_totalgib(members, bma_total)
    if bma_total and bma_total > 0 then
        return bma_total
    end
    local total_memory_mib = 0
    for i, path in ipairs(members) do
        local memory = mdb.get_object(bus, path, 'bmc.kepler.Systems.Memory')
        if memory.Presence then
            total_memory_mib = total_memory_mib + memory.CapacityMiB
        end
    end

    return total_memory_mib // 1024
end

function m.get_setting_attributes(systemid)
    local bios_obj = get_bios_object(mdb, bus, systemid)
    if bios_obj == nil then
        log:error('Get attributes failed')
        return nil
    end

    local content = get_file_content(bios_obj.SettingFileName)
    return cjson.decode(content)
end

local function get_pcie_card_list(members)
    local list = {}
    for _, path in ipairs(members) do
        local ok, obj = pcall(function ()
            return mdb.get_object(bus, path, 'bmc.kepler.Systems.PCIeDevices.PCIeCard')
        end)
        if obj and obj.FunctionClass and (obj.FunctionClass == 5 or obj.FunctionClass == 2 or
            obj.FunctionClass == 1 or obj.FunctionClass == 3 or obj.FunctionClass == 10 or
            obj.FunctionClass == 11) then
            list[#list + 1] = path
        elseif obj.FunctionClass == 9 then
            list[#list + 1] = '/Expand/NPUCard' .. path
        end
    end

    return list
end

function m.get_ubinterface_card_list(members)
    local list = {}
    for _, path in ipairs(members) do
        list[#list + 1] = path
    end

    return list
end

function m.get_smartNIC(members)
    local ok, obj
    for _, path in ipairs(members) do
        ok, obj = pcall(mdb.get_object, bus, path, 'bmc.kepler.Systems.PCIeDevices.PCIeCard')
        if ok and obj.FunctionClass and (obj.FunctionClass == 5 or obj.FunctionClass == 11) then
                return path
        end
    end
    return ''
end

function m.get_PCIeCards_list(members)
    return get_pcie_card_list(members)
end

function m.get_raid_cards(members)
    local raid_cards = {}
    for _, path in pairs(members) do
        local raid_obj = mdb.get_object(bus, path, CONTROLLER_INTERFACE)
        if raid_obj == nil or string.match(raid_obj.DeviceName, "(PCIe Card)%a*") then
            goto continue
        end
        ::continue::
    end

    return raid_cards
end

function m.get_ExtendCardInfo(NetworkAdapters, SlotID, pf_mac_info)
    local list = {}
    for _, path in ipairs(NetworkAdapters) do
        local obj = mdb.get_object(bus, path, 'bmc.kepler.Systems.NetworkAdapter')
        if obj.ParentCardSlotId == SlotID then
            local PfMacInfoArr = {}
            for index, item in ipairs(pf_mac_info) do
                PfMacInfoArr[index] = {
                    ["Port"] = item[1] + 1,
                    ["PfId"] = item[2],
                    ["PermanentMac"] = item[3]
                }
            end
            list[#list + 1] = {
                ["BoardName"] = obj.BoardName,
                ["ProductName"] = obj.Name,
                ["BoardId"] = obj.BoardID == 65535 and cjson.null or string.format('0x%04x', obj.BoardID),
                ["Slot"] = obj.SlotNumber,
                ["PfMacInfo"] = PfMacInfoArr,
                ["Model"] = obj.Model,
                ["PCBVersion"] = obj.PCBVersion,
                ["DeviceId"] = obj.DeviceID,
                ["VendorId"] = obj.VendorID,
                ["SubsystemId"] = obj.SubsystemDeviceID,
                ["SubsystemVendorId"] = obj.SubsystemVendorID,
                ["Manufacturer"] = obj.Manufacturer,
                ["ChipManufacturer"] = obj.ChipManufacturer,
                ["Description"] = obj.Description
            }
        end
    end
    return list
end

local CHASSIS_MDB_PATH<const> = '/bmc/kepler/Chassis'
local CHASSIS_MDB_INTF<const> = 'bmc.kepler.Chassis'

function m.get_chassis_device_specication()
    local res = {Memory = 0, PCIe = 0, Psu = 0, SecurityModule = 0, RAIDCard = 0, UBInterfaceCard = 0}
    local device_specication = {}
    local chassis_objs = mdb.get_sub_objects(bus, CHASSIS_MDB_PATH, CHASSIS_MDB_INTF)
    for _, chassis in pairs(chassis_objs) do
        if chassis.DeviceSpecication then
            device_specication = chassis.DeviceSpecication
        end
    end

    for _, v in ipairs(device_specication) do
        res[v[1]] = v[2]
    end
    return res
end

function m.get_RAIDCard_list(members)
    local raid_cards = {}
    for _, path in pairs(members) do
        local raid_obj = mdb.get_object(bus, path, CONTROLLER_INTERFACE)
        if raid_obj ~= nil and not string.match(raid_obj.DeviceName, "(PCIe Card)%a*") then
            raid_cards[#raid_cards + 1] = path
        end
    end
    return raid_cards
end

function m.get_history_from_jsonstr(jsonstr)
    return cjson.decode(jsonstr)
end

function m.get_cpu_usage_historys_from_jsonstr(input)
    local historys = {}
    local history
    local realpath
    local obj
    local systemId
    for _ , path in pairs(input) do
        history = {}
        realpath = path[1]
        systemId = tonumber(string.match(realpath, '/bmc/kepler/Systems/(%d+)'))
        obj = mdb.get_object(bus, realpath, PROCESSOR_CPU_METRICS_MDB_INTF)
        local ok, ret = pcall(function()
            return obj:GetBandwidthHistory(context.get_context())
        end)
        if ok then
            history = ret
        end
        history.SystemId = systemId
        table.insert(historys, history)
    end
    return historys
end

function m.get_boards(security_modules, pcie_cards, raid_cards, ocp_cards, lcns, ubinterfacecards)
    local boards = cjson.json_object_new_object()
    local chassis_device_specication = m.get_chassis_device_specication()
    if chassis_device_specication.SecurityModule > 0 and chassis_device_specication.SecurityModule ~= 0xff then
        boards.SecurityModule = cjson.json_object_new_object()
        boards.SecurityModule.CurrentNumber = #security_modules
        boards.SecurityModule.DetailLink = "/UI/Rest/System/Boards/SecurityModule"
        boards.SecurityModule.MaxNumber = chassis_device_specication.SecurityModule
    elseif #security_modules ~= 0 then
        boards.SecurityModule = cjson.json_object_new_object()
        boards.SecurityModule.CurrentNumber = #security_modules
        boards.SecurityModule.DetailLink = "/UI/Rest/System/Boards/SecurityModule"
        boards.SecurityModule.MaxNumber = nil
    end
    if chassis_device_specication.UBInterfaceCard > 0 and chassis_device_specication.UBInterfaceCard ~= 0xff then
        boards.UBInterfaceCard = cjson.json_object_new_object()
        boards.UBInterfaceCard.CurrentNumber = #ubinterfacecards
        boards.UBInterfaceCard.DetailLink = "/UI/Rest/System/Boards/UBInterfaceCard"
        boards.UBInterfaceCard.MaxNumber = chassis_device_specication.UBInterfaceCard
    elseif #ubinterfacecards ~= 0 then
        boards.UBInterfaceCard = cjson.json_object_new_object()
        boards.UBInterfaceCard.CurrentNumber = #ubinterfacecards
        boards.UBInterfaceCard.DetailLink = "/UI/Rest/System/Boards/UBInterfaceCard"
        boards.UBInterfaceCard.MaxNumber = nil
    end
    if chassis_device_specication.PCIe > 0 and chassis_device_specication.PCIe ~= 0xff then
        boards.PCIeCard = cjson.json_object_new_object()
        boards.PCIeCard.CurrentNumber = #get_pcie_card_list(pcie_cards)
        boards.PCIeCard.DetailLink = "/UI/Rest/System/Boards/PCIeCard"
        boards.PCIeCard.MaxNumber = chassis_device_specication.PCIe
    elseif #pcie_cards ~= 0 then
        boards.PCIeCard = cjson.json_object_new_object()
        boards.PCIeCard.CurrentNumber = #pcie_cards
        boards.PCIeCard.DetailLink = "/UI/Rest/System/Boards/PCIeCard"
        boards.PCIeCard.MaxNumber = nil
    end
    local raid_cards_notpcie = m.get_RAIDCard_list(raid_cards)
    if chassis_device_specication.RAIDCard > 0 and chassis_device_specication.RAIDCard ~= 0xff then
        boards.RAIDCard = cjson.json_object_new_object()
        boards.RAIDCard.CurrentNumber = #raid_cards_notpcie
        boards.RAIDCard.DetailLink = "/UI/Rest/System/Boards/RAIDCard"
        boards.RAIDCard.MaxNumber = chassis_device_specication.RAIDCard
    elseif #raid_cards_notpcie ~= 0 then
        boards.RAIDCard = cjson.json_object_new_object()
        boards.RAIDCard.CurrentNumber = #raid_cards_notpcie
        boards.RAIDCard.DetailLink = "/UI/Rest/System/Boards/RAIDCard"
        boards.RAIDCard.MaxNumber = nil
    end

    if ocp_cards and #ocp_cards ~= 0 then
        boards.OCPCard = cjson.json_object_new_object()
        boards.OCPCard.CurrentNumber = #ocp_cards
        boards.OCPCard.DetailLink = "/UI/Rest/System/Boards/OCPCard"
        boards.OCPCard.MaxNumber = 2
    end
    if lcns and #lcns ~= 0 then
        boards.LCNs = cjson.json_object_new_object()
        boards.LCNs.CurrentNumber = #lcns
        boards.LCNs.DetailLink = '/UI/Rest/System/Boards/LCN'
        boards.LCNs.MaxNumber = 1
    end

    return boards
end

function m.get_npu_card_power(npu_cards, slot_id)
    for _, path in ipairs(npu_cards) do
        local ok, obj = pcall(function ()
            return mdb.get_object(bus, path, 'bmc.kepler.Systems.NPUCard')
        end)
        if obj and slot_id == obj.SlotNumber and obj.PowerWatts then
            if obj.PowerWatts >= 16384 then
                return cjson.null
            else
                return obj.PowerWatts / 10
            end
        end
    end
    return cjson.null
end

function m.get_lcn_multi_logic(mulit_logics)
    local logic_list = cjson.json_object_new_array()
    for name, version in pairs(mulit_logics) do
        local cpld = cjson.json_object_new_object()
        cpld.Name = name
        cpld.Version = version
        logic_list[#logic_list + 1] = cpld
    end

    return logic_list
end

return m
