-- 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 class = require 'mc.class'
local c_handler_base = require 'bma.handles.handler_base'
local c_network_port = require 'device.class.network_port'
local c_network_adapter = require 'device.class.network_adapter'
local c_ipv4_address = require 'device.class.ipv4_address'
local c_ipv6_address = require 'device.class.ipv6_address'
local log = require 'mc.logging'
local singleton = require 'mc.singleton'

local is_nil_or_null = c_handler_base.is_nil_or_null
local default_na = c_handler_base.default_na
local update_if_not_config = c_handler_base.update_if_not_config
local update_if_valid = c_handler_base.update_if_valid
local get_odata_id = c_handler_base.get_odata_id
local decode_bdf = c_handler_base.decode_bdf
local update_network_card_model = c_handler_base.update_network_card_model
local update_network_card_manufacturer = c_handler_base.update_network_card_manufacturer
local update_network_port_netdev_functype = c_handler_base.update_network_port_netdev_functype

local NETDEV_FUNCTYPE_FC<const> = 2
local PCIE_NCSI_TYPE<const> = 3

local c_handler_fc = class(c_handler_base)

function c_handler_fc:ctor()
end

function c_handler_fc:init()
    self:regist_odata_type('OemFC')

    self:regist_class_type(c_network_adapter)
    self:regist_class_type(c_network_port)

    c_network_port.on_add_object:on(function(object)
        if object.BDF and #object.BDF > 0 then
            -- 已存在 BDF 的网口直接刷新 BMA 资源
            self:match_resource(object)
        end

        -- 监听 BDF 变化，有些网口的 BDF 需要通过 NCSI 或其他协议动态获取
        object:listen('BDF', function(_, value)
            log:info('c_network_port: bdf update: bdf=%s, network adapter id=%s, port id=%s', value,
                object.NetworkAdapterId, object.PortID)

            -- 刷新 BMA 资源
            self:match_resource(object)
        end)
    end)
end

function c_handler_fc:find_object(_, data)
    if not data or not data.BDFNumber then
        return nil
    end
    -- 先通过 BDF 信息匹配
    local BDF = data.BDFNumber.BDF
    local object = c_network_port.collection:find(function(obj)
        return obj.BDF == BDF
    end)
    if object then
        return object
    end

    return self:find_port_by_na(data)
end

function c_handler_fc:find_port_by_na(data)
    local na = c_network_adapter.collection:find(decode_bdf(data.BDFNumber.RootBDF))
    if not na then
        return nil
    end

    local port_num = nil
    if na.SpecialPcieCard then
        port_num = self:get_special_pcie_network_port_num(data)
    elseif not is_nil_or_null(data.BDFNumber.PortNum) then
        port_num = data.BDFNumber.PortNum
    else
        port_num = decode_bdf(data.BDFNumber.BDF).Function
    end
    return c_network_port.collection:find({PortID = port_num, NetworkAdapterId = na.NodeId})
end

local function get_port_parent_card(port)
    local parent
    if port.CreatedByDeviceObject then
        parent = port.parent
    else
        parent = port:get_parent()
    end
    return c_network_adapter.collection:find({NodeId = parent.NodeId})
end

function c_handler_fc:get_special_pcie_network_port_num(data)
    -- 如果为特殊网卡，port_num从倒数第二级BDF取
    -- PCIePath：/sys/devices/pci0000:ae/0000:ae:02.0/0000:af:00.0/0000:b0:01.0/0000:b2:00.0
    local port_num_str = data.PCIePath:match(':(%w+).%w+%/[^/]+$')
    if not port_num_str then
        log:raise('c_handler_fc, match PCIePath.PortNum faild: PCIePath=%s', data.PCIePath)
    end

    local port_num = tonumber(port_num_str, 16)
    if not port_num then
        log:raise('c_handler_fc, invalid PCIePath.PortNum, PCIePath=%s, PortNum=%s',
            data.PCIePath, port_num_str)
    end

    return port_num
end

-- bma上报更新bmc的FC网卡（MEZZ、PCIE）的属性，BMC能预配置的不从BMA更新
function c_handler_fc:update_fc_network_card(port, data)
    local na = get_port_parent_card(port)
    if not na then
        log:error(
            'update network card from bma failed: path=%s, @odata.id=%s, cannot find network card',
            port.path, get_odata_id(data))
        return
    end

    log:info('update network card from bma: path=%s, @odata.id=%s', na.path, get_odata_id(data))
    update_network_card_model(na, data)
    update_network_card_manufacturer(na, data)

    -- 更新OS侧网卡固件版本
    na.FirmwareVersion = update_if_not_config(na.FirmwareVersion,
        default_na(data.FirmwareVersion))

    -- 更新OS侧网卡驱动名称
    if data.DriverInfo ~= nil and data.DriverInfo.DriverName ~= nil then
        na.DriverName = default_na(data.DriverInfo.DriverName)
    end

    -- 更新OS侧网卡驱动版本
    if data.DriverInfo ~= nil and data.DriverInfo.DriverVersion ~= nil then
        na.DriverVersion = default_na(data.DriverInfo.DriverVersion)
    end

    -- 更新OS侧网卡描述信息
    na.Description = update_if_not_config(na.Description, default_na(data.Description))

    -- 更新OS侧ROOTBDF
    if data.BDFNumber and data.BDFNumber.RootBDF then
        na.RootBDF = default_na(data.BDFNumber.RootBDF)
    end
end

-- 更新FC口对象中的属性值
function c_handler_fc:update_fc_network_port(port, data)
    -- 更新BMA ID信息 @odata.id AgentID
    port.AgentID = update_if_valid(port.AgentID, get_odata_id(data))

    -- 更新FC卡网口连接状态
    if data.PhysicalPort ~= nil and data.PhysicalPort.LinkStatus ~= nil then
        -- bma接口下PhysicalPort的LinkStatus仅有Online、LinkDown、None
        if data.PhysicalPort.LinkStatus == 'Online' then
            port.OSLinkStatus = 'LinkUp'
        elseif data.PhysicalPort.LinkStatus == 'Offline' then
            port.OSLinkStatus = 'LinkDown'
        else
            port.OSLinkStatus = default_na(data.PhysicalPort.LinkStatus)
        end
    end

    -- 更新网络设备功能类型
    update_network_port_netdev_functype(port, data, NETDEV_FUNCTYPE_FC)

    -- 更新FC口的WWN信息
    if data.WWN ~= nil and data.WWN.WWPN ~= nil then
        port.WWPN = default_na(data.WWN.WWPN)
    end
    if data.WWN ~= nil and data.WWN.WWNN ~= nil then
        port.WWNN = default_na(data.WWN.WWNN)
    end

    -- 更新fc id值
    port.FCId = update_if_valid(port.FCId, data.FC_ID)

    -- 更新端口类型
    port.Type = PCIE_NCSI_TYPE
    
    -- 更新端口名称
    if data.PhysicalPort ~= nil and data.PhysicalPort.PortName ~= nil then
        port.Name = default_na(data.PhysicalPort.PortName)
    end

    -- 更新端口链接速率
    if data.PhysicalPort ~= nil and data.PhysicalPort.LinkSpeed ~= nil then
        port.SpeedGbps = default_na(data.PhysicalPort.LinkSpeed)
    end
    
    -- 更新BDF信息
    if data.BDFNumber ~= nil and data.BDFNumber.BDF ~= nil then
        port.BDF = default_na(data.BDFNumber.BDF)
    end

    -- 更新OS侧网卡固件版本
    port.FirmwareVersion = update_if_valid(port.FirmwareVersion, data.FirmwareVersion)

    -- 更新OS侧网卡驱动名称
    if data.DriverInfo ~= nil and data.DriverInfo.DriverName ~= nil then
        port.DriverName = default_na(data.DriverInfo.DriverName)
    end

    -- 更新OS侧网卡驱动版本
    if data.DriverInfo ~= nil and data.DriverInfo.DriverVersion ~= nil then
        port.DriverVersion = default_na(data.DriverInfo.DriverVersion)
    end
end

function c_handler_fc:add(_, data, object)
    self:update_fc_network_card(object, data)
    self:update_fc_network_port(object, data)
end

function c_handler_fc:update(_, data, object)
    self:update_fc_network_card(object, data)
    self:update_fc_network_port(object, data)
end

function c_handler_fc:reset_network_port(port)
    port:reset_bma_info()
    local na = get_port_parent_card(port)
    if not na then
        log:debug('reset_network_port failed: path=%s, cannot find network card', port.path)
        return
    end
    na:reset_bma_info()
end

function c_handler_fc:reset()
    for object, _ in pairs(self.objects) do
        self:reset_network_port(object)
    end

    self.objects = {}
end

function c_handler_fc:delete(_, _, object)
    self:reset_network_port(object)
    self.objects[object] = nil
end

return singleton(c_handler_fc)
