-- 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 c_network_port = require 'device.class.network_port'
local port_init = require 'device.class.nic_mgmt.port.port_init'
local class = require 'mc.class'
local singleton = require 'mc.singleton'
local log = require 'mc.logging'
local card_mgmt = require 'device.class.nic_mgmt.card_mgmt'
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.class.nic_mgmt.comm_defs'
local comm_fun = require 'device.class.nic_mgmt.comm_fun'
local ctx = require 'mc.context'
local mdb = require 'mc.mdb'
local c_factory = require 'mc.orm.factory'

local port_mgmt = class(nil, nil, true)

-- 设备树对象属性到资源树对象属性的映射
local prop_table = {
    PermanentMACAddress = 'PermanentMACAddress',
    MACAddress = 'MACAddress',
    MaxSpeedSupported = 'SupportedLinkCapability',
    NetDevFuncType = 'NetDevFuncType',
    MediumType = 'MediumType',
    WorkloadType = 'WorkloadType',
    SpeedMbps = 'SpeedMbps',
    FullDuplex = 'FullDuplex',
    AutoSpeedNegotiationEnabled = 'AutoSpeedNegotiation',
    LLDPEnabled = 'LLDPEnabled',
    ChassisId = 'ChassisId',
    ChassisIdSubtype = 'ChassisIdSubtype',
    PortIdSubtype = 'PortIdSubtype',
    SystemName = 'SystemName',
    ManagementVlanId = 'ManagementVlanId',
    RXFrames = 'RXFrames',
    TXFrames = 'TXFrames',
    RXUnicastFrames = 'RXUnicastFrames',
    TXUnicastFrames = 'TXUnicastFrames',
    RXMulticastFrames = 'RXMulticastFrames',
    TXMulticastFrames = 'TXMulticastFrames',
    RXBroadcastFrames = 'RXBroadcastFrames',
    TXBroadcastFrames = 'TXBroadcastFrames',
    RXFCSErrors = 'RXFCSErrors',
    RXUndersizeFrames = 'RXUndersizeFrames',
    RXOversizeFrames = 'RXOversizeFrames',
    TXSingleCollisions = 'TXSingleCollisions',
    TXMultipleCollisions = 'TXMultipleCollisions',
    TXLateCollisions = 'TXLateCollisions',
    TXExcessiveCollisions = 'TXExcessiveCollisions',
    RXFrameAlignmentErrors = 'RXFrameAlignmentErrors',
    BandwidthPercent = 'BandwidthUsagePercent',
    PacketsDropped = 'PacketsDropped',
    Up2cos = 'Up2cos',
    Uppgid = 'Uppgid',
    Pgpct = 'Pgpct',
    PgStrict = 'PgStrict',
    Pfcmap = 'Pfcmap',
    FCId = 'FCId',
    WWNN = 'WWNN',
    DefaultWWNN = 'DefaultWWNN',
    WWPN = 'WWPN',
    DefaultWWPN = 'DefaultWWPN',
    SpeedGbps = 'SpeedGbps',
    LinkStatusValue = 'LinkStatusValue',
    NpuId = 'NpuID',
    PowerOn = 'PowerOn',
    BDF = 'BDF'
}

-- Metrics接口中设备树定义是uint64，资源树定义是string，需要转换为string的属性列表
local metrics_uint64_props = {
    RXFrames = true,
    TXFrames = true,
    RXUnicastFrames = true,
    TXUnicastFrames = true,
    RXMulticastFrames = true,
    TXMulticastFrames = true,
    RXBroadcastFrames = true,
    TXBroadcastFrames = true,
    RXFCSErrors = true,
    RXFrameAlignmentErrors = true,
    RXUndersizeFrames = true,
    RXOversizeFrames = true,
    TXSingleCollisions = true,
    TXMultipleCollisions = true,
    TXLateCollisions = true,
    TXExcessiveCollisions = true
}

local link_status_table = {
    [0] = 'Disconnected',
    [1] = 'Connected',
    [255] = 'N/A'
}

-- 处理特殊的属性映射逻辑
local function handle_special_property_mapping(interface, prop, orm_obj, value)
    if prop == 'PortId' and interface == comm_defs.NETWORK_PORT_DEVICE_INTERFACE then
        orm_obj['PortID'] = value
    end
    if prop == 'PortId' and interface == comm_defs.NETWORK_PORT_LLDP_RECEIVE_INTERFACE then
        orm_obj['LLDPReceivePortId'] = value
    end
    if prop == 'LinkStatus' and interface == comm_defs.NETWORK_PORT_LINK_INFO_INTERFACE then
        local link_status = link_status_table[value] or 'N/A'
        orm_obj:set_link_status(link_status)
        orm_obj:set_link_status_numeric(link_status)
    end
end

local function listen_device_obj_property_change(bus, sig_slot, port_device_path, orm_obj)
    log:notice('listen_device_obj_property_change start, port_device_path is %s', port_device_path)
    local property_changed_sig = match_rule.signal(property_changed.name, property_changed.interface):with_path(
    port_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
            -- 使用pcall包裹，避免一个属性赋值失败导致循环结束
            pcall(function()
                if prop_table[k] then
                    local value = v:value()
                    -- Metrics 属性需要转换为字符串
                    if metrics_uint64_props[k] then
                        value = tostring(value)
                    end
                    orm_obj[prop_table[k]] = value
                else
                    handle_special_property_mapping(interface, k, orm_obj, v:value())
                end
            end)
        end
    end)
end

local function synchronize_property(bus, port_device_path, orm_obj)
    log:notice('synchronize_property start, port_device_path is %s', port_device_path)
    local ret_ori = bus:call(comm_defs.MACA_SERVICE, comm_defs.MDB_PATH, comm_defs.MDB_INTERFACE, 'GetObject', 'a{ss}sas',
        ctx.get_context_or_default(), port_device_path,
        { comm_defs.NETWORK_PORT_DEVICE_INTERFACE, comm_defs.NETWORK_PORT_DATA_CENTER_BRIDGING_INTERFACE,
            comm_defs.NETWORK_PORT_LLDP_RECEIVE_INTERFACE, comm_defs.NETWORK_PORT_LINK_INFO_INTERFACE,
            comm_defs.NETWORK_PORT_METRICS_INTERFACE, comm_defs.NETWORK_PORT_FIBRE_CHANNEL_INTERFACE })
    local port_device_obj, props
    for service_name, interfaces in pairs(ret_ori) do
        for _, interface in pairs(interfaces) do
            mdb.register_interface(interface, comm_defs.INTERFACE_REGISTER_TABLE[interface] or {}, {}, {})
            port_device_obj = mdb.get_object(bus, port_device_path, interface)
            props = bus:call(service_name, port_device_path, comm_defs.ORG_PROPERTIES_INTERFACE,
                'GetAll', 's', interface)
            for prop in pairs(props) do
                -- 使用pcall包裹，避免一个属性赋值失败导致循环结束
                pcall(function()
                    local ret, val = port_device_obj:get_property(prop)
                    if ret ~= 0 then
                        log:error('get_property failed, prop=%s, device_path=%s', prop, port_device_path)
                        goto continue
                    end
                    if prop_table[prop] then
                        -- Metrics 属性需要转换为字符串
                        if metrics_uint64_props[prop] then
                            val = tostring(val)
                        end
                        orm_obj[prop_table[prop]] = val
                    else
                        handle_special_property_mapping(interface, prop, orm_obj, val)
                    end
                    ::continue::
                end)
            end
        end
    end
end

local function get_port_id_by_device_path(bus, port_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(), port_device_path, { comm_defs.NETWORK_PORT_DEVICE_INTERFACE })
    local port_device_obj
    for _, interfaces in pairs(ret) do
        for _, interface in pairs(interfaces) do
            mdb.register_interface(interface, comm_defs.INTERFACE_REGISTER_TABLE[interface] or {}, {}, {})
            port_device_obj = mdb.get_object(bus, port_device_path, interface)
            goto continue
        end
    end
    ::continue::
    if not port_device_obj then
        log:notice('get_port_id_by_device_path failed, port_device_path is %s', port_device_path)
        return
    end
    local port_id
    ret, port_id = port_device_obj:get_property('PortId')
    if ret ~= 0 then
        log:error('get_property failed, prop=PortId, port_device_path=%s', port_device_path)
        return
    end
    return port_id
end

local function create_port_orm_object(bus, port_device_path, card_node_id, card_object_name, card_orm_obj)
    log:notice('create_port_orm_object start, port_device_path is %s', port_device_path)
    local port_id = get_port_id_by_device_path(bus, port_device_path)
    if not port_id then
        log:error('create_port_orm_object failed, because port_id is nil, port_device_path is %s', port_device_path)
        return
    end
    local object_name = comm_fun.get_object_name_by_device_path(bus, port_device_path,
        {from = "NicPort", to = "NetworkPort"})
    if not object_name then
        log:error('create_port_orm_object failed, because object_name is nil, port_device_path is %s', port_device_path)
        return
    end
    local object_identifier = comm_fun.get_object_identifier_by_device_path(bus, port_device_path)
    if not object_identifier then
        log:error('create_port_orm_object failed, because object_identifier is nil, port_device_path is %s', port_device_path)
        return
    end
    local mdb_obj = c_network_port.create_mdb_object({
        PortID = port_id,
        NetworkAdapterId = card_node_id,
        ObjectName = object_name,
        ObjectIdentifier = object_identifier,
        CardObjectName = card_object_name,
        CreatedByDeviceObject = true -- 标识orm对象来源于设备树对象
    }, card_orm_obj)
    c_factory.get_instance():create_object('NetworkPort', mdb_obj)
    local port_orm_obj = c_network_port.collection:find({
        PortID = port_id,
        NetworkAdapterId = card_node_id
    })
    if port_orm_obj then
        log:notice('create_port_orm_object success, port_device_path is %s', port_device_path)
    else
        log:error('create_port_orm_object failed, port_device_path is %s', port_device_path)
    end
    return port_orm_obj
end

-- 应用层连接状态更新后，同步更新到设备树对象（用于iBMA获取到连接状态的场景）
function port_mgmt:set_link_status_to_device_obj(port_orm_obj, value)
    if not port_orm_obj.CreatedByDeviceObject then
        return
    end
    log:notice('%s set_link_status_to_device_obj start, value is %s', port_orm_obj.NodeId, value)
    local port_device_path = port_orm_obj.device_path
    if not port_device_path or port_device_path == '' then
        log:error('%s set_link_status_to_dev_obj failed, because port_device_path is nil or empty', port_orm_obj.NodeId)
        return
    end
    local devcie_obj = comm_fun.get_device_obj_by_path_interface(self.bus, port_device_path,
        comm_defs.NETWORK_PORT_LINK_INFO_INTERFACE)
    if not devcie_obj then
        log:error('%s set_link_status_to_dev_obj failed, because devcie_obj is nil, port_device_path is %s',
            port_orm_obj.NodeId, port_device_path)
        return
    end
    devcie_obj.LinkStatus = value
    log:notice('%s set_link_status_to_device_obj success, value is %s', port_orm_obj.NodeId, value)
end

-- 带宽利用率更新后，同步更新到设备树对象（用于iBMA获取到带宽利用率的场景）
function port_mgmt:set_bandwidth_usage_to_device_obj(port_orm_obj, value)
    if not port_orm_obj.CreatedByDeviceObject then
        return
    end
    log:notice('%s set_bandwidth_usage_to_device_obj start, value is %s', port_orm_obj.NodeId, value)
    local port_device_path = port_orm_obj.device_path
    if not port_device_path or port_device_path == '' then
        log:error('%s set_bandwidth_usage_to_dev_obj failed, because port_device_path is nil or empty', port_orm_obj.NodeId)
        return
    end
    local devcie_obj = comm_fun.get_device_obj_by_path_interface(self.bus, port_device_path,
        comm_defs.NETWORK_PORT_METRICS_INTERFACE)
    if not devcie_obj then
        log:error('%s set_bandwidth_usage_to_dev_obj failed, because devcie_obj is nil, port_device_path is %s',
            port_orm_obj.NodeId, port_device_path)
        return
    end
    devcie_obj.BandwidthPercent = value
    log:notice('%s set_bandwidth_usage_to_dev_obj success, value is %s', port_orm_obj.NodeId, value)
end

function port_mgmt:on_add_port_device_obj()
    log:notice('on_add_port_device_obj start')
    local self_instance = self
    comm_fun.set_interface_add_signal(self.bus, self.sig_slot, comm_defs.CARD_DEVICE_PATH_PATTERN,
        comm_defs.NETWORK_PORT_DEVICE_INTERFACE, function(path)
            self_instance:init_obj(path)
        end)
end

function port_mgmt:on_del_port_device_obj()
    log:notice('on_del_port_device_obj start')
    local self_instance = self
    comm_fun.set_interface_del_signal(self.bus, self.sig_slot, comm_defs.CARD_DEVICE_PATH_PATTERN,
        comm_defs.NETWORK_PORT_DEVICE_INTERFACE, function(path)
            self_instance:del_obj(path)
        end)
end

-- 光模块资源树对象创建后，注册到网口的子对象集合中
function port_mgmt:register_children(optical_module_orm_obj, port_orm_obj)
    table.insert(port_orm_obj.children, optical_module_orm_obj)
end

function port_mgmt:get_orm_obj_by_device_path(port_device_path)
    local port_orm_obj = self.objects[port_device_path]
    if not port_orm_obj then
        log:error('there is not orm obj for port device obj, port_device_path is %s', port_device_path)
        return
    end
    return port_orm_obj
end

local function register_parent(port_orm_obj, card_orm_obj)
    port_orm_obj.parent = card_orm_obj
    card_mgmt.get_instance():register_children(port_orm_obj, card_orm_obj)
end

function port_mgmt:init_obj(port_device_path)
    -- 用pcall包裹，避免某一个网口初始化失败导致循环结束
    pcall(function()
        if self.objects[port_device_path] then
            return
        end
        local card_device_path = comm_fun.get_parent_path(self.bus, port_device_path)
        if not card_device_path then
            log:error('there is not parent device obj for port, port_device_path is %s', port_device_path)
            return
        end
        local card_orm_obj = card_mgmt.get_instance():get_orm_obj_by_device_path(card_device_path)
        if not card_orm_obj then
            log:error('there is not parent orm obj for port, port_device_path is %s', port_device_path)
            return
        end
        local port_orm_obj = create_port_orm_object(self.bus, port_device_path,
            card_orm_obj.NodeId, card_orm_obj.ObjectName, card_orm_obj)
        if port_orm_obj then
            self.objects[port_device_path] = port_orm_obj
            -- 保存device_path到ORM对象中，供后续使用
            port_orm_obj.device_path = port_device_path
            -- 先启监听设备树对象属性变化，更新对应的资源树对象属性
            listen_device_obj_property_change(self.bus, self.sig_slot, port_device_path, port_orm_obj)
            -- 再将设备树对象属性赋值给对应资源树对象
            synchronize_property(self.bus, port_device_path, port_orm_obj)
            -- 网口资源树对象创建后，建立和网卡对象的父子关系
            register_parent(port_orm_obj, card_orm_obj)
            -- 网口的版本号来源于网卡，初始化时需赋值一次
            port_orm_obj.FirmwareVersion = card_orm_obj.FirmwareVersion
            port_init.init(port_orm_obj, self.bus)
        end
    end)
end

function port_mgmt:ctor(bus)
    self.bus = bus
    self.objects = {}  -- 网口设备树对象和资源树对象的映射关系
    self.sig_slot = {} -- 存放设备树对象相关信号
end

function port_mgmt:init()
    log:notice('port_mgmt init')
    -- 先监听网口设备树对象新增信号
    self:on_add_port_device_obj()
    self:on_del_port_device_obj()
    -- 获取所有的网口设备树对象，创建对应的orm对象
    local port_device_objs = comm_fun.get_all_device_paths(self.bus, comm_defs.CARD_DEVICE_PATH_PATTERN, 3,
        comm_defs.NETWORK_PORT_DEVICE_INTERFACE)
    for _, port_device_path in pairs(port_device_objs) do
        log:notice('get port_device_path: %s', port_device_path)
        self:init_obj(port_device_path)
    end
end

function port_mgmt:del_obj(path)
    log:notice('del_obj start, optical_device_path is %s', path)
    local orm_obj = self.objects[path]
    c_network_port.collection:del_object(orm_obj)
    self.objects[path] = nil
end

return singleton(port_mgmt)
