-- 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 comm_defs = require 'device_mgmt.comm_defs'
local comm_fun = require 'device_mgmt.comm_fun'
local ctx = require 'mc.context'
local c_device_service = require 'device.device_service'
local cmn = require 'common'

local c_object = require 'mc.orm.object'
local clsmgmt = require 'mc.class_mgnt'
local org_freedesktop_dbus = require 'sd_bus.org_freedesktop_dbus'
local class = require 'mc.class'
local singleton = require 'mc.singleton'
local log = require 'mc.logging'
local mdb = require 'mc.mdb'

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

local c_pcie_device = c_object('PCIeDevice')

local pcie_device_mgmt = class()

-- 设备树对象属性到资源树对象属性的映射
local PROP_TABLE = {
    ["bmc.dev.PCIeDevice"] = {
        ["DeviceName"] = 'DeviceName',
        ["FunctionClass"] = 'FunctionClass',
        ["DeviceType"] = 'PCIeDeviceType',
        ["SlotType"] = 'SlotType',
        ["FunctionProtocol"] = 'FunctionProtocol',
        ["FunctionType"] = 'FunctionType',
        ["Container"] = 'Container',
        ["ComponentType"] = 'DeviceType'
    }
}

-- 资源树对象属性到设备树属性的映射
local RESOURCE_PROP_TABLE = {
    ["SlotID"] = 'Slot',
    ["Segment"] = 'Segment',
    ["SocketID"] = 'SocketId',
    ["Bus"] = 'Bus',
    ["Device"] = 'Device',
    ["Function"] = 'Function',
    ["DevBus"] = 'DevBus',
    ["DevDevice"] = 'DevDevice',
    ["DevFunction"] = 'DevFunction',
    ["DiagnosticFault"] = 'DiagnosticFault',
    ["PredictiveFault"] = 'PredictiveFault',
    ["UCEByBIOS"] = 'UCEByBIOS',
    ["BandwidthReduction"] = 'BandwidthReduction',
    ["LinkSpeedReduced"] = 'LinkSpeedReduced',
    ["DeviceName"] = 'DeviceName', -- 在替换槽位后设置回设备树
    ["Position"] = function(dev_obj, v)
        if type(v) == "string" and v ~= "" then
            dev_obj['Location'] = v
            return dev_obj['Location'] == v
        end
        -- 如果资源树对应属性为空，不向设备树同步，默认成功
        return true
    end,
    ["GroupPosition"] = 'GroupPosition' -- 这个参数只在资源树有，设备树需要从资源树拿
}

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

function c_pcie_device.create_mdb_object(app, pcie_device_object_name, object_identifier)
    return app:CreatePCIeDevice(1, pcie_device_object_name, function(obj)
            obj.ObjectName = pcie_device_object_name
            obj.GroupPosition = pcie_device_object_name
            obj.ObjectIdentifier = object_identifier
            end)
end

-- 同步设备树属性到资源树对象
function pcie_device_mgmt:sync_dev_prop_to_resource_obj(resource_obj, interface, prop, value, device_path)
    if prop == "DeviceName" and self.dev_obj_device_name_init[device_path] then
        return
    elseif prop == "DeviceName" and not self.dev_obj_device_name_init[device_path] then
        self.dev_obj_device_name_init[device_path] = true
    end
    if PROP_TABLE[interface] and PROP_TABLE[interface][prop] then
        local ok
        local retries = 0
        repeat
            log:info('[device_mgmt] sync_dev_prop_to_resource_obj intf=%s prop=%s value=%s', interface, prop, value)
            if type(PROP_TABLE[interface][prop]) == 'string' then
                resource_obj[PROP_TABLE[interface][prop]] = value
                ok = resource_obj[PROP_TABLE[interface][prop]] == value
            else
                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] Synchronization failed. intf=%s prop=%s value=%s', interface, prop, value)
        end
    end
end

-- 同步资源树属性到设备树对象
function pcie_device_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] 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] Synchronization failed. prop=%s value=%s', prop, value)
        end
    end
end

function pcie_device_mgmt:listen_device_obj_property_change(bus, sig_slot, device_path, resource_obj)
    log:info('[device_mgmt]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(), device_path)
        end
    end)
end

function pcie_device_mgmt:synchronize_property(bus, device_path, resource_obj)
    log:info('[device_mgmt]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 })
    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), device_path)
            end
        end
    end
end

-- 同步单个属性到资源树对象
function pcie_device_mgmt:pcall_sync_dev_prop_to_resource_obj(resource_obj, interface, prop, value, device_path)
    local ok, ret = pcall(function()
        self:sync_dev_prop_to_resource_obj(resource_obj, interface, prop, value, device_path)
    end)
    if not ok then
        log:error('[device_mgmt]sync_dev_prop_to_resource_obj err=%s', ret)
    end
end

function pcie_device_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 ret_key, interfaces = next(ret)
    if not ret_key or not interfaces then
        return
    end
    local _, interface = next(interfaces)
    if not interface then
        return
    end
    log:notice('[device_mgmt]get_object_name_by_device_path ret_key=%s interface=%s', ret_key, interface)
    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:error('[device_mgmt]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]get_object_name_by_device_path, object_name is %s', object_name)

    return object_name
end

function pcie_device_mgmt:create_resource_obj(bus, device_path)
    log:notice('[device_mgmt]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]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_device_object_name = comm_fun.get_device_name_by_object_name(object_name,
            comm_defs.PCIE_DEVICE_CLASS_NAME)
        local object_identifier = comm_fun.get_object_identifier_by_device_path(bus, device_path)
        if not object_identifier then
            log:error('create_device_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]create_resource_obj, pcie_device_object_name is %s', pcie_device_object_name)
        -- c_pcie_device调用了框架机制，在创建资源树对象后使用new又创建了orm对象，其能把资源树的更改同步到数据库
        local resource_obj = c_pcie_device.create_mdb_object(self.app, pcie_device_object_name, object_identifier)
        c_pcie_device.new(resource_obj, 0, {GroupPosition = pcie_device_object_name})
        return resource_obj, object_position
    end)
    if not ok or not ret then
        log:error('[device_mgmt]create_resource_obj failed, device_path=%s ret=%s', device_path, ret)
        return
    else
        log:notice('[device_mgmt]create_resource_obj success, device_path is %s', device_path)
        return ret, position
    end
end

function pcie_device_mgmt:on_add_device_obj()
    log:notice('[device_mgmt]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_device_mgmt:init_obj(device_path)
    -- 用pcall包裹，避免某一个网卡初始化失败导致循环结束
    pcall(function()
        if self.objects[device_path] then
            log:notice('[device_mgmt]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})
        self.dev_obj_name_paths[device_path] = comm_fun.get_prop(dev_obj, 'ObjectName')
        self.dev_obj_set_dev_bus_done[device_path] = false -- 标记当前设备树对象是否调用过SetDeviceBDF
        self.dev_obj_device_name_init[device_path] = false
        -- 创建资源树对象
        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)
            -- 再将设备树对象属性赋值给对应资源树对象
            self:synchronize_property(self.bus, device_path, resource_obj)
            -- 调用组件回调
            c_device_service.get_instance():on_add_object("PCIeDevice", resource_obj, position)
            cmn.skynet.sleep(100)
            c_device_service.get_instance():on_add_object_complete(position)
            -- 同步资源树对象属性到设备树对象属性
            cmn.skynet.sleep(100)
            self:sync_all_resource_prop_to_dev_obj(resource_obj, device_path)
        end
    end)
end

function pcie_device_mgmt:on_del_device_obj()
    log:notice('[device_mgmt]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_DEVICE_INTERFACE,
            function(path)
                self:del_obj(path)
            end)
    end
end

function pcie_device_mgmt:del_obj(device_path)
    pcall(function()
        if not self.objects[device_path] then
            log:notice('[device_mgmt]del_obj not exist, device_path is %s', device_path)
            return
        end
        self.dev_obj_name_paths[device_path] = nil
        self.dev_obj_set_dev_bus_done[device_path] = nil
        self.dev_obj_device_name_init[device_path] = nil

        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]del_obj class_name is PCIeDevice, object_name is %s', object_name)
        -- 删除资源树对象
        clsmgmt(comm_defs.PCIE_DEVICE_CLASS_NAME):remove(resource_obj)
        -- 调用组件回调
        c_device_service.get_instance():on_delete_object(comm_defs.PCIE_DEVICE_CLASS_NAME, resource_obj, position)
        self.objects[device_path] = nil
    end)
end

function pcie_device_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_DEVICE_INTERFACE)
        for _, device_path in pairs(device_paths) do
            log:notice('[device_mgmt]get device_path: %s', device_path)
            self:init_obj(device_path)
        end
    end
end

-- 判断DevBus是否发生变化，如果变化就调用SetDeviceBDF
function pcie_device_mgmt:set_device_bdf(dev_path, device_obj, prop, value)
    if prop ~= 'DevBus' or value == 0 or self.dev_obj_set_dev_bus_done[dev_path] then
        return
    end
    if self.objects[dev_path] then
        local resource_obj = self.objects[dev_path]
        local ok, rsp_code = pcall(function()
            return device_obj:call_method("SetDeviceBDF", "yyy", resource_obj["DevBus"],
                resource_obj["DevDevice"], resource_obj["DevFunction"])
        end)
        if not ok or not rsp_code then
            log:error('[device_mgmt]SetDeviceBDF failed, rsp_code=%s b=%s d=%s f=%s', rsp_code,
                resource_obj["DevBus"], resource_obj["DevDevice"], resource_obj["DevFunction"])
        else
            log:notice('[device_mgmt]SetDeviceBDF success, rsp_code=%s b=%s d=%s f=%s', rsp_code,
                resource_obj["DevBus"], resource_obj["DevDevice"], resource_obj["DevFunction"])
            self.dev_obj_set_dev_bus_done[dev_path] = true
        end
    end
end

function pcie_device_mgmt:set_device_bdf_init()
    local device_obj, resource_obj
    for dev_path, is_done in pairs(self.dev_obj_set_dev_bus_done) do
        if not is_done and self.objects[dev_path] and self.objects[dev_path]["DevBus"] ~= 0 then
            device_obj = comm_fun.get_device_obj_by_path(self.bus, dev_path,
                {comm_defs.PCIE_DEVICE_INTERFACE})
            resource_obj = self.objects[dev_path]
            self:set_device_bdf(dev_path, device_obj, "DevBus", resource_obj["DevBus"])
        end
    end
end

function pcie_device_mgmt:sync_all_resource_prop_to_dev_obj(resource_obj, dev_path)
    local ok, ret = pcall(function()
        local dev_obj = comm_fun.get_device_obj_by_path(self.bus, dev_path, {comm_defs.PCIE_DEVICE_INTERFACE})
        for res_prop, _ in pairs(RESOURCE_PROP_TABLE) do
            self:pcall_sync_resource_prop_to_dev_obj(dev_path, dev_obj, res_prop, resource_obj[res_prop])
        end
    end)
    if not ok then
        log:error('[device_mgmt]sync_all_resource_prop_to_dev_obj err=%s', ret)
    end
end

-- 同步单个属性到设备树对象
function pcie_device_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)
        -- 调用设备树方法
        self:set_device_bdf(dev_path, dev_obj, prop, value)
    end)
    if not ok then
        log:error('[device_mgmt]sync_resource_prop_to_dev_obj err=%s', ret)
    end
end

-- PCIeDevice对象变化，设置回设备树对象
function pcie_device_mgmt:on_dev_prop_changed()
    c_device_service.get_instance().dev_prop_changed:on(function (object_name, prop, value)
        if not RESOURCE_PROP_TABLE[prop] then
            log:debug('[device_mgmt]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_DEVICE_INTERFACE})
                self:pcall_sync_resource_prop_to_dev_obj(dev_path, dev_obj, prop, value)
                break
            end
        end
    end)
end

function pcie_device_mgmt:ctor(app)
    self.app = app
    self.bus = app.bus
    self.objects = {}  -- 设备树对象路径对应资源树对象
    self.sig_slot = {} -- 存放设备树对象相关信号
    self.dev_obj_name_paths = {} -- 设备树对象路径对应ObjectName
    self.dev_obj_set_dev_bus_done = {} -- 设备树对象路径对应是否触发设备BDF监听
    self.dev_obj_device_name_init = {} -- 设备树对象DeviceName属性获取过一次
end

function pcie_device_mgmt:init()
    log:notice('[device_mgmt]pcie_device_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()
        -- 查询PCIeDevice对象属性，调用设备树方法
        self:set_device_bdf_init()
    end)
    if not ok then
        log:error('[device_mgmt]init failed, err=%s', ret)
    end
end

return singleton(pcie_device_mgmt)
