-- 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
        },
        PortPaths = {}
    }
    return setting
end

-- 用于web页面和资源树类型的相互转化
local type_switch_table = {
    mdb_to_web = {
        Lom = 'LOM',
        Dedicated = 'Dedicated',
        ExternalPCIe = 'ExternalPCIe',
        Ocp = 'OCP',
        Ocp2 = 'OCP2',
        Aggregation = 'Aggregation',
        FlexIO = 'FlexIO'
    },
    web_to_mdb = {
        LOM = 'Lom',
        Dedicated = 'Dedicated',
        ExternalPCIe = 'ExternalPCIe',
        OCP = 'Ocp',
        OCP2 = 'Ocp2',
        Aggregation = 'Aggregation',
        FlexIO = 'FlexIO'
    }
}

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 string.lower(net_mac) == string.lower(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 string.lower(net_mac) == string.lower(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 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

local function get_ports_path(ports, mdb_setting)
    local port_objs = mdb.get_sub_objects(bus, ETH_OBJ_PATH, ETH_PORT_INTERFACE)
    local port_paths = {}
    for _, port in ipairs(ports) do
        for _, port_o in pairs(port_objs) do
            if port_o.Type == type_switch_table.web_to_mdb[port.Type] and port_o.DevicePortId == port.PortNumber then
                local port_path = ETH_OBJ_PATH .. '/' .. port_o.Id
                table.insert(port_paths, port_path)
            end
        end
    end
    mdb_setting.PortPaths = port_paths
end

function m.patch_eth_info(mac_id, network_port)
    -- 之前的单网口环境不需要传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
        if network_port and network_port.ManagementNetworkPorts then
            get_ports_path(network_port.ManagementNetworkPorts, eth_interface_setting)
        end
        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
        if network_port and network_port.ManagementNetworkPorts then
            get_ports_path(network_port.ManagementNetworkPorts, eth_group_setting)
        end
        return eth_group_setting
    end

    return create_base_setting()
end

function m.check_single_network_port()
    local is_single_network_port = true
    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, path = %s, interface = %s', ETH_OBJ_PATH, ETH_GROUP_INTERFACE)
        return false
    end
    for _, sub_path in pairs(rsp.SubPaths) do
        local ethgroup_object = mdb.get_object(bus, sub_path, ETH_GROUP_INTERFACE)
        if ethgroup_object and ethgroup_object.OutType == 2 then
            is_single_network_port = false
            error(custom_messages.PropertyModificationNotSupported('DedicatedVLAN'))
        end
    end

    return is_single_network_port
end

return m