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

local property_changed = org_freedesktop_dbus.ObjMgrPropertiesChanged
local match_rule = org_freedesktop_dbus.MatchRule

local pcie_card_mgmt = class()

-- 设备树对象属性到资源树对象属性的映射
local PROP_TABLE = {
    ["bmc.dev.PCIeDevice"] = {
        ["FunctionClass"] = 'FunctionClass',
        ["VendorId"] = function(resource_obj, v)
            local num = tonumber(v)
            if not num or num < 0 or num > 65535 then
                resource_obj["VendorID"] = 65535
                return resource_obj["VendorID"] == 65535
            end
            resource_obj["VendorID"] = num
            return resource_obj["VendorID"] == tonumber(v)
        end,
        ["DeviceId"] = function(resource_obj, v)
            local num = tonumber(v)
            if not num or num < 0 or num > 65535 then
                resource_obj["DeviceID"] = 65535
                return resource_obj["DeviceID"] == 65535
            end
            resource_obj["DeviceID"] = num
            return resource_obj["DeviceID"] == tonumber(v)
        end,
        ["SubSystemVendorId"] = function(resource_obj, v)
            local num = tonumber(v)
            if not num or num < 0 or num > 65535 then
                resource_obj["SubVendorID"] = 65535
                return resource_obj["SubVendorID"] == 65535
            end
            resource_obj["SubVendorID"] = num
            return resource_obj["SubVendorID"] == tonumber(v)
        end,
        ["SubSystemDeviceId"] = function(resource_obj, v)
            local num = tonumber(v)
            if not num or num < 0 or num > 65535 then
                resource_obj["SubDeviceID"] = 65535
                return resource_obj["SubDeviceID"] == 65535
            end
            resource_obj["SubDeviceID"] = num
            return resource_obj["SubDeviceID"] == tonumber(v)
        end,
    },
    ["bmc.dev.PCIeCard"] = {
        ["Name"] = 'Name',
        ["Model"] = 'Model',
        ["Description"] = 'Description',
        ["BoardName"] = function(resource_obj, v)
            if resource_obj["BoardName"] ~= "" then
                return true -- 兼容新实现，如果已经同步了Board中的数据，不做处理
            end
            resource_obj["BoardName"] = v
            return resource_obj["BoardName"] == v
        end,
        ["BoardId"] = function(resource_obj, v)
            if resource_obj["BoardID"] ~= 65535 and resource_obj["BoardID"] ~= 0 then
                return true
            end
            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,
        ["Manufacturer"] = 'Manufacturer',
        ["PartNumber"] = 'PartNumber',
        ["Health"] = 'Health',
        ["PcbId"] = function(resource_obj, v)
            if not resource_obj["PcbID"] then
                return true
            end
            resource_obj["PcbID"] = v
            return resource_obj["PcbID"] == v
        end,
        ["PcbVersion"] = function(resource_obj, v)
            if not resource_obj["PcbVersion"] then
                return true
            end
            resource_obj["PcbVersion"] = v
            return resource_obj["PcbVersion"] == v
        end
    },
    ["bmc.dev.NetworkAdapter"] = {
        ["FirmwareVersion"] = 'FirmwareVersion'
    },
    ['bmc.dev.Board'] = {
        ["Id"] = function(resource_obj, v)
            if not v or v == 0 and resource_obj["BoardID"] ~= 0 then
                return true
            end
            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,
        ["Name"] = function(resource_obj, v)
            if not v or v == "" then
                return true
            end
            resource_obj["BoardName"] = v
            return resource_obj["BoardName"] == v
        end
    }
}

-- 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"] = function(pcie_card_obj, v)
        if not pcie_card_obj["DevBus"] then
            return true
        end
        pcie_card_obj["DevBus"] = v
        return pcie_card_obj["DevBus"] == v
    end,
    ["DevDevice"] = function(pcie_card_obj, v)
        if not pcie_card_obj["DevDevice"] then
            return true
        end
        pcie_card_obj["DevDevice"] = v
        return pcie_card_obj["DevDevice"] == v
    end,
    ["DevFunction"] = function(pcie_card_obj, v)
        if not pcie_card_obj["DevFunction"] then
            return true
        end
        pcie_card_obj["DevFunction"] = v
        return pcie_card_obj["DevFunction"] == v
    end
}

-- pcie card 资源树对象属性到 pcie card 设备树对象属性的映射
local RESOURCE_PROP_TABLE = {
    ["SerialNumber"] = 'SerialNumber',
    ["PartNumber"] = 'PartNumber'
}

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

-- 同步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
        local retries = 0
        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
            if not ok then
                cmn.skynet.sleep(1)
                retries = retries + 1
            end
        until ok or retries > 100
        if not ok then
            log:notice('[device_mgmt_pcie_card] Synchronization failed. prop=%s value=%s', prop, value)
        end
    end
end

-- 同步设备树属性到资源树对象
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
        local retries = 0
        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)
                retries = retries + 1
            end
        until ok or retries > 100
        if not ok then
            log:notice('[device_mgmt_pcie_card] Synchronization failed. intf=%s prop=%s value=%s', interface, prop, value)
        end
    end
end

-- 同步PCIeCard资源树属性到PCIeCard设备树对象
function pcie_card_mgmt:sync_resource_prop_to_dev_obj(dev_obj, prop, value)
    if RESOURCE_PROP_TABLE[prop] then
        local ok
        local retries = 0
        repeat
            log:info('[device_mgmt_pcie_card] sync_resource_prop_to_dev_obj prop=%s value=%s', prop, value)
            if type(RESOURCE_PROP_TABLE[prop]) == 'string' then
                dev_obj[RESOURCE_PROP_TABLE[prop]] = value
                ok = dev_obj[RESOURCE_PROP_TABLE[prop]] == value
            else
                ok = RESOURCE_PROP_TABLE[prop](dev_obj, value)
            end
            if not ok then
                cmn.skynet.sleep(1)
                retries = retries + 1
            end
        until ok or retries > 100
        if not ok then
            log:notice('[device_mgmt_pcie_card] Synchronization failed. prop=%s value=%s', prop, value)
        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:pcall_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: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

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')
        for k, v in pairs(props) do
            self:pcall_sync_dev_prop_to_resource_obj(resource_obj, interface, k, v:value())
        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,
            comm_defs.BOARD_INTERFACE })
    local device_obj, props
    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
                self:pcall_sync_dev_prop_to_resource_obj(resource_obj, interface, prop,
                    comm_fun.get_prop(device_obj, prop))
            end
        end
    end
end

-- 同步单个属性到资源树对象
function pcie_card_mgmt:pcall_sync_dev_prop_to_resource_obj(resource_obj, interface, prop, value)
    local ok, ret = pcall(function()
        self:sync_dev_prop_to_resource_obj(resource_obj, interface, prop, value)
    end)
    if not ok then
        log:error('[device_mgmt_pcie_card]sync_dev_prop_to_resource_obj err=%s', ret)
    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 _, interfaces = next(ret)
    if not interfaces then
        return
    end
    local _, interface = next(interfaces)
    if not interface then
        return
    end
    mdb.register_interface(interface, comm_defs.INTERFACE_REGISTER_TABLE[interface] or {}, {}, {})
    local device_obj = mdb.get_object(bus, device_path, interface)
    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, class_name)
    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, class_name) -- PCIeCard或OCPCard
        local object_identifier = comm_fun.get_object_identifier_by_device_path(bus, device_path)
        if not object_identifier then
            log:error('[device_mgmt_pcie_card]create_card_orm_object filed, because object_identifier is nil, device_path is %s', device_path)
            return
        end
        -- 设备树对象 identifier第4个对象为position
        local object_position = object_identifier[4]
        log:notice('[device_mgmt_pcie_card]create_resource_obj, pcie_card_object_name is %s', pcie_card_object_name)
        local resource_obj = self.create_fun[class_name](self.app, 1, pcie_card_object_name, function(obj)
            obj.ObjectName = pcie_card_object_name
            obj.ObjectIdentifier = object_identifier
        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:pcall_sync_resource_prop_to_dev_obj(dev_path, dev_obj, prop, value)
    local ok, ret = pcall(function()
        -- 设置设备树对象属性
        self:sync_resource_prop_to_dev_obj(dev_obj, prop, value)
    end)
    if not ok then
        log:error('[device_mgmt]sync_resource_prop_to_dev_obj err=%s', ret)
    end
end

-- PCIeCard对象变化，设置回设备树对象
function pcie_card_mgmt:on_resource_prop_changed()
    c_device_service.get_instance().card_prop_changed:on(function (object_name, prop, value)
        if not RESOURCE_PROP_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
        local dev_obj
        for dev_path, dev_obj_name in pairs(self.dev_obj_name_paths) do
            if comm_fun.get_position_by_object_name(dev_obj_name) == res_position then
                -- 匹配到对应的设备树对象，获取设备树对象
                dev_obj = comm_fun.get_device_obj_by_path(self.bus, dev_path,
                    {comm_defs.PCIE_CARD_DEVICE_INTERFACE})
                self:pcall_sync_resource_prop_to_dev_obj(dev_path, dev_obj, prop, value)
                break
            end
        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

        -- 记录设备树对象的ObjectName
        local dev_obj = comm_fun.get_device_obj_by_path(self.bus, device_path, {comm_defs.OBJECT_PROPERTIES_INTERFACE})
        local dev_obj_pcie_device = comm_fun.get_device_obj_by_path(self.bus, device_path, {comm_defs.PCIE_DEVICE_INTERFACE})
        local resource_obj_class_name = comm_fun.get_prop(dev_obj_pcie_device, comm_defs.COMPONENT_TYPE) == comm_defs.OCP_CARD_COMPONENT_TYPE and
            comm_defs.OCP_CARD_CLASS_NAME or comm_defs.PCIE_CARD_CLASS_NAME
        self.dev_obj_name_paths[device_path] = comm_fun.get_prop(dev_obj, 'ObjectName')
        -- 创建资源树对象
        local resource_obj, position = self:create_resource_obj(self.bus, device_path, resource_obj_class_name)
        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(resource_obj_class_name, 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)
            -- 监听所有PCIeCard对象属性变化
            self:on_resource_prop_changed()
        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
        local resource_obj_class_name = resource_obj.ClassName
        log:notice('[device_mgmt_pcie_card]del_obj class_name is %s, object_name is %s', resource_obj_class_name,
            object_name)
        if not self.create_fun[resource_obj_class_name] then
            -- 限制可能删除范围
            return
        end
        -- 删除资源树对象
        clsmgmt(resource_obj_class_name):remove(resource_obj)
        -- 调用组件回调
        c_device_service.get_instance():on_delete_object(resource_obj_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 对象
        for _, resource_obj in pairs(self.objects) do
            if comm_fun.get_position_by_object_name(resource_obj["ObjectName"]) == res_position then
                self:pcall_sync_pcie_device_prop_to_card(resource_obj, prop, value)
                break
            end
        end
    end)
end

-- 同步单个属性到设备树对象
function pcie_card_mgmt:pcall_sync_pcie_device_prop_to_card(resource_obj, prop, value)
    local ok, ret = pcall(function()
        self:sync_pcie_device_prop_to_card(resource_obj, prop, value)
    end)
    if not ok then
        log:error('[device_mgmt_pcie_card]sync_pcie_device_prop_to_card err=%s', ret)
    end
end

function pcie_card_mgmt:ctor(app)
    self.app = app
    self.bus = app.bus
    self.objects = {}  -- 设备树对象路径对应资源树对象
    self.sig_slot = {} -- 存放设备树对象相关信号
    self.dev_obj_name_paths = {} -- 设备树对象路径对应ObjectName
    self.create_fun = {
        [comm_defs.PCIE_CARD_CLASS_NAME] = self.app.CreatePCIeCard,
        [comm_defs.OCP_CARD_CLASS_NAME] = self.app.CreateOCPCard
    }
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)
