-- 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 log = require 'mc.logging'
local skynet = require 'skynet'
local class_mgnt = require "mc.class_mgnt"
local c_optical_module = require 'device.class.optical_module'
local c_object_manage = require 'mc.orm.object_manage'
local npu_imu_cmd = require 'npu.hdk_cmd'
local fructl = require 'infrastructure.fructl'
local utils_core = require 'utils.core'
local str_hash = utils_core.str_hash
local port_defs = require 'device.class.nic_mgmt.port.port_defs'

local port_npu = {}

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

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

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

local function get_npu_port_basic_info(orm_obj)
    local app, info, adapter_object_name, port_object_name

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

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

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

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

    -- 更新ipv4缓存信息并上树
    adapter_object_name = string.format('NetworkAdapter_1_%s', orm_obj:get_position())
    port_object_name = string.format('NetworkPort_%02d_%s', (orm_obj.PortID + 1), orm_obj:get_position())
    orm_obj.npu_ipv4_info.Address = info.ip_addr
    orm_obj.npu_ipv4_info.SubnetMask = info.subnet_mask
    orm_obj.npu_ipv4_info.Gateway = info.gateway
    orm_obj.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 = orm_obj.PortID
            obj.NetworkAdapterId = orm_obj.NetworkAdapterId
        end)
    log:info('[NPU] npu%s update ipv4 info success', orm_obj.NpuID)
end

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

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

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

local function start_npu_link_status_listening(orm_obj)
    orm_obj:connect_signal(orm_obj.on_property_changed, function(name, value)
        if name ~= 'LinkStatusValue' then
            return
        end
        orm_obj.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 orm_obj.LinkStatusNumeric == 255 then
                orm_obj.LinkStatusNumeric = 2
                log:notice("npu port%s first link down", orm_obj.PortID)
                return
            end
            if orm_obj.LinkStatusNumeric == 1 then
                orm_obj.LinkStatusNumeric = 0
                log:notice("npu port%s link down", orm_obj.PortID)
            end
        end
        if value == 1 then
            orm_obj.LinkStatusNumeric = 1
            log:notice("npu port%s link up", orm_obj.PortID)
        end
    end)
end

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

function port_npu.init(orm_obj)
    orm_obj:next_tick(function()
        update_npu_heartbeat_status(orm_obj)
        get_info_from_imu(orm_obj)
    end)
    -- npu虚拟网卡开启网口状态监控
    orm_obj:connect_signal(orm_obj.on_add_object_complete, function()
        register_npu_changed_callback(orm_obj)
    end)
end

return port_npu