-- 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 lu = require 'luaunit'
local device_mgmt = require 'device_mgmt.device_mgmt'
local gpu_mgmt = require 'device_mgmt.gpu_mgmt'
local log = require 'mc.logging'
local org_freedesktop_dbus = require 'sd_bus.org_freedesktop_dbus'
local match_rule = org_freedesktop_dbus.MatchRule
local mdb = require 'mc.mdb'
local comm_fun = require 'device_mgmt.comm_fun'
local gpu_service = require 'gpu_service.gpu_service'

TestGpuMgmtObj = {}

local mds_obj = {
}

function TestGpuMgmtObj:setupClass()
end

function TestGpuMgmtObj:teardownClass()
end

function TestGpuMgmtObj:test_device_mgmt()
    local gpu_mgmt_bak = gpu_mgmt
    gpu_mgmt.new = function()
        log:raise("gpu init error")
    end
    local ok, ret = pcall(function()
        local device_mgmt = device_mgmt.new({})
    end)
    lu.assertEquals(ok, true)
    lu.assertEquals(ret, nil)

    gpu_mgmt = gpu_mgmt_bak
end

function TestGpuMgmtObj:test_listen_device_obj_property_change()
    local resource_obj = {}
    -- message from Processor interface with PowerWatts
    local msg_processor = {
        read = function()
            return 'bmc.dev.Processor.Metrics', {
                PowerWatts = {
                    value = function()
                        return 100
                    end
                }
            }
        end
    }
    -- message from Gpu.Power interface with power related flags
    local msg_gpu_power = {
        read = function()
            return 'bmc.dev.Gpu.Power', {
                PowerBrakeState = {
                    value = function()
                        return 1
                    end
                },
                ExternalPowerSufficient = {
                    value = function()
                        return 0
                    end
                }
            }
        end
    }
    local bus = {
        match = function(self, property_changed_sig, cb)
            cb(msg_processor)
            cb(msg_gpu_power)
            return 'sig'
        end
    }
    local c_signal = match_rule.signal
    match_rule.signal = function()
        return {
            with_path = function() end
        }
    end
    local sig_slot = {}
    local ok = pcall(gpu_mgmt.listen_device_obj_property_change, gpu_mgmt, bus, 
                    sig_slot, nil, resource_obj)
    match_rule.signal = c_signal
    lu.assertEquals(ok, true)
    lu.assertEquals(sig_slot[1], 'sig')
    lu.assertEquals(resource_obj.PowerWatts, 100)
    lu.assertEquals(resource_obj.PowerBrakeSet, 1)
    lu.assertEquals(resource_obj.ExternalPowerSufficient, 0)
end

function TestGpuMgmtObj:test_synchronize_property()
    local resource_obj = {}
    local bus = {
        call = function(obj, service)
            if service == 'bmc.kepler.maca' then
                return {
                    test_service = {
                        'bmc.dev.Processor.Metrics'
                    }
                }
            end
            if service == 'test_service' then
                return {
                    PowerWatts = 0
                }
            end 
        end
    }
    local c_register_interface = mdb.register_interface
    mdb.register_interface = function()
        return true
    end
    local c_get_object = mdb.get_object
    mdb.get_object = function()
        return {
            get_property = function()
                return 0, 100
            end
        }
    end
    local ok = pcall(gpu_mgmt.synchronize_property, gpu_mgmt, bus, nil, resource_obj)
    mdb.register_interface = c_register_interface
    mdb.get_object = c_get_object
    lu.assertEquals(ok, true)
    lu.assertEquals(resource_obj.PowerWatts, 100)
end

function TestGpuMgmtObj:test_sync_dev_prop_to_resource_obj()
    local resource_obj = {}
    local interface = 'bmc.dev.PCIeDevice.PCIeFunction'
    local prop = 'VendorId'
    local value = '1'
    pcall(gpu_mgmt.sync_dev_prop_to_resource_obj, gpu_mgmt, resource_obj, interface, prop, value)
    prop = 'DeviceId'
    value = '2'
    pcall(gpu_mgmt.sync_dev_prop_to_resource_obj, gpu_mgmt, resource_obj, interface, prop, value)
    prop = 'SubSystemVendorId'
    value = '3'
    pcall(gpu_mgmt.sync_dev_prop_to_resource_obj, gpu_mgmt, resource_obj, interface, prop, value)
    prop = 'SubSystemDeviceId'
    value = '4'
    pcall(gpu_mgmt.sync_dev_prop_to_resource_obj, gpu_mgmt, resource_obj, interface, prop, value)
    interface = 'bmc.dev.PCIeDevice'
    prop = 'Slot'
    value = 5
    pcall(gpu_mgmt.sync_dev_prop_to_resource_obj, gpu_mgmt, resource_obj, interface, prop, value)
    interface = 'bmc.dev.Processor'
    prop = 'Manufacturer'
    value = 'ManufacturerTest'
    pcall(gpu_mgmt.sync_dev_prop_to_resource_obj, gpu_mgmt, resource_obj, interface, prop, value)
    prop = 'Model'
    value = 'ModelTest'
    pcall(gpu_mgmt.sync_dev_prop_to_resource_obj, gpu_mgmt, resource_obj, interface, prop, value)
    lu.assertEquals(resource_obj.VendorID, 1)
    lu.assertEquals(resource_obj.DeviceID, 2)
    lu.assertEquals(resource_obj.SubVendorID, 3)
    lu.assertEquals(resource_obj.SubDeviceID, 4)
    lu.assertEquals(resource_obj.Manufacturer, 'ManufacturerTest')
    lu.assertEquals(resource_obj.InventoryManufacturer, 'ManufacturerTest')
    lu.assertEquals(resource_obj.Model, 'ModelTest')
    lu.assertEquals(resource_obj.InventoryModel, 'ModelTest')
end

function TestGpuMgmtObj:test_create_resource_obj()
    local bus = {
        call = function()
            return {
                {
                    'bmc.kepler.maca',
                    'bmc.kepler.general_hardware'
                }
            }
        end
    }
    local c_register_interface = mdb.register_interface
    mdb.register_interface = function()
        return true
    end
    local c_get_object = mdb.get_object
    mdb.get_object = function()
        return {
            get_property = function()
                return 0, 'GPU_1'
            end,
        }
    end
    local obj = {}
    local c_app = gpu_mgmt.app
    gpu_mgmt.app = {
        CreateGPU = function(self, num, obj_name, cb)
            cb(obj)
            return true
        end
    }

    local get_device_obj_by_path = comm_fun.get_device_obj_by_path
    comm_fun.get_device_obj_by_path = function ()
        local pcie_obj = {
            ['FirmwareVersion'] = '1.0',
            ['PartNumber'] = '0123'
        }
        local dev_obj = {
            get_property = function (_, name)
                return 0, pcie_obj[name]
            end
        }
        return dev_obj
    end

    local ok, _, object_position = pcall(gpu_mgmt.create_resource_obj, gpu_mgmt, bus)
    mdb.register_interface = c_register_interface
    mdb.get_object = c_get_object
    gpu_mgmt.app = c_app
    lu.assertEquals(ok, true)
    lu.assertEquals(obj.ObjectName, 'GPU_1')
    lu.assertEquals(obj.CardPartNumber, '0123')
    lu.assertEquals(obj.CardFirmwareVersion, '1.0')
    lu.assertEquals(object_position, '1')

    comm_fun.get_device_obj_by_path = get_device_obj_by_path
end

function TestGpuMgmtObj:test_on_del_device_obj()
    local del_signal_bak = comm_fun.set_interface_del_signal
    gpu_mgmt.objects = {}
    comm_fun.set_interface_del_signal = function(_, _, _, _, cb)
        cb('device_path')
    end
    gpu_mgmt:on_del_device_obj()
    gpu_mgmt.objects = {
        device_path = {
            ObjectIdentifier = {1,2,3,4},
            ObjectName = 'ObjectName'
        }
    }
    gpu_mgmt:on_del_device_obj()
    lu.assertEquals(#gpu_mgmt.objects['device_path'], 0)
    comm_fun.set_interface_del_signal = del_signal_bak
end

function TestGpuMgmtObj:test_sync_all_resource_prop_to_dev_obj()
    local gpu_obj = {
        Utilization = 0
    }
    local memory_metrics_obj = {
        MemoryUsagePercent = 0
    }
    local get_device_obj_by_path_mock = comm_fun.get_device_obj_by_path
    comm_fun.get_device_obj_by_path = function(_, path, interfaces)
        local _, interface = next(interfaces)

        if interface == 'bmc.dev.Gpu' then
            return gpu_obj
        elseif interface == 'bmc.dev.Memory.Metrics' then
            return memory_metrics_obj
        end
    end

    local resource_obj = {
        MemoryUtilization = 5,
        GPUUtilization = 6
    }

    gpu_mgmt:sync_all_resource_prop_to_dev_obj(resource_obj, '')

    lu.assertEquals(gpu_obj.Utilization, 6)
    lu.assertEquals(memory_metrics_obj.MemoryUsagePercent, 5)

    comm_fun.get_device_obj_by_path = get_device_obj_by_path_mock
end

function TestGpuMgmtObj:test_on_dev_prop_changed()
    local gpu_obj = {
        Utilization = 0
    }
    local memory_metrics_obj = {
        MemoryUsagePercent = 0
    }
    local get_device_obj_by_path_mock = comm_fun.get_device_obj_by_path
    comm_fun.get_device_obj_by_path = function(_, path, interfaces)
        local _, interface = next(interfaces)

        if interface == 'bmc.dev.Gpu' then
            return gpu_obj
        elseif interface == 'bmc.dev.Memory.Metrics' then
            return memory_metrics_obj
        end
    end

    gpu_mgmt.dev_obj_name_paths = {
        ['/bmc/dev/'] = 'PCIeGpuCard_1_0101'
    }

    gpu_mgmt:on_dev_prop_changed()
    gpu_service.get_instance().dev_prop_changed:emit('PCIeGpuCard_1_0101', 'aaa', 3)
    lu.assertEquals(gpu_obj.Utilization, 0)
    lu.assertEquals(memory_metrics_obj.MemoryUsagePercent, 0)

    gpu_service.get_instance().dev_prop_changed:emit('PCIeGpuCard_1_0101', 'GPUUtilization', 3)
    lu.assertEquals(gpu_obj.Utilization, 3)

    gpu_service.get_instance().dev_prop_changed:emit('PCIeGpuCard_1_0101', 'MemoryUtilization', 2)
    lu.assertEquals(memory_metrics_obj.MemoryUsagePercent, 2)

    comm_fun.get_device_obj_by_path = get_device_obj_by_path_mock
end

function TestGpuMgmtObj:test_get_device_obj_by_path()
    local register_interface_mock = mdb.register_interface
    mdb.register_interface = function ()
    end
    local get_object_mock = mdb.get_object
    mdb.get_object = function (_, device_path, interface)
        if interface == 'bmc.dev.Gpu' then
            return {'aaa'}
        elseif interface == 'bmc.dev.Memory.Metrics' then
            return {'bbb'}
        end
    end
    local bus = {
        call = function ()
            return {}
        end
    }
    local dev_obj = comm_fun.get_device_obj_by_path(bus, '', {})

    lu.assertEquals(dev_obj, nil)
    bus = {
        call = function ()
            return {{}}
        end
    }
    dev_obj = comm_fun.get_device_obj_by_path(bus, '', {})
    lu.assertEquals(dev_obj, nil)

    bus = {
        call = function ()
            return {{'bmc.dev.Gpu'}}
        end
    }
    dev_obj = comm_fun.get_device_obj_by_path(bus, '', {})
    lu.assertEquals(dev_obj, {'aaa'})

    bus = {
        call = function ()
            return {{'bmc.dev.Memory.Metrics'}}
        end
    }
    dev_obj = comm_fun.get_device_obj_by_path(bus, '', {})
    lu.assertEquals(dev_obj, {'bbb'})

    mdb.register_interface = register_interface_mock
    mdb.get_object = get_object_mock
end
