-- 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 class = require 'mc.class'
local singleton = require 'mc.singleton'
local log = require 'mc.logging'
local org_freedesktop_dbus = require 'sd_bus.org_freedesktop_dbus'
local property_changed = org_freedesktop_dbus.ObjMgrPropertiesChanged
local match_rule = org_freedesktop_dbus.MatchRule
local comm_defs = require 'device_mgmt.comm_defs'
local comm_fun = require 'device_mgmt.comm_fun'
local ctx = require 'mc.context'
local mdb = require 'mc.mdb'
local c_device_service = require 'device.device_service'
local cmn = require 'common'
local clsmgmt = require 'mc.class_mgnt'

local pcie_card_mgmt = class()

-- 设备树对象属性到资源树对象属性的映射
local PROP_TABLE = {
    ["bmc.dev.PCIeDevice"] = {
        ["FunctionClass"] = 'FunctionClass',
        ["VendorId"] = function(resource_obj, v)
            resource_obj["VendorID"] = tonumber(v)
            return resource_obj["VendorID"] == tonumber(v)
        end,
        ["DeviceId"] = function(resource_obj, v)
            resource_obj["DeviceID"] = tonumber(v)
            return resource_obj["DeviceID"] == tonumber(v)
        end,
        ["SubSystemVendorId"] = function(resource_obj, v)
            resource_obj["SubVendorID"] = tonumber(v)
            return resource_obj["SubVendorID"] == tonumber(v)
        end,
        ["SubSystemDeviceId"] = function(resource_obj, v)
            resource_obj["SubDeviceID"] = tonumber(v)
            return resource_obj["SubDeviceID"] == tonumber(v)
        end,
    },
    ["bmc.dev.PCIeCard"] = {
        ["Name"] = 'Name',
        ["Model"] = 'Model',
        ["Description"] = 'Description',
        ["BoardId"] = function(resource_obj, v)
            if v == 0 then
                resource_obj["BoardID"] = 65535
                return resource_obj["BoardID"] == 65535
            else
                resource_obj["BoardID"] = v
                return resource_obj["BoardID"] == v
            end
        end,
        ["BoardName"] = 'BoardName',
        ["Manufacturer"] = 'Manufacturer',
        ["PartNumber"] = 'PartNumber',
        ["Health"] = 'Health',
        ["PcbId"] = 'PcbID'
    },
    ["bmc.dev.NetworkAdapter"] = {
        ["FirmwareVersion"] = 'FirmwareVersion'
    }
}

-- pcie device 资源树对象属性到 pcie card 资源树属性的映射
local PCIE_DEVICE_PROP_TO_PCIE_CARD_TABLE = {
    ["SlotID"] = function(pcie_card_obj, v)
        pcie_card_obj["SlotID"] = v
        pcie_card_obj["NodeID"] = string.format('PCIeCard%s', v)
        return pcie_card_obj["SlotID"] == v and pcie_card_obj["NodeID"] == string.format('PCIeCard%s', v)
    end,
    ["DeviceName"] = 'DeviceName',
    ["Position"] = 'Position',
    ["SocketID"] = 'LaneOwner',
    ["DevBus"] = 'DevBus',
    ["DevDevice"] = 'DevDevice',
    ["DevFunction"] = 'DevFunction'
}

local PATH_PATTERN = {
    comm_defs.PCIE_NIC_CARD_PATH_PATTERN,
    comm_defs.PCIE_GPU_CARD_PATH_PATTERN
}

-- 同步设备树属性到资源树对象
function pcie_card_mgmt:sync_dev_prop_to_resource_obj(resource_obj, interface, prop, value)
    if PROP_TABLE[interface] and PROP_TABLE[interface][prop] then
        local ok
        repeat
            if type(PROP_TABLE[interface][prop]) == 'string' then
                resource_obj[PROP_TABLE[interface][prop]] = value
                ok = resource_obj[PROP_TABLE[interface][prop]] == value
            else
                PROP_TABLE[interface][prop](resource_obj, value)
                ok = PROP_TABLE[interface][prop](resource_obj, value)
            end
            if not ok then
                cmn.skynet.sleep(1)
            end
        until ok
    end
end

-- 同步PCIeDevice资源树属性到PCIeCard资源树对象
function pcie_card_mgmt:sync_pcie_device_prop_to_card(pcie_card_obj, prop, value)
    if PCIE_DEVICE_PROP_TO_PCIE_CARD_TABLE[prop] then
        local ok
        repeat
            log:info('[device_mgmt_pcie_card] sync_pcie_device_prop_to_card prop=%s value=%s', prop, value)
            if type(PCIE_DEVICE_PROP_TO_PCIE_CARD_TABLE[prop]) == 'string' then
                pcie_card_obj[PCIE_DEVICE_PROP_TO_PCIE_CARD_TABLE[prop]] = value
                ok = pcie_card_obj[PCIE_DEVICE_PROP_TO_PCIE_CARD_TABLE[prop]] == value
            else
                ok = PCIE_DEVICE_PROP_TO_PCIE_CARD_TABLE[prop](pcie_card_obj, value)
            end
        until ok
    end
end

-- 添加重试同步资源树属性
function pcie_card_mgmt:get_pcie_device_prop_to_card_retry(pcie_card_obj)
    cmn.skynet.fork(function ()
        local retry = 3 -- 重试3次
        while retry > 0 do
            cmn.skynet.sleep(300) -- 等待3秒
            retry = retry - 1
            self:get_pcie_device_prop_to_card(pcie_card_obj)
        end
    end)
end

-- 查询PCIeDevice对象，设置属性到PCIeCard
function pcie_card_mgmt:get_pcie_device_prop_to_card(pcie_card_obj)
    local ok, ret = pcall(function()
        local pcie_card_position = comm_fun.get_position_by_object_name(pcie_card_obj['ObjectName'])
        local pcie_device_position
        local pcie_device_obj = cmn.find(c_device_service.get_instance().pcie_device_list, function(item)
            pcie_device_position = comm_fun.get_position_by_object_name(item:get_prop('ObjectName'))
            if pcie_device_position == pcie_card_position then
                return true
            end
        end)
        for k, _ in pairs(PCIE_DEVICE_PROP_TO_PCIE_CARD_TABLE) do
            self:sync_pcie_device_prop_to_card(pcie_card_obj, k, pcie_device_obj:get_prop(k))
            cmn.skynet.sleep(1) -- 等待10ms避免阻塞
        end
    end)
    if not ok then
        log:error('[device_mgmt_pcie_card] get_pcie_device_prop_to_card error=%s', ret)
    end
end

function pcie_card_mgmt:listen_device_obj_property_change(bus, sig_slot, device_path, resource_obj)
    log:info('[device_mgmt_pcie_card]listen_device_obj_property_change start, device_path is %s', device_path)
    local property_changed_sig = match_rule.signal(property_changed.name, property_changed.interface):with_path(
        device_path)
    sig_slot[#sig_slot + 1] = bus:match(property_changed_sig, function(msg)
        local interface, props = msg:read('sa{sv}as')
        local sync_ok, sync_ret
        for k, v in pairs(props) do
            -- 使用pcall包裹，避免一个属性赋值失败导致循环结束
            sync_ok, sync_ret = pcall(function()
                self:sync_dev_prop_to_resource_obj(resource_obj, interface, k, v:value())
            end)
            if not sync_ok then
                log:error('[device_mgmt_pcie_card] listen_device_obj_property_change error=%s', sync_ret)
            end
        end
    end)
end

function pcie_card_mgmt:synchronize_property(bus, device_path, resource_obj)
    log:info('[device_mgmt_pcie_card]synchronize_property start, device_path is %s', device_path)
    local ret = bus:call(comm_defs.MACA_SERVICE, comm_defs.MDB_PATH,
        comm_defs.MDB_INTERFACE, 'GetObject',
        'a{ss}sas', ctx.get_context_or_default(),
        device_path,
        { comm_defs.PCIE_DEVICE_INTERFACE, comm_defs.PCIE_CARD_DEVICE_INTERFACE, comm_defs.NETWORK_ADAPTER_INTERFACE })
    local device_obj, props
    local sync_ok, sync_ret
    for service_name, interfaces in pairs(ret) do
        for _, interface in pairs(interfaces) do
            mdb.register_interface(interface, comm_defs.INTERFACE_REGISTER_TABLE[interface] or {}, {}, {})
            device_obj = mdb.get_object(bus, device_path, interface)
            props = bus:call(service_name, device_path, comm_defs.ORG_PROPERTIES_INTERFACE, 'GetAll', 's', interface)
            for prop in pairs(props) do
                -- 使用pcall包裹，避免一个属性赋值失败导致循环结束
                sync_ok, sync_ret = pcall(function()
                    log:info('[device_mgmt_pcie_card] synchronize_property interface=%s prop=%s', interface, prop)
                    self:sync_dev_prop_to_resource_obj(resource_obj, interface, prop,
                        comm_fun.get_prop(device_obj, prop))
                end)
                if not sync_ok then
                    log:error('[device_mgmt_pcie_card] synchronize_property error=%s', sync_ret)
                end
            end
        end
    end
end

function pcie_card_mgmt:get_object_name_by_device_path(bus, device_path)
    local ret = bus:call(comm_defs.MACA_SERVICE, comm_defs.MDB_PATH,
        comm_defs.MDB_INTERFACE, 'GetObject',
        'a{ss}sas', ctx.get_context_or_default(),
        device_path, { comm_defs.OBJECT_PROPERTIES_INTERFACE })
    local device_obj
    for ret_key, interfaces in pairs(ret) do
        for _, interface in pairs(interfaces) do
            mdb.register_interface(interface, comm_defs.INTERFACE_REGISTER_TABLE[interface] or {}, {}, {})
            device_obj = mdb.get_object(bus, device_path, interface)
            goto continue
        end
    end
    ::continue::
    if not device_obj then
        log:notice('[device_mgmt_pcie_card]get_object_name_by_device_path failed, device_path is %s', device_path)
        return
    end
    local object_name = comm_fun.get_prop(device_obj, 'ObjectName')
    log:notice('[device_mgmt_pcie_card]get_object_name_by_device_path, object_name is %s', object_name)

    return object_name
end

function pcie_card_mgmt:create_resource_obj(bus, device_path)
    log:notice('[device_mgmt_pcie_card]create_resource_obj start, device_path is %s', device_path)
    local object_name = self:get_object_name_by_device_path(bus, device_path)
    if not object_name then
        log:error('[device_mgmt_pcie_card]create_resource_obj failed, because object_name is nil, device_path is %s',
            device_path)
        return
    end
    -- 创建资源树对象 SystemId默认1 Id是Object_name
    local ok, ret, position = pcall(function()
        local pcie_card_object_name = comm_fun.get_device_name_by_object_name(object_name,
            comm_defs.PCIE_CARD_CLASS_NAME)
        local object_position = comm_fun.get_position_by_object_name(pcie_card_object_name)
        log:notice('[device_mgmt_pcie_card]create_resource_obj, pcie_card_object_name is %s', pcie_card_object_name)
        local resource_obj = self.app:CreatePCIeCard(1, pcie_card_object_name, function(obj)
            obj.ObjectName = pcie_card_object_name
            obj.ObjectIdentifier = {
                1, "1", "1", object_position
            }
        end)
        return resource_obj, object_position
    end)
    if not ok or not ret then
        log:error('[device_mgmt_pcie_card]create_resource_obj failed, device_path=%s ret=%s', device_path, ret)
        return
    else
        log:notice('[device_mgmt_pcie_card]create_resource_obj success, device_path is %s', device_path)
        return ret, position
    end
end

function pcie_card_mgmt:on_add_device_obj()
    log:notice('[device_mgmt_pcie_card]on_add_device_obj start')
    for _, path_pattern in pairs(PATH_PATTERN) do
        comm_fun.set_interface_add_signal(self.bus, self.sig_slot, path_pattern, comm_defs.PCIE_DEVICE_INTERFACE,
            function(path)
                self:init_obj(path)
            end)
    end
end

function pcie_card_mgmt:init_obj(device_path)
    -- 用pcall包裹，避免某一个网卡初始化失败导致循环结束
    pcall(function()
        if self.objects[device_path] then 
            log:notice('[device_mgmt_pcie_card]init_obj already exist, device_path is %s', device_path)
            return 
        end

        -- 创建资源树对象
        local resource_obj, position = self:create_resource_obj(self.bus, device_path)
        if resource_obj then
            self.objects[device_path] = resource_obj
            -- 先启监听设备树对象属性变化，更新对应的资源树对象属性
            self:listen_device_obj_property_change(self.bus, self.sig_slot, device_path, resource_obj)
            -- 调用组件回调
            c_device_service.get_instance():on_add_object("PCIeCard", resource_obj, position)
            cmn.skynet.sleep(100)
            -- 再将设备树对象属性赋值给对应资源树对象
            self:synchronize_property(self.bus, device_path, resource_obj)
            -- 同步一次PCIeDevice属性到PCIeCard
            self:get_pcie_device_prop_to_card_retry(resource_obj)
        end
    end)
end

function pcie_card_mgmt:on_del_device_obj()
    log:notice('[device_mgmt_pcie_card]on_del_device_obj start')
    for _, path_pattern in pairs(PATH_PATTERN) do
        comm_fun.set_interface_del_signal(self.bus, self.sig_slot, path_pattern, comm_defs.PCIE_CARD_DEVICE_INTERFACE,
            function(path)
                self:del_obj(path)
            end)
    end
end

function pcie_card_mgmt:del_obj(device_path)
    pcall(function()
        if not self.objects[device_path] then 
            log:notice('[device_mgmt_pcie_card]del_obj not exist, device_path is %s', device_path)
            return 
        end

        local resource_obj = self.objects[device_path]
        local position = resource_obj.ObjectIdentifier[comm_defs.POSITION_INDEX]
        local object_name = resource_obj.ObjectName
        log:notice('[device_mgmt_pcie_card]del_obj class_name is %s, object_name is %s', comm_defs.PCIE_CARD_CLASS_NAME,
            object_name)
        -- 删除资源树对象
        clsmgmt(comm_defs.PCIE_CARD_CLASS_NAME):remove(resource_obj)
        -- 调用组件回调
        c_device_service.get_instance():on_delete_object(comm_defs.PCIE_CARD_CLASS_NAME, resource_obj, position)
        self.objects[device_path] = nil
    end)
end

function pcie_card_mgmt:init_check_obj()
    for _, path_pattern in pairs(PATH_PATTERN) do
        local device_paths = comm_fun.get_all_device_paths(self.bus, path_pattern, 1,
            comm_defs.PCIE_CARD_DEVICE_INTERFACE)
        for _, device_path in pairs(device_paths) do
            log:notice('[device_mgmt_pcie_card]get device_path: %s', device_path)
            self:init_obj(device_path)
        end
    end
end

-- 监听PCIeDevice对象属性变化
function pcie_card_mgmt:on_dev_prop_changed()
    c_device_service.get_instance().dev_prop_changed:on(function (object_name, prop, value)
        if not PCIE_DEVICE_PROP_TO_PCIE_CARD_TABLE[prop] then
            log:debug('[device_mgmt_pcie_card]on_dev_prop_changed not include, prop=%s', prop)
            return
        end
        local res_position = comm_fun.get_position_by_object_name(object_name) -- 资源树对象 position
        -- 根据 position 匹配对应的 PCIeCard 对象
        local ok, ret
        for dev_path, resource_obj in pairs(self.objects) do
            if comm_fun.get_position_by_object_name(resource_obj["ObjectName"]) == res_position then
                ok, ret = pcall(function()
                    -- 设置设备树对象属性
                    self:sync_pcie_device_prop_to_card(resource_obj, prop, value)
                    log:info('[device_mgmt_pcie_card]on_dev_prop_changed, device_path is %s prop=%s value=%s src=%s',
                        dev_path, prop, resource_obj[prop], object_name)
                end)
                if not ok then
                    log:error('[device_mgmt_pcie_card]on_dev_prop_changed err=%s', ret)
                end
                break
            end
        end
    end)
end

function pcie_card_mgmt:ctor(app)
    self.app = app
    self.bus = app.bus
    self.objects = {}  -- 设备树对象路径对应资源树对象
    self.sig_slot = {} -- 存放设备树对象相关信号
end

function pcie_card_mgmt:init()
    log:notice('[device_mgmt_pcie_card]pcie_card_mgmt init')
    local ok, ret = pcall(function()
        -- 先监听网卡/GPU卡设备树对象新增信号
        self:on_add_device_obj()
        self:on_del_device_obj()
        -- 获取所有的网卡/GPU设备树对象，创建对应的资源树对象
        self:init_check_obj()
        -- 监听所有PCIeDevice对象属性变化
        self:on_dev_prop_changed()
    end)
    if not ok then
        log:error('[device_mgmt_pcie_card]init failed, err=%s', ret)
    end
end

return singleton(pcie_card_mgmt)
