-- 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 mdb_service = require 'mc.mdb.mdb_service'
local log = require 'mc.logging'

local ETH_OBJ_PATH<const> = '/bmc/kepler/Managers/1/EthernetInterfaces'
local ETH_OBJ_INTERFACE<const> = 'bmc.kepler.Managers.EthernetInterfaces'
local ETH_PORT_INTERFACE<const> = 'bmc.kepler.Managers.EthernetInterfaces.MgmtPort'
local ETH_GROUP_INTERFACE<const> = 'bmc.kepler.Managers.EthernetInterfaces.EthGroup'

local m = {}

local function create_base_setting()
    local setting = {
        GroupId = 0,
        EthernetInterfaces = {
            Path = "",
            Interface = "",
            PortId = "PortId",
            VLANEnable = "VLANEnable"
        },
        Ipv4 = {
            Path = "",
            Interface = ""
        },
        Ipv6 = {
            Path = "",
            Interface = "",
            IpMode = "IpMode",
            IpAddr = "IpAddr",
            DefaultGateway = "DefaultGateway",
            SetIpAddr = "SetIpAddr",
            SetDefaultGateway = "SetDefaultGateway" 
        },
        NetworkConfig = {
            mode = "",
            portId = 0,
            vlanEnable = "",
            vlanId = 0
        }
    }
    return setting
end

local function get_netinterface_object(mdb_path, interface_name)
    local ok, netinterface_object = pcall(function()
        return mdb.get_object(bus, mdb_path, interface_name)
    end)
    if not ok then
        return nil
    end
    return netinterface_object
end

-- 获取和当前mac地址匹配的ethGroup资源树对象
local function get_mac_patched_ethgroup_object(sub_path, mac_id)
    local netinterface_object = get_netinterface_object(sub_path, ETH_GROUP_INTERFACE)
    if netinterface_object ~= nil and netinterface_object.OutType == 2 then
        local net_mac = netinterface_object.Mac
        if net_mac == mac_id then
            return netinterface_object
        end
    end
    return nil
end

-- 获取和当前mac地址匹配的interface资源树对象
local function get_mac_patched_eth_object(mac_id)
    local netinterface_object = get_netinterface_object(ETH_OBJ_PATH, ETH_OBJ_INTERFACE)

    if netinterface_object ~= nil then
        local net_mac = netinterface_object.Mac
        log:notice("[multi_patch] netmac = %s, macid = %s", net_mac, mac_id)
        if net_mac == mac_id then
            return netinterface_object
        end
    end

    return nil
end

local function patch_mac_to_ethgroup(mac_id, eth_group_setting)
    local ok, rsp = pcall(mdb_service.get_sub_paths, bus, ETH_OBJ_PATH .. "/EthGroup", 1, {ETH_GROUP_INTERFACE})

    if not ok then
        log:debug('Incorrect parent path or interface')
        return false
    end
    for _, sub_path in pairs(rsp.SubPaths) do
        local ethgroup_object = get_mac_patched_ethgroup_object(sub_path, mac_id)
        if ethgroup_object ~= nil then
            if ethgroup_object.OutType == 2 and ethgroup_object.Status then
                eth_group_setting.GroupId = ethgroup_object.GroupId
                eth_group_setting.EthernetInterfaces.Path = sub_path
                eth_group_setting.EthernetInterfaces.Interface = ETH_GROUP_INTERFACE
                eth_group_setting.EthernetInterfaces.PortId = "ActivePortId"
                eth_group_setting.EthernetInterfaces.VLANEnable = "VLANEnabled"
                eth_group_setting.Ipv4.Path = sub_path
                eth_group_setting.Ipv4.Interface = ETH_GROUP_INTERFACE
                eth_group_setting.Ipv6.Path = sub_path 
                eth_group_setting.Ipv6.Interface = ETH_GROUP_INTERFACE
                eth_group_setting.Ipv6.IpMode = "Ipv6Mode" 
                eth_group_setting.Ipv6.IpAddr = "Ipv6Addr"
                eth_group_setting.Ipv6.DefaultGateway = "Ipv6DefaultGateway"
                eth_group_setting.Ipv6.SetIpAddr = "SetIpv6Addr"
                eth_group_setting.Ipv6.SetDefaultGateway = "SetIpv6DefaultGateway"
                return true
            end
        end
    end

    return false
end

local function patch_mac_to_eth(mac_id, eth_interface_setting)
    local netinterface_object = get_mac_patched_eth_object(mac_id)
    if netinterface_object ~= nil and netinterface_object.Status then
        eth_interface_setting.GroupId = 0
        eth_interface_setting.EthernetInterfaces.Path = ETH_OBJ_PATH
        eth_interface_setting.EthernetInterfaces.Interface = ETH_OBJ_INTERFACE
        eth_interface_setting.Ipv4.Path = ETH_OBJ_PATH .. "/Ipv4"
        eth_interface_setting.Ipv4.Interface = ETH_OBJ_INTERFACE ..".Ipv4"
        eth_interface_setting.Ipv6.Path = ETH_OBJ_PATH .. "/Ipv6"
        eth_interface_setting.Ipv6.Interface = ETH_OBJ_INTERFACE ..".Ipv6"
        return true
    end
    return false
end

function m.patch_eth_info(mac_id)
    -- 之前的单网口环境不需要传mac地址
    if mac_id == nil then
        local mdb_setting = create_base_setting()
        mdb_setting.GroupId = 0
        mdb_setting.EthernetInterfaces.Path = ETH_OBJ_PATH
        mdb_setting.EthernetInterfaces.Interface = ETH_OBJ_INTERFACE
        mdb_setting.Ipv4.Path = ETH_OBJ_PATH .. "/Ipv4"
        mdb_setting.Ipv4.Interface = ETH_OBJ_INTERFACE ..".Ipv4"
        mdb_setting.Ipv6.Path = ETH_OBJ_PATH .. "/Ipv6"
        mdb_setting.Ipv6.Interface = ETH_OBJ_INTERFACE ..".Ipv6"
        return mdb_setting
    end

    -- 符合Interfaces的Mac 
    local eth_interface_setting = create_base_setting()
    if patch_mac_to_eth(mac_id, eth_interface_setting) then
        return eth_interface_setting
    end

    -- 符合EthGroup的Mac 
    local eth_group_setting = create_base_setting()
    if patch_mac_to_ethgroup(mac_id, eth_group_setting) then
        return eth_group_setting
    end

    return create_base_setting()
end

local function all_port_automatic(port_objs, mdb_setting_array, ethernet_object)
    -- 先处理OriginalGroupId为0的网口对象
    for _, port_o in pairs(port_objs) do
        if port_o.OriginalGroupId == 0 and port_o.CurrentGroupId ~= 0 then
            port_o.CurrentGroupId = 0
        end
    end
    -- 再处理OriginalGroupId不为0的网口对象
    for _, port_o in pairs(port_objs) do
        if port_o.OriginalGroupId ~= 0 then
            port_o.CurrentGroupId = 0
        end
    end

    local set_network_config_para = {}
    set_network_config_para.path = ETH_OBJ_PATH
    set_network_config_para.interface = ETH_OBJ_INTERFACE
    set_network_config_para.NetworoConfig = {}
    set_network_config_para.NetworoConfig.mode = 'Automatic'
    set_network_config_para.NetworoConfig.portId = ethernet_object.PortId
    set_network_config_para.NetworoConfig.vlanEnable = ethernet_object.VLANEnable
    set_network_config_para.NetworoConfig.vlanId = ethernet_object.VLANId

    table.insert(mdb_setting_array, set_network_config_para)
end

local function switch_port_to_fixed(port_o, mdb_setting_array, ethernet_object, eth_group_objs, target_groupid)
    -- 网口回到自己的网口组
    port_o.CurrentGroupId = target_groupid

    local set_network_config_para = {}
    set_network_config_para.NetworoConfig = {}
    local group_obj = nil
    if port_o.CurrentGroupId == 0 then
        group_obj = ethernet_object
        set_network_config_para.path = ETH_OBJ_PATH
        set_network_config_para.interface = ETH_OBJ_INTERFACE
        set_network_config_para.NetworoConfig.vlanEnable = group_obj.VLANEnable
        log:notice("switch port to fixed, ethernet type, portid:%s", port_o.Id)
    else
        for _, eth_group_obj in pairs(eth_group_objs) do
            if eth_group_obj.GroupId == port_o.OriginalGroupId then
                group_obj = eth_group_obj
            end
        end
        if group_obj == nil then
            return
        end
        set_network_config_para.path = group_obj.path
        set_network_config_para.interface = ETH_GROUP_INTERFACE
        set_network_config_para.NetworoConfig.vlanEnable = group_obj.VLANEnabled
        log:notice("switch port to fixed, eth group type, portid:%s", port_o.Id)
    end

    set_network_config_para.NetworoConfig.mode = 'Fixed'
    set_network_config_para.NetworoConfig.portId = port_o.Id
    set_network_config_para.NetworoConfig.vlanId = group_obj.VLANId

    table.insert(mdb_setting_array, set_network_config_para)
end

local function get_table_size(t)
    local count = 0
    for _ in pairs(t) do
        count = count + 1
    end
    return count
end

-- 固定模式，只选择一个网口
local function fixed_mode_with_one_port(mgmt_ports, patch_ports, mdb_setting_array, ethernet_object)
    if get_table_size(patch_ports) ~= 1 then
        return false
    end

    local patch_port
    for _, port in pairs(patch_ports) do
        patch_port = port
    end

    local port_id
    -- 先处理OriginalGroupId为0的网口对象
    for _, port_o in pairs(mgmt_ports) do
        if port_o.OriginalGroupId == 0 and port_o.CurrentGroupId ~= 0 then
            port_o.CurrentGroupId = 0
        end
    end
    -- 再处理OriginalGroupId不为0的网口对象
    for _, port_o in pairs(mgmt_ports) do
        if port_o.OriginalGroupId ~= 0 then
            port_o.CurrentGroupId = 0
        end
        if port_o.Type == patch_port.Type and port_o.DevicePortId == patch_port.PortNumber then
            port_id = port_o.Id
        end
    end

    local set_network_config_para = {}
    set_network_config_para.path = ETH_OBJ_PATH
    set_network_config_para.interface = ETH_OBJ_INTERFACE
    set_network_config_para.NetworoConfig = {}
    set_network_config_para.NetworoConfig.mode = 'Fixed'
    set_network_config_para.NetworoConfig.portId = port_id
    set_network_config_para.NetworoConfig.vlanEnable = ethernet_object.VLANEnable
    set_network_config_para.NetworoConfig.vlanId = ethernet_object.VLANId

    table.insert(mdb_setting_array, set_network_config_para)
    return true
end

-- 固定模式，选择所有网口
local function fixed_mode_with_all_port(mgmt_ports, patch_ports, mdb_setting_array, ethernet_object, eth_group_objs)
    local type, device_port_id
    for _, network_port in pairs(patch_ports) do
        type = network_port.Type
        device_port_id = network_port.PortNumber
        for _, port_o in pairs(mgmt_ports) do
            if device_port_id == port_o.DevicePortId and tostring(type) == port_o.Type then
                switch_port_to_fixed(port_o, mdb_setting_array, ethernet_object, eth_group_objs, port_o.OriginalGroupId)
            end
        end
    end

    return true
end

-- 固定模式，三网口场景选择两个网口
local function fixed_mode_with_two_port(mgmt_ports, patch_ports, mdb_setting_array, ethernet_object, eth_group_objs)
    local has_ncsi_port = false
    for _, network_port in pairs(patch_ports) do
        if network_port.Type ~= 'Dedicated' then
            has_ncsi_port = true
        end
    end

    -- 如果有NCSI口，则每个网口切换到自己的网口组
    if has_ncsi_port then
        return fixed_mode_with_all_port(mgmt_ports, patch_ports, mdb_setting_array, ethernet_object, eth_group_objs)
    end

    -- 如果没有NCSI口，配置专用口1到group0上，另一个专用口配置到自己的group
    local type, device_port_id
    for _, network_port in pairs(patch_ports) do
        type = network_port.Type
        device_port_id = network_port.PortNumber
        for _, port_o in pairs(mgmt_ports) do
            if device_port_id == port_o.DevicePortId and tostring(type) == port_o.Type then
                switch_port_to_fixed(port_o, mdb_setting_array, ethernet_object, eth_group_objs,
                    device_port_id == 1 and 0 or port_o.OriginalGroupId)
            end
        end
    end

    return true
end

-- 此方法只修改多网口组端口数据 
function m.patch_network_ports(network_ports, net_mode)
    local port_objs = mdb.get_sub_objects(bus, ETH_OBJ_PATH, ETH_PORT_INTERFACE)
    local ethernet_object = get_netinterface_object(ETH_OBJ_PATH, ETH_OBJ_INTERFACE)
    local mdb_setting_array = {}
    local ok, err = pcall(function ()
        -- 自动模式，全部网口对象都归属到Group0
        if net_mode == 'Automatic' then
            all_port_automatic(port_objs, mdb_setting_array, ethernet_object)
            return mdb_setting_array
        end
        local eth_group_objs = mdb.get_sub_objects(bus, ETH_OBJ_PATH .. "/EthGroup", ETH_GROUP_INTERFACE)

        if fixed_mode_with_one_port(port_objs, network_ports, mdb_setting_array, ethernet_object) then
            return mdb_setting_array
        end

        fixed_mode_with_two_port(port_objs, network_ports, mdb_setting_array, ethernet_object, eth_group_objs)
    end)

    if not ok then
        log:error("patch network ports failed, err:%s", err)
    end

    return mdb_setting_array
end

return m