-- 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 c_object = require 'mc.orm.object'
local c_tasks = require 'mc.orm.tasks'
local log = require 'mc.logging'
local c_optical_module = require 'device.class.optical_module'
local custom_messages = require 'messages.custom'
local skynet = require 'skynet'
local npu_imu_cmd = require 'npu.hdk_cmd'
local c_object_manage = require 'mc.orm.object_manage'
local c_vlan = require 'device.class.vlan'
local class_mgnt = require "mc.class_mgnt"
local utils_core = require 'utils.core'
local utils = require 'mc.utils'
local fructl = require 'infrastructure.fructl'
local signal = require 'mc.signal'
local common_def = require 'common.common_def'
local str_hash = utils_core.str_hash

local BUSINESSPORT_CARD_TYPE_VIR<const> = 6
local BUSINESSPORT_CARD_TYPE_TEAM<const> = 7
local BUSINESSPORT_CARD_TYPE_BRIDGE<const> = 8
local MAC_ADDR_DEFAULT<const> = '00:00:00:00:00:00'
local INVALID_DATA_STRING<const> = 'N/A'

local c_network_port = c_object('NetworkPort')

-- 根据网卡初始化网口的类型
local function init_port_type(network_adapter, network_port)
    if network_adapter.Type ~= common_def.network_adapter_type.OCP then
        network_port.Type = network_adapter.Type
        return
    end
    -- 如果是OCP卡需区分OCP1和OCP2
    if network_adapter.SlotNumber == 1 then
        network_port.Type = common_def.network_port_type.OCP1
    else
        network_port.Type = common_def.network_port_type.OCP2
    end
end

-- 框架收到自发现分发的对象后，在回调 on_add_object 函数前会装载持久化数据回对象，但是
-- 网口的主键依赖其所属的网卡对象，必须在这里填充好 NetworkPort 对象的主键值，确保框架从
-- 持久化能找回正确的持久化数据
function c_network_port.before_add_object(object)
    local parent = object:get_parent()
    -- 网口必须依赖其父对象网卡上树
    while not parent or parent.NodeId == '' do
        skynet.sleep(100)
        parent = object:get_parent()
    end
    init_port_type(parent, object)
    object.NetworkAdapterId = parent.NodeId
    object.NodeId =  parent.NodeId .. (object.PortID + 1)
    return false
end

local lldp_receive_mds_name_map<const> = {
    ChassisId = 'chassis_id',
    ChassisIdSubtype = 'chassis_id_subtype',
    PortId = 'port_id',
    PortIdSubtype = 'port_id_subtype',
    SystemName = 'system_name',
    ManagementVlanId = 'vlan_id'
}

local default_lldp_receive_data<const> = {
    ChassisId = '',
    ChassisIdSubtype = '',
    PortId = '',
    PortIdSubtype = '',
    SystemName = '',
    ManagementVlanId = 0xFFFF
}

function c_network_port:reset_port_prop_without_bma()
    for k, v in pairs(self.t_port_prop_without_bma) do
        -- 当前表中仅有LinkStatus,后续拓展需判空
        self[k] = v
    end
end

-- 从bma获取的连接状态为NoLink、LinkUp、LinkDown，其他途径获取的状态为Connected、Disconnected
local linkstatus_from_bma<const> = {
    "LinkUp",
    "NoLink",
    "LinkDown"
}

function c_network_port:is_linkstatus_from_bma(link_status)
    local ok, ret = pcall(function()
        return utils.array_contains(linkstatus_from_bma, link_status)
    end)
    if not ok then
        log:error('check linkstatus from bma failed,err = %s', ret)
        return false
    end
    return ret
end

function c_network_port:set_ncsi_config_obj(ncsi_config_obj)
    self.ncsi_config_obj = ncsi_config_obj
end

function c_network_port:get_port_id()
    return self.PortID
end

function c_network_port:set_op_temp(temp)
    local op = self:get_optical_module()
    if op then
        op:set_temp(temp)
    end
end

local function update_network_port_lldp_receive(port, data)
    for k, v in pairs(lldp_receive_mds_name_map) do
        port[k] = data[v] or default_lldp_receive_data[k]
    end
end

function c_network_port:update_lldp_receive(lldp_rsp)
    -- 先清除clear task，根据获取的新的ttl创建新的clear task
    if self.lldp_clear_task then
        self.lldp_clear_task:cancel()
        self.lldp_clear_task = false
    end

    -- 如果收到ttl为0的数据，则代表数据不再有效，重置资源树属性到默认值
    if lldp_rsp.parsed_data.ttl == 0 then
        update_network_port_lldp_receive(self, {})
        return
    end
    update_network_port_lldp_receive(self, lldp_rsp.parsed_data)

    -- 根据ttl创建延时任务，到ttl秒后重置资源树属性到默认值
    self.lldp_clear_task = self:timeout_ms(lldp_rsp.parsed_data.ttl * 1000, function()
        update_network_port_lldp_receive(self, {})
        self.lldp_clear_task = false
    end)
end

-- 若网卡支持接收lldp，则默认在初始化时打开lldp开关
-- 后续可由redfish开关lldp
function c_network_port:enable_lldp()
    self:timeout_ms(1000, function()
        if not next(self.ncsi_config_obj) then
            return true
        end
        self:set_LLDP_capability(true)
    end)
end

function c_network_port:set_firmware_version(firmware_version)
    self.FirmwareVersion = firmware_version
end

function c_network_port:get_firmware_version()
    return self.FirmwareVersion
end

function c_network_port:set_permanent_mac_addr(addr)
    self.PermanentMACAddress = addr
end

function c_network_port:set_mac_addr(addr)
    self.MACAddress = addr
end

local link_status_numeric_table = {
    ['NoLink'] = 0,
    ['LinkDown'] = 0,
    ['LinkUp'] = 1,
    ['Disconnected'] = 0,
    ['Connected'] = 1,
    ['N/A'] = 255
}

function c_network_port:set_link_status_numeric(link_status)
    -- 加pcall规避部分nic卡和其他网卡关于LinkStatusNumeric属性的sr配置差异
    pcall(function ()
        if link_status ~= 'Disconnected' and link_status ~= 'Connected' and
            link_status ~= 'NoLink' and link_status ~= 'LinkDown' and
            link_status ~= 'LinkUp' and link_status ~= 'N/A' then
            return
        end
        local link_status_numeric = link_status_numeric_table[link_status]
        if self.LinkStatusNumeric == link_status_numeric then
            return
        end
        self.LinkStatusNumeric = link_status_numeric
        log:notice('%s link_status_numeric change to %s', self.NodeId, link_status_numeric)
    end)
end

function c_network_port:set_link_status(status)
    if not self:is_linkstatus_from_bma(self.LinkStatus) then
        self.LinkStatus = status
    end
    self.t_port_prop_without_bma.LinkStatus = status -- 用于重置bma时恢复为其他途径获取的状态
end

function c_network_port:send_cmd(command_code, bin_data)
    if next(self.ncsi_config_obj) == nil then
        log:error('mctp not start')
        return
    end
    local resp_data = self.ncsi_config_obj:Test({
        packet_type = command_code,
        data = bin_data,
        channel_id = self:get_port_id(),
        package_id = self.package_id
    }):value()
    return resp_data
end

function c_network_port:update_ncsi_port_stats()
    local s = self.ncsi_config_obj:PortMetrics({channel_id = self:get_port_id(), package_id = self.package_id})
    self:connect_signal(s.on_data_change, function(data)
        for name in pairs(data) do
            self[name] = tostring(data[name])
        end
    end)
    table.insert(self.schedulers, s)
    s:start()
end

function c_network_port:update_ncsi_link_status()
    local s = self.ncsi_config_obj:LinkStatus({channel_id = self:get_port_id(), package_id = self.package_id})
    s.on_data_change:on(function(data)
        if not self.get_ncsi_link_status_succeess then
            log:notice('update ncsi link status to %s, card:%s, portid:%s',
                data.LinkStatus, self.NetworkAdapterId, self.PortID)
            self.get_ncsi_link_status_succeess = true
        end
        self:set_link_status(data.LinkStatus)
        self:set_link_status_numeric(data.LinkStatus)
        self.SpeedMbps = data.SpeedMbps
        self.AutoSpeedNegotiation = data.AutoSpeedNegotiation
        self.FullDuplex = data.FullDuplex
        self.ncsi_get_linkstatus_retry_count = 0
    end)
    -- 消息发不通的时候，更新网口状态。
    s.on_error:on(function()
        self.ncsi_get_linkstatus_retry_count = self.ncsi_get_linkstatus_retry_count + 1
        if self.ncsi_get_linkstatus_retry_count > 2 then
            if self.get_ncsi_link_status_succeess then
                log:notice('get ncsi link status failed, set link_status to N/A, card:%s, port_id: %s',
                    self.NetworkAdapterId, self.PortID)
                self.get_ncsi_link_status_succeess = false
            end
            self:set_link_status(INVALID_DATA_STRING)
            self:set_link_status_numeric(INVALID_DATA_STRING)
            self.SpeedMbps = 0
            self.AutoSpeedNegotiation = false
            self.FullDuplex = false
            self.ncsi_get_linkstatus_retry_count = 0
        end
    end)

    table.insert(self.schedulers, s)
    s:start()
end

function c_network_port:initialize_ncsi_channel()
    local ret = self.ncsi_config_obj:InitializeNCSIChannel({
        channel_id = self:get_port_id(),
        package_id = self.package_id
    }):value()
    if not ret then
        log:error('unable to initialize NCSI channel, channel_id: %d', self:get_port_id())
    end
end

function c_network_port:update_BDF_by_ncsi()
    local s = self.ncsi_config_obj:BDF({
        channel_id = self:get_port_id(), -- port id
        extra_cmd = self:get_port_id(), -- port id
        package_id = self.package_id
    }):value()
    if s and (s.bus ~= 0 or s.device ~= 0 or s.func ~= 0) and self.WorkloadType ~= 1 then
        self.BDF = string.format('%04x:%02x:%02x.%01x', 0, s.bus, s.device, s.func)
        log:notice('update_BDF_by_ncsi %s bdf:%s', self.NodeId, self.BDF)
    else
        log:debug('update_BDF_by_ncsi %s failed', self.NodeId)
        self:next_tick(function()
            self:sleep_ms(60000)
            self:update_BDF_by_ncsi()
        end)
    end
end

function c_network_port:update_MAC_by_ncsi()
    local parent = self:get_parent()
    local mac_addr
    if parent and parent.Model == 'BroadCom' then
        mac_addr = self.ncsi_config_obj:MacAddrNCSI({
            channel_id = self:get_port_id(), -- port id
            package_id = self.package_id
        }):value()
    else
        mac_addr = self.ncsi_config_obj:MacAddrNCSI({
            channel_id = self:get_port_id(), -- port id
            extra_cmd = self:get_port_id(), -- port id
            package_id = self.package_id
        }):value()
    end
    if mac_addr then
        self.MACAddress = mac_addr
        log:info('get mac address: %s of port[%d]', self.MACAddress, self:get_port_id())
    end
end

function c_network_port:update_default_MAC_by_ncsi()
    local parent = self:get_parent()
    local default_mac_addr
    if parent and parent.Model == 'BroadCom' then
        default_mac_addr = self.ncsi_config_obj:DefaultMacAddrNCSI({
            channel_id = self:get_port_id(), -- port id
            package_id = self.package_id
        }):value()
    else
        default_mac_addr = self.ncsi_config_obj:DefaultMacAddrNCSI({
            channel_id = self:get_port_id(), -- port id
            extra_cmd = self:get_port_id(), -- port id
            package_id = self.package_id
        }):value()
    end
    self.PermanentMACAddress = default_mac_addr
    log:info('get permanent mac address: %s of port[%d]', self.PermanentMACAddress,
        self:get_port_id())

    -- 判断工作mac地址是否有效，无效则用默认的永久地址进行刷新
    if self.MACAddress == MAC_ADDR_DEFAULT or self.MACAddress == INVALID_DATA_STRING then
        self.MACAddress = self.PermanentMACAddress
        log:info('update mac address: %s of port[%d]', self.MACAddress, self:get_port_id())
    end
end

function c_network_port:update_DCBX_by_ncsi()
    local s = self.ncsi_config_obj:DCBX({
        channel_id = self:get_port_id(), -- port id
        package_id = self.package_id
    }):value()
    if not s then
        return
    end
    for k, v in pairs(s) do
        self[k] = v
    end
    log:info('the dcbx info, up2cos:%s, up_pgid:%s, pgpct:%s, strict:%s, pfcmap:%s', self.Up2cos,
        self.Uppgid, self.Pgpct, self.PgStrict, self.Pfcmap)
end

-- 使能网口LLDP是通过业务组件发送property_change信号进行触发的
function c_network_port:set_LLDP_capability(enable)
    local ret

    -- 现阶段lldpEnabled跟新的状态一致，无需操作直接返回
    if self.LLDPEnabled == enable then
        return
    end

    if enable then
        ret = self.ncsi_config_obj:SetOnLLDPCapability({channel_id = self:get_port_id(), package_id = self.package_id})
            :value()
    else
        ret = self.ncsi_config_obj:SetOffLLDPCapability({channel_id = self:get_port_id(), package_id = self.package_id})
            :value()
    end

    -- 网口不支持lldp使能
    if not ret then
        log:error('unable to set LLDP capability for port %s', self:get_port_id())
        error(custom_messages.ConfigPortLLDPErr())
    end
    self.ncsi_config_obj:ResetNic({package_id = self.package_id}):value()
    self.LLDPEnabled = enable
    log:info('set port %d LLDP to %s', self:get_port_id(), enable and 'enable' or 'disable')
end

function c_network_port:update_LLDP_enabled_by_ncsi()
    local s = self.ncsi_config_obj:GetLLDPCapability({channel_id = self:get_port_id(), package_id = self.package_id})
    self:connect_signal(s.on_data_change, function(data)
        self.LLDPEnabled = data
    end)
    table.insert(self.schedulers, s)
    s:start()
end

function c_network_port:set_package_id(package_id)
    self.package_id = package_id
end

function c_network_port:update_ncsi_properties()
    self:next_tick(self.update_ncsi_port_stats, self)
    self:next_tick(self.update_ncsi_link_status, self)
    self:next_tick(self.update_BDF_by_ncsi, self)
    self:next_tick(self.update_MAC_by_ncsi, self)
    self:next_tick(self.update_DCBX_by_ncsi, self)
    self:next_tick(self.update_default_MAC_by_ncsi, self)
    self:next_tick(self.update_LLDP_enabled_by_ncsi, self)

    local op = self:get_optical_module()
    if op then
        op:update_ncsi_properties(self.ncsi_config_obj, self.package_id)
    end
end

function c_network_port:get_optical_module()
    return c_optical_module.collection:find({
        PortID = self.PortID,
        NetworkAdapterId = self.NetworkAdapterId
    })
end

function c_network_port:update_link_status(link_status)
    local op = self:get_optical_module()
    if not op then
        return
    end

    local fault_state = op:get_fault_state()
    op:set_link_status(link_status)
    local power_state_flag = (((fault_state & 771) ~= 0) and (link_status ~= 'LinkDown') and
                                 (link_status ~= INVALID_DATA_STRING))
    op:set_power_state((power_state_flag and 1 or 0))
end

function c_network_port:synchronize_link_status(link_status)
    local status = link_status == 1 and 'Connected' or 'Disconnected'
    self:set_link_status(status)
end

function c_network_port:update_ipv6_default_gateway(ipv6_addresses)
    local first_ipv6_gateway = ''
    for _, addr in ipairs(ipv6_addresses) do

        -- 获取第一个ipv6 gateway，它就是default_ipv6_gateway
        if #addr.Gateway > 0 and first_ipv6_gateway == '' then
            first_ipv6_gateway = addr.Gateway[1]
        end
    end
    self.IPv6DefaultGateway = first_ipv6_gateway
end

function c_network_port:reset_bma_info()
    self:reset_port_prop_without_bma()
    self.Name = ''
    self.WorkMode = 'NonLoop'
    self.DriverName = ''
    self.DriverVersion = ''
    if not next(self.ncsi_config_obj) or self.ncsi_config_obj.LinkStatus then
        self.SpeedMbps = 0
        self.AutoSpeedNegotiation = false
    end
    if self.PermanentMACAddress and self.PermanentMACAddress ~= MAC_ADDR_DEFAULT and
        self.PermanentMACAddress ~= INVALID_DATA_STRING then
        log:notice('NetworkPort(%sPort%s) macaddr reset from permanent mac(%s) complete',
            self.NetworkAdapterId, self.PortID, self.PermanentMACAddress)
        self.MACAddress = self.PermanentMACAddress
    end
    log:debug('NetworkPort(%sPort%s) reset from bma complete', self.NetworkAdapterId, self.PortID)

    -- FC卡相关属性
    if self.FCId and #self.FCId > 0 then
        self.WWPN = ''
        self.WWNN = ''
        self.SpeedGbps = 0
        self.FCId = ''
        self.FirmwareVersion = ''
    end
end

function c_network_port:reset_bma_stats()
    if not next(self.ncsi_config_obj) or self.ncsi_config_obj.PortMetrics then
        self.RXFrames = ''
        self.TXFrames = ''
    end
    self.BandwidthUsagePercent = 0
    log:debug('NetworkPort(%sPort%s) stats reset from bma complete', self.NetworkAdapterId,
        self.PortID)
end

local property_changed_switch<const> = {
    LinkStatus = c_network_port.update_link_status,
    LinkStatusNumeric = c_network_port.synchronize_link_status,
    LLDPEnabled = c_network_port.set_LLDP_capability,
    PermanentMACAddress = c_network_port.emit_mac_change_signal
}

function c_network_port:emit_mac_change_signal(value)
    self.mac_change:emit()
end

function c_network_port:register_property_changed_callback()
    local link_status = self.LinkStatusNumeric == 1 and 'Connected' or
        self.LinkStatusNumeric == 0 and 'Disconnected' or INVALID_DATA_STRING
    self:set_link_status(link_status)
    self:connect_signal(self.on_property_changed, function(name, value)
        if property_changed_switch[name] then
            self:next_tick(property_changed_switch[name], self, value)
        end
    end)
end

function c_network_port:start_npu_link_status_listening()
    self:connect_signal(self.on_property_changed, function(name, value)
        if name ~= 'LinkStatusValue' then
            return
        end
        self.LinkStatus = value == 1 and 'Connected' or 'Disconnected'
        -- 判断上下电状态和复位信号，非上电状态或者复位时，不更新 numberic
        if fructl.get_power_status() ~= 'ON' or fructl.get_system_reset_flag() == 1 then
            return
        end
        -- 使用LinkStatusNumeric表示告警状态：初始值（255） -> down(2) -> up(1) -> down(0)
        -- 出现down，LinkStatusNumeric被更新为2 或者 0
        -- 出现up, LinkStatusNumeric被更新为1
        if value == 0 then
            if self.LinkStatusNumeric == 255 then
                self.LinkStatusNumeric = 2
                log:notice("npu port%s first link down", self.PortID)
                return
            end
            if self.LinkStatusNumeric == 1 then
                self.LinkStatusNumeric = 0
                log:notice("npu port%s link down", self.PortID)
            end
        end
        if value == 1 then
            self.LinkStatusNumeric = 1
            log:notice("npu port%s link up", self.PortID)
        end
    end)
end

function c_network_port:register_npu_changed_callback()
    self:start_npu_link_status_listening()
    self.LinkStatus = self.LinkStatusValue == 1 and 'Connected' or 'Disconnected'
    -- 启动监听后，如果是正常上电状态，先同步一次属性值，防止组件异常复位后，无信号变更触发，导致资源树值为初始值，与实际硬件值不同。
    if fructl.get_power_status() == 'ON' and fructl.get_system_reset_flag()  == 0 then
        if self.LinkStatusValue == 1 then
            self.LinkStatusNumeric = 1
        else
            self.LinkStatusNumeric = 2
        end
        log:notice("npu port%s link status is to %s", self.PortID, self.LinkStatus)
    end
end

--MPU恢复空闲则发送一次性信息获取指令
function c_network_port:resume_fetch_properties()
    self:next_tick(self.update_BDF_by_ncsi, self)
    self:next_tick(self.update_MAC_by_ncsi, self)
    self:next_tick(self.update_DCBX_by_ncsi, self)
    self:next_tick(self.update_default_MAC_by_ncsi, self)
end

-- MPU繁忙时暂停所有的轮询操作
function c_network_port:pause()
    log:notice("pause port schedulers ,size of port schedulers is %s", #self.schedulers)
    for _, s in ipairs(self.schedulers) do
        s:pause()
    end
    -- 暂停对应光模块的轮询操作
    local op = self:get_optical_module()
    if op then
        op:pause()
    end
end

-- MPU空闲时恢复所有的轮询操作
function c_network_port:resume()
    log:notice("resume port schedulers ,size of port schedulers is %s", #self.schedulers)
    self:resume_fetch_properties()
    for _, s in ipairs(self.schedulers) do
        s:resume()
    end
    -- 恢复对应光模块的轮询操作
    local op = self:get_optical_module()
    if op then
        op:resume()
    end
end

-- 卸载时停止所有的轮询操作
function c_network_port:stop()
    for _, s in ipairs(self.schedulers) do
        s:deconstruct()
    end
    self.schedulers = {}
    -- 停止对应光模块的轮询操作
    local op = self:get_optical_module()
    if op then
        op:stop()
    end

    -- 网口对象卸载时，需要把其子路径上VLANs及ipv4、ipv6均卸载
    local vlans = c_vlan.collection:fetch({PortID = self.PortID, NetworkAdapterId = self.NetworkAdapterId})
    for _, vlan in ipairs(vlans) do
        vlan:reset()
        c_vlan.collection:del_object(vlan)
    end

    class_mgnt('VLANs'):remove(self.vlans)

    -- 卸载时确保数据库不操作
    local db = c_object_manage.get_instance().per_db
    db.db:flush()
end

local old_dtor = c_network_port.dtor
function c_network_port:dtor()
    self:stop()
end

function c_network_port:destroy()
    self:dtor()
    old_dtor(self)
end

function c_network_port:ctor()
    self.schedulers = {}
    self.lldp_config_obj = {}
    self.ncsi_config_obj = {}
    self.package_id = 0
    self.na_object_name = ''
    self.np_object_name = ''
    self.lldp_clear_task = false
    self.tasks = c_tasks.new()
    self.vlans = false
    self.get_ncsi_link_status_succeess = false
    self.npu_ipv4_info = {}
    self.npu_ipv4_obj = false
    -- 此表用于临时保存非bma获取的属性，用于bma断开后恢复，当前仅LinkStatus使用
    self.t_port_prop_without_bma = {}
    self.ncsi_get_linkstatus_retry_count = 0
    self.is_npu_heartbeat_loss = false
    self.mac_change = signal.new()
end

local function is_virtual_port(type)
    if type == BUSINESSPORT_CARD_TYPE_VIR or
       type == BUSINESSPORT_CARD_TYPE_BRIDGE or
       type == BUSINESSPORT_CARD_TYPE_TEAM then
        return true
    end
    return false
end

function c_network_port:update_mac_addr_from_db()
    local card = self:get_parent()
    -- 不支持热插拔的网卡无需从数据库中恢复mac地址
    if not card or not card.HotPluggable then
        return
    end

    local db = c_object_manage.get_instance().per_db
    local network_port = db:select(db.NetworkPort):where({PortID = self.PortID,
        NetworkAdapterId = self.NetworkAdapterId}):first()
    local times = 0

    while not network_port and times < 20 do
        network_port = db:select(db.NetworkPort):where({PortID = self.PortID,
            NetworkAdapterId = self.NetworkAdapterId}):first()
        self:sleep_ms(1000)
        times = times + 1
    end

    if network_port then
        log:notice('set macaddr from database, mac = %s, port = %s', network_port.MACAddress, self.PortID)
        self.MACAddress = network_port.MACAddress
        self.PermanentMACAddress = network_port.MACAddress
    else
        log:notice('can not find macaddr from database, port = %s', self.PortID)
    end
end

function c_network_port:init_id()
    local retry_times = 0

    -- bridge 不通过devicelocator生成ID信息
    if is_virtual_port(self.Type) then
        return
    end
    local parent = self:get_parent()
    while not parent or parent.NodeId == '' and retry_times < 10 do
        retry_times = retry_times + 1
        self.tasks:sleep_ms(1000)
    end
    self.NetworkAdapterId = parent.NodeId
    self.NodeId =  parent.NodeId .. (self.PortID + 1)
    self.t_port_prop_without_bma = {
        LinkStatus = INVALID_DATA_STRING
    }
    self:register_mdb_objects()
end

function c_network_port:get_npu_cdr_temp_from_imu()
    local temp = 0
    local cdr_manu = ""
    if self.is_npu_heartbeat_loss then
        if log:getLevel() >= log.DEBUG then
            log:debug('[NPU] npu %s heartbeat loss, pause get npu port info', self.NpuID)
        end
        return temp
    end
    local ok, err = pcall(function()
        temp, cdr_manu = npu_imu_cmd.get_npu_cdr_temp_from_imu(self.NpuID, self.PowerOn)
    end) 
    if not ok then
        if log:getLevel() >= log.DEBUG then
            log:debug('[NPU] npu %s get cdr temp fail, err: %s', self.NpuID, err)
        end
    end
    if log:getLevel() >= log.DEBUG then
        log:debug('[NPU] npu %s cdr temp is %s', self.NpuID, temp)
    end
    return temp, cdr_manu
end

function c_network_port:init()
    self:init_id()
    c_network_port.super.init(self)
    local parent = self:get_parent()
    if parent and string.find(parent.NodeId, 'ROH') then
        return
    end
    -- 网口必须依赖其父对象网卡上树
    if parent and parent.Model == 'NPU' then
        log:error('[npuport]port model = %s', parent.Model)
        log:error('NPU%s port init start', parent.SlotNumber)
        self:next_tick(function ()
            self:update_npu_heartbeat_status()
            self:get_info_from_imu()
        end)
        -- npu虚拟网卡开启网口状态监控
        self:connect_signal(self.on_add_object_complete, function()
            self:register_npu_changed_callback()
        end)
    else
        self.na_object_name, self.np_object_name = self.path:match('([^/]+)/Ports/([^/]+)')
        self.vlans = c_object_manage.get_instance().app:CreateVLANs(1, self.na_object_name, self.np_object_name,
            function (obj)
                obj.NetworkPortObjectName = self.na_object_name .. self.np_object_name
                local mdb_object = obj:get_mdb_object('bmc.kepler.Systems.NetworkPort.VLANs')
                rawset(mdb_object, 'get_parent', function ()
                return self
            end)
        end)

        self:connect_signal(self.on_add_object_complete, function()
            self:register_property_changed_callback()
        end)
        self:next_tick(self.update_mac_addr_from_db, self)
    end
end

function c_network_port:update_npu_heartbeat_status()
    log:notice('[NPU] update_npu_heartbeat_status task start, id = %s', self.NpuID)
    local op
    skynet.fork_loop({count = 0}, function()
        while true do
            self:sleep_ms(15000)
            if fructl.get_power_status() ~= 'ON' or self.PowerOn ~= 1 then
                log:debug('[NPU] npu is power off')
                goto next
            end
            self.is_npu_heartbeat_loss = npu_imu_cmd.heartbeat_check(self.NpuID)
            op = self:get_optical_module()
            if op then
                op:set_npu_heartbeat_loss_status(self.is_npu_heartbeat_loss)
            end
            ::next::
        end
    end)
end

function c_network_port:clear_npu_port_basic_info()
    self.RXFrames = ''
    self.TXFrames = ''
    self.PacketsDropped = 0
    self.MACAddress = MAC_ADDR_DEFAULT
    self.RXFCSErrors = ''
    if self.npu_ipv4_obj then
        class_mgnt('IPv4Address'):remove(self.npu_ipv4_obj)
        self.npu_ipv4_info = {
            Address = INVALID_DATA_STRING,
            SubnetMask = INVALID_DATA_STRING,
            Gateway = INVALID_DATA_STRING
        }
    end
    log:debug('[NPU] clear npu %s port basic info success', self.NpuID)
end

function c_network_port:get_npu_port_basic_info()
    local app, ip_addr, info, adapter_object_name, port_object_name, ipv4

    app = c_object_manage.get_instance().app
    info = npu_imu_cmd.get_info_from_imu(self.NpuID, self.PowerOn)

    -- 更新mac和统计信息
    self.RXFrames = info.received
    self.TXFrames = info.transmitted
    self.PacketsDropped = info.dropped
    self.MACAddress = info.mac_addr
    self.RXFCSErrors = info.rx_fcs_err_pkt_num
    log:info('[NPU] npu%s ipv4 =%s, gateway= %s, netmask =%s',
        self.NpuID, info.ip_addr, info.gateway, info.subnet_mask)
    -- npu仅能获取一个ipv4信息，当ip网关掩码均不变时不做资源树对象操作
    if self.npu_ipv4_obj and self.npu_ipv4_info.Address == info.ip_addr and
        self.npu_ipv4_info.SubnetMask == info.subnet_mask and
        self.npu_ipv4_info.Gateway == info.gateway then
        log:info('[NPU] npu%s ipv4 info not change', self.NpuID)
        return
    end

    -- 非首次上树ipv4对象时，需要先下树后再上树
    if self.npu_ipv4_obj then
        class_mgnt('IPv4Address'):remove(self.npu_ipv4_obj)
        self.npu_ipv4_info = { Address = INVALID_DATA_STRING, SubnetMask = INVALID_DATA_STRING,
            Gateway = INVALID_DATA_STRING }
        log:info('[NPU] clear npu%s ipv4 info success', self.NpuID)
    end

    -- 当ip网关掩码均为无效值时不上树
    if info.ip_addr == INVALID_DATA_STRING and info.subnet_mask == INVALID_DATA_STRING and
        info.gateway == INVALID_DATA_STRING then
        log:info('[NPU] npu%s ipv4 info is invalid', self.NpuID)
        return
    end

    -- 更新ipv4缓存信息并上树
    adapter_object_name = string.format('NetworkAdapter_1_%s', self:get_position())
    port_object_name = string.format('NetworkPort_%02d_%s', (self.PortID + 1), self:get_position())
    self.npu_ipv4_info.Address = info.ip_addr
    self.npu_ipv4_info.SubnetMask = info.subnet_mask
    self.npu_ipv4_info.Gateway = info.gateway
    self.npu_ipv4_obj = app:CreateIPv4Address(1, adapter_object_name, port_object_name,
        str_hash(info.ip_addr .. info.subnet_mask .. info.gateway), function(obj)
            obj.Address = info.ip_addr
            obj.SubnetMask = info.subnet_mask
            obj.Gateway = info.gateway
            obj.NetworkAdapterObjectName = adapter_object_name
            obj.NetworkPortObjectName = port_object_name
            obj.PortID = self.PortID
            obj.NetworkAdapterId = self.NetworkAdapterId
        end)
    log:info('[NPU] npu%s update ipv4 info success', self.NpuID)
end

function c_network_port:get_npu_port_info()
    -- 与hdk通信失败或下电时，清空已有信息
    if self.is_npu_heartbeat_loss or fructl.get_power_status() ~= 'ON' or self.PowerOn ~= 1 then
        log:debug('[NPU] npu %s heartbeat loss, pause get npu port basic info', self.NpuID)
        self:clear_npu_port_basic_info()
        return
    end

    local ok, err = pcall(function()
        self:get_npu_port_basic_info()
    end)
    if not ok then
        log:error('[NPU] get npu%s port basic info from imu fail, err: %s', self.NpuID, err)
    end
    -- 整个轮询周期为600秒
    self:sleep_ms(600000 - 60000)
end

function c_network_port:get_info_from_imu()
    self.npu_ipv4_info = {
        Address = INVALID_DATA_STRING,
        SubnetMask = INVALID_DATA_STRING,
        Gateway = INVALID_DATA_STRING
    }
    skynet.fork_loop({count = 0}, function()
        local ok, err
        while true do
            self:get_npu_port_info()
            self:sleep_ms(60000)
        end
    end)
end

local function generate_object_name(type, port_id)
    if type == BUSINESSPORT_CARD_TYPE_VIR then
        return "VirtaulPort_" .. (port_id + 1)
    end
    if type == BUSINESSPORT_CARD_TYPE_BRIDGE then
        return "BridgePort_" .. (port_id + 1)
    end
    if type == BUSINESSPORT_CARD_TYPE_TEAM then
        return "TeamPort_" .. (port_id + 1)
    end
    return "Port_" .. (port_id + 1)
end

function c_network_port.create_mdb_object(value)
    local app = c_object_manage.get_instance().app
    local obj_name = generate_object_name(value.Type, value.PortID)
    return app:CreateNetworkPort(1, value.NetworkAdapterObjectName, obj_name, function (obj)
        log:debug("create network port id(%d), nodeid(%s), type(%d), BDF(%s)",
            value.PortID, value.NodeId, value.Type, value.BDF)
        obj.PortID = value.PortID
        obj.NodeId = value.NodeId
        obj.NetworkAdapterId = value.NetworkAdapterId
        obj.Type = value.Type
        obj.BDF = value.BDF
        obj.NetworkAdapterObjectName = value.NetworkAdapterObjectName
    end)
end

function c_network_port.insert_or_update(na_obj_name, port)
    local db_addr = c_network_port.collection:find(port)
    if db_addr then
        port = db_addr
    end
    port.NetworkAdapterObjectName = na_obj_name
    return db_addr or c_network_port.__table(port)
end

return c_network_port
