-- 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 utils = require 'mc.utils'

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

local m = {}

function m.get_address_mode(DHCPv4UseDNSServers, DHCPv6UseDNSServers)
    if (not DHCPv4UseDNSServers) and (not DHCPv6UseDNSServers) then
        return 'Static'
    elseif DHCPv4UseDNSServers and (not DHCPv6UseDNSServers) then
        return 'IPv4'
    elseif DHCPv6UseDNSServers and (not DHCPv4UseDNSServers) then
        return 'IPv6'
    end

    return 'IPv4'
end

-- 资源树上的link地址和slaac地址是以fec0::01/64这样呈现的
-- 适配网页显示需要做转换
local function split_ipv6_address(ipv6_address)
    local strs = utils.split(ipv6_address, '/')
    return {addr = strs[1], prefix = strs[2]}
end

function m.get_ipv6_addresses(eth_obj, ipv6_obj)
    local ipv6_addresses = {}
    local pre_fix = ipv6_obj.PrefixLength == 0 and '' or ipv6_obj.PrefixLength
    ipv6_addresses[#ipv6_addresses + 1] = {
        IPAddress = ipv6_obj.IpAddr,
        IPAddressMode = ipv6_obj.IpMode,
        PrefixLength = pre_fix
    }
    local link_local = eth_obj.LinkLocalAddress
    if link_local and #link_local ~= 0 then
        local format_addr = split_ipv6_address(link_local)
        ipv6_addresses[#ipv6_addresses + 1] = {
            IPAddress = format_addr.addr,
            IPAddressMode = 'LinkLocal',
            PrefixLength = tonumber(format_addr.prefix)
        }
    end
    local slaac_list = eth_obj.SLAACAddressList
    if slaac_list then
        for _, value in ipairs(slaac_list) do
            local format_slaac_addr = split_ipv6_address(value)
            ipv6_addresses[#ipv6_addresses + 1] = {
                IPAddress = format_slaac_addr.addr,
                IPAddressMode = 'SLAAC',
                PrefixLength = tonumber(format_slaac_addr.prefix)
            }
        end
    end 
    return ipv6_addresses
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_net_mode(ether_props, group_props)
    local activie_group_num = 0
    local net_mode = 'Fixed'
    if ether_props.Status then
        activie_group_num = activie_group_num + 1
        net_mode = ether_props.NetMode
    end
    for _, group_prop in pairs(group_props) do
        net_mode = group_prop.NetMode
        activie_group_num = activie_group_num + 1
    end
    if activie_group_num > 1 then
        return 'Fixed'
    end

    return net_mode
end

local function handler_network_port(ports, port_id, management_network_port, members)
    local port_number, member
    for _, port_o in ipairs(ports) do
        port_number = port_o.DevicePortId
        member = {}
        if port_o.Id == port_id then
            management_network_port.PortNumber = port_number
            management_network_port.Type = type_switch_table.mdb_to_web[port_o.Type]
            management_network_port.PortName = "Eth" .. tostring(port_o.EthId)
        end

        if port_o.Type == 'Dedicated' then
            if string.match(port_o.Silkscreen, 'Mgmt') then
                member.PortName = string.gsub(string.gsub(port_o.Silkscreen, "^%s+", ""), "%s+$", "")
            else
                member.PortName = 'Port' .. port_o.DevicePortId .. '(' .. port_o.Silkscreen .. ')'
            end
        else
            member.PortName = 'Port' ..  tostring(port_o.DevicePortId)
        end

        member.PortNumber = port_number
        member.LinkStatus = port_o.LinkStatus
        member.AdaptiveFlag = port_o.AdaptiveFlag
        local type = type_switch_table.mdb_to_web[port_o.Type]
        if type then
            members[type][#members[type] + 1] = member
        end
    end
end

local function process_members(port_obj, group_id, members)
    -- 此处members返回当前网口组的对象
    local port_number, member
    local ok, port_o = pcall(mdb.get_object, bus, ETH_OBJ_PATH .. '/' .. port_obj.Id, ETH_PORT_INTERFACE)
    if not ok then
        return
    end
    local current_group_id = port_o.CurrentGroupId
    if group_id == current_group_id then
        port_number = port_o.DevicePortId
        member = {}
        if port_o.Type == 'Dedicated' then
            if string.match(port_o.Silkscreen, 'Mgmt') then
                member.PortName = string.gsub(string.gsub(port_o.Silkscreen, "^%s+", ""), "%s+$", "")
            else
                member.PortName = 'Port' .. port_o.DevicePortId .. '(' .. port_o.Silkscreen .. ')'
            end
        else
            member.PortName = 'Port' ..  tostring(port_o.DevicePortId)
        end
        member.PortNumber = port_number
        member.LinkStatus = port_o.LinkStatus
        member.AdaptiveFlag = port_o.AdaptiveFlag
        local type = type_switch_table.mdb_to_web[port_o.Type]
        if type then
            members[type][#members[type] + 1] = member
        end
    end
end

local function get_group_props(group_paths)
    local ok, eth_group_obj
    local group_props = {}
    for _, group_path in ipairs(group_paths) do 
        ok, eth_group_obj = pcall(mdb.get_object, bus, group_path,  'bmc.kepler.Managers.EthernetInterfaces.EthGroup') 
        if ok and eth_group_obj.OutType == 2 and eth_group_obj.Status then
            group_props[eth_group_obj.GroupId] = {
                ['ActiveEthId'] = eth_group_obj.ActiveEthId,
                ['ActivePortId'] = eth_group_obj.ActivePortId,
                ['NetMode'] = eth_group_obj.NetMode
            }
        end
    end
    return group_props
end

function m.get_network_port(ether_props, port_id, ports, group_paths)
    local network_port = {}
    local management_network_port = {}
    local members = {
        LOM = {},
        Dedicated = {},
        ExternalPCIe = {},
        OCP = {},
        OCP2 = {},
        Aggregation = {},
        FlexIO = {}
    }
    local group_props = get_group_props(group_paths)
    local net_mode = get_net_mode(ether_props, group_props)
    handler_network_port(ports, port_id, management_network_port, members)
    network_port = {
        Mode = net_mode,
        ManagementNetworkPort = management_network_port,
        Members = members
    }

    return network_port
end

function m.is_multi_ethernet_interface_supported(group_paths)
    local multi_ethernet_interface_supported = false
    for _, group_path in ipairs(group_paths) do
        local ok, ethGroup_obj = pcall(mdb.get_object, bus, group_path, ETH_GROUP_INTERFACE)
        if ok and ethGroup_obj.OutType == 2 then
            multi_ethernet_interface_supported = true
        end
    end
    return multi_ethernet_interface_supported
end

local function process_network_port(port_obj, port_id, management_network_port)
    if port_obj.Id == port_id then
        local port_number = port_obj.DevicePortId
        management_network_port.PortNumber = port_number
        management_network_port.Type = type_switch_table.mdb_to_web[port_obj.Type]
        management_network_port.PortName = port_obj.Silkscreen
    end
end

local function get_ethernet_group(ethGroup_obj, management_network_port, management_network_ports, members)
    local ipv6_info = {
        IpMode = ethGroup_obj.Ipv6Mode,
        IpAddr = ethGroup_obj.Ipv6Addr,
        PrefixLength = ethGroup_obj.PrefixLength
    }
    local ipv6_infos = m.get_ipv6_addresses(ethGroup_obj, ipv6_info)
    local ethernet_group = {
        GroupId = ethGroup_obj.GroupId,
        NetworkPort = {
            Mode = ethGroup_obj.NetMode,
            ManagementNetworkPort = management_network_port,
            ManagementNetworkPorts = management_network_ports,
            Members = members
        },
        NetworkProtocols = {
            ProtocolMode = ethGroup_obj.IpVersion,
            IPv4Config = {
                IPAddressMode = ethGroup_obj.IpMode,
                IPAddress = ethGroup_obj.IpAddr,
                SubnetMask = ethGroup_obj.SubnetMask,
                Gateway = ethGroup_obj.DefaultGateway,
                PermanentMACAddress = ethGroup_obj.Mac
            },
            IPv6Config = {
                Gateway = ethGroup_obj.Ipv6DefaultGateway,
                IPv6Addresses = ipv6_infos
            }
        }
    }
    return ethernet_group
end

local function get_ethernet_interface_group(eth_obj, ipv4_obj, ipv6_obj, management_network_port, 
    management_network_ports, members)
    local ipv6_info = {
        IpMode = ipv6_obj.IpMode,
        IpAddr = ipv6_obj.IpAddr,
        PrefixLength = ipv6_obj.PrefixLength
    }
    local ipv6_infos = m.get_ipv6_addresses(eth_obj, ipv6_info)
    local ethernet_interface_group = {
        NetworkPort = {
            Mode = eth_obj.NetMode,
            ManagementNetworkPort = management_network_port,
            ManagementNetworkPorts = management_network_ports,
            Members = members
        },
        NetworkProtocols = {
            ProtocolMode = eth_obj.IpVersion,
            IPv4Config = {
                IPAddressMode = ipv4_obj.IpMode,
                IPAddress = ipv4_obj.IpAddr,
                SubnetMask = ipv4_obj.SubnetMask,
                Gateway = ipv4_obj.DefaultGateway,
                PermanentMACAddress = eth_obj.Mac
            },
            IPv6Config = {
                Gateway = ipv6_obj.DefaultGateway,
                IPv6Addresses = ipv6_infos
            }
        }
    }

    return ethernet_interface_group
end

local function process_network_ports(group_id, obj, management_network_ports)
    -- 处理匹配GroupId且连接正常的端口
    local ok, port_obj = pcall(mdb.get_object, bus, ETH_OBJ_PATH .. '/' .. obj.Id, ETH_PORT_INTERFACE)
    if not ok then
        return
    end
    
    if port_obj.CurrentGroupId == group_id then
        local management_network_port = {
            PortNumber = port_obj.DevicePortId,
            Type = type_switch_table.mdb_to_web[port_obj.Type],
            PortName = port_obj.Silkscreen
        }
        table.insert(management_network_ports, management_network_port)
    end
end

local function process_eth_group(ethGroup_obj, ports)
    local management_network_port = {} -- 显示网口组当前使能对象
    local management_network_ports = {} -- 显示网口组所有对象
    local group_id = ethGroup_obj.GroupId
    local members = {
        LOM = {},
        Dedicated = {},
        ExternalPCIe = {},
        OCP = {},
        OCP2 = {},
        Aggregation = {},
        FlexIO = {}
    }
  
    for _, port_obj in ipairs(ports) do
        process_network_port(port_obj, ethGroup_obj.ActivePortId, management_network_port)
        process_members(port_obj, group_id, members)
        process_network_ports(group_id, port_obj, management_network_ports)
    end
    
    local ethernet_group = get_ethernet_group(ethGroup_obj, management_network_port, 
    management_network_ports, members)
    return ethernet_group
end

local function handler_eth_groups(eth_groups, eth_net_groups)
    table.sort(eth_net_groups, function(a, b)
        return a.GroupId < b.GroupId
    end)
    for _, group in ipairs(eth_net_groups) do
        group.GroupId = nil
        if group.NetworkPort.ManagementNetworkPort and next(group.NetworkPort.ManagementNetworkPort) == nil then
            group.NetworkPort.ManagementNetworkPort = nil
        end
        if group.NetworkPort.ManagementNetworkPorts and #group.NetworkPort.ManagementNetworkPorts == 0 then
            group.NetworkPort.ManagementNetworkPorts = nil
        end
        table.insert(eth_groups, group)
    end
end

local function process_eth_interface_group(eth_obj, ports, ipv4_obj, ipv6_obj, eth_groups)
    local management_network_port = {} -- 显示网口组当前使能对象
    local management_network_ports = {} -- 显示网口组所有对象
    local ok, port_o = pcall(mdb.get_object, bus, ETH_OBJ_PATH .. '/' .. eth_obj.PortId, ETH_PORT_INTERFACE)
    if not ok then
        return
    end
    local group_id = port_o.CurrentGroupId
    local members = {
        LOM = {},
        Dedicated = {},
        ExternalPCIe = {},
        OCP = {},
        OCP2 = {},
        Aggregation = {},
        FlexIO = {}
    }
    
    for _, port_obj in ipairs(ports) do
        process_network_port(port_obj, eth_obj.PortId, management_network_port)
        process_members(port_obj, group_id, members)
        process_network_ports(group_id, port_obj, management_network_ports)
    end

    local ethernet_interface_group = get_ethernet_interface_group(eth_obj, ipv4_obj, ipv6_obj, management_network_port,
    management_network_ports, members)
    table.insert(eth_groups, ethernet_interface_group)
end

function m.get_ethernet_interface_groups(eth_obj, ipv4_obj, ipv6_obj, ports, group_paths)
    local eth_groups = {}
    local eth_net_groups = {}
    local multi_ethernet_interface_supported = false
    local valid_eth_groups = {} 
    for _, group_path in ipairs(group_paths) do
        local ok, ethGroup_obj = pcall(mdb.get_object, bus, group_path, ETH_GROUP_INTERFACE)
        if ok and ethGroup_obj.OutType == 2 then
            multi_ethernet_interface_supported = true
            table.insert(valid_eth_groups, ethGroup_obj)
        end
    end

    if multi_ethernet_interface_supported then
        -- 添加interface网口组 
        process_eth_interface_group(eth_obj, ports, ipv4_obj, ipv6_obj, eth_groups)
        -- 添加其他网口组
        for _, ethGroup_obj in ipairs(valid_eth_groups) do
            local ethernet_group = process_eth_group(ethGroup_obj, ports)
            table.insert(eth_net_groups, ethernet_group)
        end
        handler_eth_groups(eth_groups, eth_net_groups)
    end

    return eth_groups
end

function m.check_network_config(ncsi_vlan, network_port)
    -- 设置ncsi_vlan
    if ncsi_vlan ~= nil then
        return true
    end
    -- 设置网口固定模式
    if network_port.Mode == "Fixed" then
        if network_port.ManagementNetworkPort == nil then
            error(base_messages.PropertyMissing("ManagementNetworkPort"))
        end
        return true
    end
    -- 设置网口自动模式
    if network_port.Mode == "Automatic" then
        -- Members 参数必须
        if network_port.Members ~= nil then
            return true
        end
        error(base_messages.PropertyMissing("Members"))
    end
    -- 入参校验 只传入Mode
    if network_port.Mode ~= nil then
        error(base_messages.PropertyValueNotInList(network_port.Mode, "Mode"))
    end
    return false
end

function m.set_dedicated_vlan(enabled, vlan_id)
    if enabled == false and vlan_id == nil then
        return {enabled, 0, 1}
    end
    if enabled == true and vlan_id == nil then
        error(base_messages.PropertyMissing("VLANId"))
    end
    if enabled == nil and vlan_id ~= nil and vlan_id ~= 0 then
        return {true, vlan_id, 1}
    end
    if enabled == nil and vlan_id ~= nil and vlan_id == 0 then
        return {true, vlan_id, 1}
    end
    return {enabled, vlan_id, 1}
end

function m.check_ipv4_address_mode(ipv4_config)
    if ipv4_config.IPAddressMode and tostring(ipv4_config.IPAddressMode) == 'DHCP' and
        (ipv4_config.IPAddress or ipv4_config.SubnetMask or ipv4_config.Gateway) then
        error(custom_messages.IPv4InfoConflictwithDHCP())
        return false
    end

    return true
end

function m.check_ipv6_address_mode(ipv6_config)
    if ipv6_config.IPv6Addresses and ipv6_config.IPv6Addresses[1] and ipv6_config.IPv6Addresses[1].IPAddressMode then
        local ipv6_address = ipv6_config.IPv6Addresses[1]
        if tostring(ipv6_address.IPAddressMode) == 'DHCPv6' and
            (ipv6_address.IPAddress or ipv6_address.PrefixLength) then
            error(custom_messages.IPv6InfoConflictwithDHCPv6())
            return false
        end
    end

    return true
end

function m.check_ipv6_config(ipv6_config)
    if ipv6_config.Gateway or
        (ipv6_config.IPv6Addresses and ipv6_config.IPv6Addresses[1] and ipv6_config.IPv6Addresses[1].IPAddress) or
        (ipv6_config.IPv6Addresses and ipv6_config.IPv6Addresses[1] and ipv6_config.IPv6Addresses[1].PrefixLength) then
        return true
    end

    return false
end

local function get_portid_by_type(port_id, port)
    local port_obj = mdb.get_sub_objects(bus, ETH_OBJ_PATH, ETH_PORT_INTERFACE)
    local type = port.Type
    local device_port_id = port.PortNumber
    if type ~= nil then
        local type_valid = false
        for _, obj in pairs(port_obj) do
            if type_switch_table.web_to_mdb[type] == obj.Type then
                type_valid = true
                break
            end
        end
        if not type_valid then
            error(custom_messages.InvalidValue(type, "Type"))
        end
    end

    if not type or not device_port_id then
        return port_id
    end
    for _, obj in pairs(port_obj) do
        if device_port_id == obj.DevicePortId and type_switch_table.web_to_mdb[type] == obj.Type then
            port_id = obj.Id
            return port_id
        end
    end
    error(custom_messages.PortNotExist(type, device_port_id))
end

function m.set_network_config(network_port, ncsi_VLAN, eth_obj)
    local mode = eth_obj.NetMode
    local port_id = eth_obj.PortId
    local vlan_enable = eth_obj.VLANEnable
    local vlan_id = eth_obj.VLANId
    if network_port then
        if network_port.Mode then
            mode = tostring(network_port.Mode)
        end
        if network_port.ManagementNetworkPort then
            port_id = get_portid_by_type(port_id, network_port.ManagementNetworkPort)
        end
    end
    if ncsi_VLAN then
        if ncsi_VLAN.VLANId then
            vlan_id = ncsi_VLAN.VLANId
        end
        if ncsi_VLAN.Enabled ~= nil then
            vlan_enable = ncsi_VLAN.Enabled
        else
            vlan_enable = true
        end
        -- vlan关闭时，id设置为0
        if not vlan_enable then
            vlan_id = 0
        end
        if ncsi_VLAN.Enabled == true and ncsi_VLAN.VLANId == nil then
            error(base_messages.PropertyMissing("VLANId"))
        end
    end

    return {mode, port_id, vlan_enable, vlan_id}
end

function m.is_port_null(type, members)
    local port_obj = mdb.get_sub_objects(bus, ETH_OBJ_PATH, ETH_PORT_INTERFACE)
    local match = false
    for _, dedicated_port_info in ipairs(members) do
        local device_port_id = dedicated_port_info.PortNumber
        for _, obj in pairs(port_obj) do
            if device_port_id and device_port_id == obj.DevicePortId and type_switch_table.web_to_mdb[type] == obj.Type then
                match = true
            end
        end

        if not match then
            error(custom_messages.PortNotExist(type, device_port_id))
        end

        match = false
    end

    return true
end

function m.get_dedicated_vlan(group_paths, dedicated_vlan, vlan_id)
    local multi_ethernet_interface_supported = m.is_multi_ethernet_interface_supported(group_paths)
    if multi_ethernet_interface_supported then
        return nil
    end
    local dedicated_vlan_info = {}
    dedicated_vlan_info.VLANId = 0
    if dedicated_vlan.VLANEnabled then
        dedicated_vlan_info.VLANId = dedicated_vlan.VLANId
    end
    dedicated_vlan_info.Enabled = dedicated_vlan.VLANEnabled
    dedicated_vlan_info.MinVLANId = vlan_id.MinVLANId
    dedicated_vlan_info.MaxVLANId = vlan_id.MaxVLANId

    return dedicated_vlan_info
end

return m
