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 = {
        IsValidId = false,
        GroupId = 0,
        LinkStatus = "UnConnected",
        MgmtPortNumber = 255,
        MgmtType = cjson.null,
        NegotiatedSpeedMbps = 0,
        VLANEnable = "VLANEnable",
        EthernetInterfaces = {
            Path = "",
            Interface = "",
            PortId = "PortId"
        },
        Ipv4 = {
            Path = "",
            Interface = ""
        },
        Ipv6 = {
            Path = "",
            Interface = "",
            IpMode = "IpMode",
            IpAddr = "IpAddr",
            DefaultGateway = "DefaultGateway",
            SetIpAddr = "SetIpAddr",
            SetDefaultGateway = "SetDefaultGateway"
        },
        EthernetInterfacesPortId = {
            Path = "",
            Interface = "",
            PortNumber = 255
        },
        EthernetInterfacesDNS = {
            Path = "",
            Interface = ""
        },
        PortPaths = {},
    }
    return setting
end

local function get_netinterface_object(mdb, bus, mdb_path, interface_name)
    local ok, netinterface_object = pcall(function()
        return mdb.get_object(bus, mdb_path, interface_name)
    end)
    if not ok then
        log:debug('Incorrect mdb path or interface, path = %s, interface = %s', mdb_path, interface_name)
        return nil
    end

    return netinterface_object
end

-- 获取和当前mac地址匹配的网口对象，若没有匹配上就返回nil
local function get_mac_patched_eth_object(mdb, bus, mdb_path, interface_name, mac_id)
    local netinterface_object = get_netinterface_object(mdb, bus, mdb_path, interface_name)

    if netinterface_object ~= nil 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

-- 获取最外围的LinkStatus
local function set_link_state(mdb, bus, mdb_path, port_id, mdb_setting)
    mdb_setting.LinkStatus = "UnConnected"
    mdb_setting.MgmtPortNumber = 255
    mdb_setting.MgmtType = cjson.null
    mdb_setting.NegotiatedSpeedMbps = 0
    if port_id == 255 then
        return
    end
    local link_obj = get_netinterface_object(mdb, bus, mdb_path .. "/" .. port_id, ETH_PORT_INTERFACE)
    if link_obj ~= nil and link_obj.LinkStatus == "Connected" then
        mdb_setting.LinkStatus = link_obj.LinkStatus
        mdb_setting.MgmtType = link_obj.Type
        mdb_setting.MgmtPortNumber = link_obj.DevicePortId
        mdb_setting.NegotiatedSpeedMbps = link_obj.NegotiatedSpeedMbps
    end
    return
end

-- 获取和当前mac地址匹配的组对象，若没有匹配上就返回nil，由于兼容性问题，旧接口没有OutType因此二者处理不同
local function get_mac_patched_ethgroup_object(mdb, bus, mdb_path, interface_name, mac_id)
    local netinterface_object = get_netinterface_object(mdb, bus, mdb_path, interface_name)

    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地址匹配的网口组对象，若没有匹配上就返回nil
local function patch_mac_to_ethgroup(mdb, bus, mdb_path, interface_name, mac_id, mdb_setting)
    local ok, rsp = pcall(mdb_service.get_sub_paths, bus, mdb_path .. "/EthGroup", 1, {interface_name})

    if not ok then
        log:debug('Incorrect parent path or interface, path = %s, interface = %s', mdb_path, interface_name)
        return false
    end

    for _, sub_path in pairs(rsp.SubPaths) do
        local ethgroup_object = get_mac_patched_ethgroup_object(mdb, bus, sub_path, interface_name, mac_id)
        if ethgroup_object ~= nil then
            mdb_setting.IsValidId = true
            mdb_setting.GroupId = ethgroup_object.GroupId
            mdb_setting.EthernetInterfaces.Path = sub_path
            mdb_setting.EthernetInterfaces.Interface = ETH_GROUP_INTERFACE
            mdb_setting.EthernetInterfaces.VLANEnable = "VLANEnabled"
            if ethgroup_object.ActivePortId ~= 255 then
                mdb_setting.EthernetInterfacesPortId.Path = mdb_path .. "/" .. ethgroup_object.ActivePortId
                mdb_setting.EthernetInterfacesPortId.Interface = ETH_PORT_INTERFACE
            end
            mdb_setting.EthernetInterfacesPortId.PortNumber = ethgroup_object.ActivePortId
            set_link_state(mdb, bus, mdb_path, mdb_setting.EthernetInterfacesPortId.PortNumber, mdb_setting)
            mdb_setting.Ipv4.Path = sub_path
            mdb_setting.Ipv4.Interface = ETH_GROUP_INTERFACE
            mdb_setting.Ipv6.Path = sub_path
            mdb_setting.Ipv6.Interface = ETH_GROUP_INTERFACE
            mdb_setting.Ipv6.IpMode = "Ipv6Mode"
            mdb_setting.Ipv6.IpAddr = "Ipv6Addr"
            mdb_setting.Ipv6.DefaultGateway = "Ipv6DefaultGateway"
            mdb_setting.Ipv6.SetIpAddr = "SetIpv6Addr"
            mdb_setting.Ipv6.SetDefaultGateway = "SetIpv6DefaultGateway"
            mdb_setting.EthernetInterfacesDNS.Path = mdb_path .. "/DNS"
            mdb_setting.EthernetInterfacesDNS.Interface = ETH_OBJ_INTERFACE ..".DNS"
            return true
        end
    end

    return false
end

local function patch_mac_to_eth(mdb, bus, mdb_path, interface_name, mac_id, mdb_setting)
    local netinterface_object = get_mac_patched_eth_object(mdb, bus, mdb_path, interface_name, mac_id)
    if netinterface_object ~= nil then
        mdb_setting.IsValidId = true
        mdb_setting.GroupId = 0
        mdb_setting.EthernetInterfaces.Path = mdb_path
        mdb_setting.EthernetInterfaces.Interface = ETH_OBJ_INTERFACE
        mdb_setting.EthernetInterfaces.VLANEnable = "VLANEnable"
        if netinterface_object.ActivePortId ~= 255 then
            mdb_setting.EthernetInterfacesPortId.Path = mdb_path .. "/" .. tostring(netinterface_object.PortId)
            mdb_setting.EthernetInterfacesPortId.Interface = ETH_PORT_INTERFACE
        end
        mdb_setting.EthernetInterfacesPortId.PortNumber = netinterface_object.PortId
        set_link_state(mdb, bus, mdb_path, mdb_setting.EthernetInterfacesPortId.PortNumber, mdb_setting)
        mdb_setting.Ipv4.Path = mdb_path .. "/Ipv4"
        mdb_setting.Ipv4.Interface = ETH_OBJ_INTERFACE ..".Ipv4"
        mdb_setting.Ipv6.Path = mdb_path .. "/Ipv6"
        mdb_setting.Ipv6.Interface = ETH_OBJ_INTERFACE ..".Ipv6"
        mdb_setting.Ipv6.IpMode = "IpMode"
        mdb_setting.Ipv6.IpAddr = "IpAddr"
        mdb_setting.Ipv6.DefaultGateway = "DefaultGateway"
        mdb_setting.Ipv6.SetIpAddr = "SetIpAddr"
        mdb_setting.Ipv6.SetDefaultGateway = "SetDefaultGateway"
        mdb_setting.EthernetInterfacesDNS.Path = mdb_path .. "/DNS"
        mdb_setting.EthernetInterfacesDNS.Interface = ETH_OBJ_INTERFACE ..".DNS"
        return true
    end

    return false
end

local function get_ports_path(members, mdb_setting)
    local port_objs = mdb.get_sub_objects(bus, ETH_OBJ_PATH, ETH_PORT_INTERFACE)
    local port_paths = {}
    for _, member in ipairs(members) do
        local is_valid_member = false
        if member.Type == nil or member.Type == "" then
            error(base_messages.PropertyMissing("Type"))
        end
        if member.PortNumber == nil then
            error(base_messages.PropertyMissing("PortNumber"))
        end
        for _, port_o in pairs(port_objs) do
            if port_o.Type == member.Type and port_o.DevicePortId == member.PortNumber then
                is_valid_member = true
                local port_path = ETH_OBJ_PATH .. '/' .. port_o.Id
                table.insert(port_paths, port_path)
            end
        end
        if not is_valid_member then
            local member_string = "Type: " .. member.Type .. " PortNumber: " .. member.PortNumber
            error(base_messages.PropertyValueNotInList(member_string, 'ManagementNetworkPortMembers'))
        end
    end
    mdb_setting.PortPaths = port_paths
end

function m.patch_eth_info(manager_id, eth_id, members)
    -- 格式化eth_id为Mac地址
    if #eth_id ~= 12 then
        return false
    end
    local mac_id = string.format('%s:%s:%s:%s:%s:%s', string.sub(eth_id, 1, 2), string.sub(eth_id, 3, 4),
    string.sub(eth_id, 5, 6), string.sub(eth_id, 7,8), string.sub(eth_id, 9, 10), string.sub(eth_id, 11, 12))
    
    local m_path = "/bmc/kepler/Managers/" .. manager_id .. "/EthernetInterfaces"

    log:debug("[PATCH MAC] mdb_path = %s, mac = %s", m_path, mac_id)

    -- 检查是否符合组信息挂载在EthernetInterfaces下的情况
    local eth_interface_setting = create_base_setting()
    if patch_mac_to_eth(mdb, bus, m_path, ETH_OBJ_INTERFACE, mac_id, eth_interface_setting) then
        if members and #members > 0 then
            get_ports_path(members, eth_interface_setting)
        end
        return eth_interface_setting
    end

    -- 检查是否符合组信息挂载在EthGroup子目录下的情况
    local eth_group_setting = create_base_setting()
    if patch_mac_to_ethgroup(mdb, bus, m_path, ETH_GROUP_INTERFACE, mac_id, eth_group_setting) then
        if members and #members > 0 then
            get_ports_path(members, eth_group_setting)
        end
        return eth_group_setting
    end

    -- 必须要专门赋值，实测前端可能会使用旧数据，使用默认值可能会意外访问到非法mac
    local mdb_setting = create_base_setting()
    mdb_setting.IsValidId = false
    return mdb_setting
end

function m.get_network_port_members(port_table, group_id)
    local network_port_members = {}
    pcall(function()
        for _, path in pairs(port_table) do
            local port = {}
            local port_obj = mdb.get_object(bus, path, ETH_PORT_INTERFACE)
            if port_obj.CurrentGroupId == nil or port_obj.CurrentGroupId == group_id then
                port["Type"] = port_obj.Type
                port["PortNumber"] = port_obj.DevicePortId
                table.insert(network_port_members, port)
            end
        end
    end)
    return network_port_members
end

function m.get_odata(manager_id)
    local odatas = {
        Count = 0,
        Odatas = {}
    }
    local m_path = "/bmc/kepler/Managers/" .. manager_id .. "/EthernetInterfaces"
    log:debug("[Get Manage Mac]")
    local ethinterface_obj = get_netinterface_object(mdb, bus, m_path, ETH_OBJ_INTERFACE)
    if ethinterface_obj ~= nil then
        odatas.Count = odatas.Count + 1
        local mac_str = string.gsub(ethinterface_obj.Mac, ':', '')
        local odata = {}
        odata["@odata.id"] = "/redfish/v1/Managers/1/EthernetInterfaces/" .. mac_str
        table.insert(odatas.Odatas, odata)
    end

    local ok, rsp = pcall(mdb_service.get_sub_paths, bus, m_path .. "/EthGroup", 1, {ETH_GROUP_INTERFACE})
    if not ok then
        log:debug('Incorrect parent path or interface, path = %s, interface = %s',
            m_path, "bmc.kepler.Managers.EthernetInterfaces.EthGroup")
        return odatas
    end

    -- 检查group子目录下的OutType为2的组
    for _, sub_path in pairs(rsp.SubPaths) do
        local ethgroup_obj = get_netinterface_object(mdb, bus, sub_path, ETH_GROUP_INTERFACE)
        if ethgroup_obj ~= nil and ethgroup_obj.OutType == 2 then
            odatas.Count = odatas.Count + 1
            local mac_str = string.gsub(ethgroup_obj.Mac, ':', '')
            local odata = {}
            odata["@odata.id"] = "/redfish/v1/Managers/1/EthernetInterfaces/" .. mac_str
            table.insert(odatas.Odatas, odata)
        end
    end

    return odatas
end

local function get_netinterface_data()
    local ncsi_setting = create_base_setting()
    local netinterface_object = mdb.get_object(bus, ETH_OBJ_PATH, ETH_OBJ_INTERFACE)
    if netinterface_object ~= nil then
        ncsi_setting.EthernetInterfaces.Path = ETH_OBJ_PATH
        ncsi_setting.EthernetInterfaces.Interface = ETH_OBJ_INTERFACE
        ncsi_setting.EthernetInterfaces.VLANEnable = "VLANEnable"
        if netinterface_object.ActivePortId ~= 255 then
            ncsi_setting.EthernetInterfacesPortId.Path = ETH_OBJ_PATH .. "/" .. tostring(netinterface_object.PortId)
            ncsi_setting.EthernetInterfacesPortId.Interface = ETH_PORT_INTERFACE
        end
        ncsi_setting.Ipv4.Path = ETH_OBJ_PATH .. "/Ipv4"
        ncsi_setting.Ipv4.Interface = ETH_OBJ_INTERFACE ..".Ipv4"
        ncsi_setting.Ipv6.Path = ETH_OBJ_PATH .. "/Ipv6"
        ncsi_setting.Ipv6.Interface = ETH_OBJ_INTERFACE ..".Ipv6"
        ncsi_setting.Ipv6.IpMode = "IpMode"
        ncsi_setting.Ipv6.IpAddr = "IpAddr"
        ncsi_setting.Ipv6.DefaultGateway = "DefaultGateway"
        ncsi_setting.Ipv6.SetIpAddr = "SetIpAddr"
        ncsi_setting.Ipv6.SetDefaultGateway = "SetDefaultGateway"
    end
    return ncsi_setting
end

local function get_out_group_data(manager_id, group_id)
    local ncsi_setting = create_base_setting()
    local mdb_path = "/bmc/kepler/Managers/" .. manager_id .. "/EthernetInterfaces"
    local ok, rsp = pcall(mdb_service.get_sub_paths, bus, mdb_path .. "/EthGroup", 1, {ETH_GROUP_INTERFACE})

    if not ok then
        log:debug('Incorrect parent path or interface, path = %s, interface = %s', mdb_path, ETH_GROUP_INTERFACE)
        return ncsi_setting
    end

    for _, sub_path in pairs(rsp.SubPaths) do
        local out_group_object = mdb.get_object(bus, sub_path, ETH_GROUP_INTERFACE)
        if out_group_object and out_group_object.OutType == 2 and out_group_object.GroupId == group_id then
            ncsi_setting.EthernetInterfaces.Path = sub_path
            ncsi_setting.EthernetInterfaces.Interface = ETH_GROUP_INTERFACE
            ncsi_setting.EthernetInterfaces.VLANEnable = "VLANEnabled"
            ncsi_setting.EthernetInterfaces.PortId = "ActivePortId"
            if out_group_object.ActivePortId ~= 255 then
                ncsi_setting.EthernetInterfacesPortId.Path = mdb_path .. "/" .. out_group_object.ActivePortId
                ncsi_setting.EthernetInterfacesPortId.Interface = ETH_PORT_INTERFACE
            end
            ncsi_setting.Ipv4.Path = sub_path
            ncsi_setting.Ipv4.Interface = ETH_GROUP_INTERFACE
            ncsi_setting.Ipv6.Path = sub_path
            ncsi_setting.Ipv6.Interface = ETH_GROUP_INTERFACE
            ncsi_setting.Ipv6.IpMode = "Ipv6Mode"
            ncsi_setting.Ipv6.IpAddr = "Ipv6Addr"
            ncsi_setting.Ipv6.DefaultGateway = "Ipv6DefaultGateway"
            ncsi_setting.Ipv6.SetIpAddr = "SetIpv6Addr"
            ncsi_setting.Ipv6.SetDefaultGateway = "SetIpv6DefaultGateway"
        end
    end

    return ncsi_setting
end

function m.patch_nic_id(manager_id, nic_id)
    -- nic_id DedicatedPort1 PCIePort1 PCIEPort2
    local nic_id_lower = string.lower(nic_id)
    local type, portNumber = string.match(nic_id_lower, "^(%a+)port(%d+)$")
    if not type or not portNumber or type == "" or portNumber == "" then
        log.error("get type/portNumber failed form nic_id")
        return create_base_setting()
    end

    local port_objs = mdb.get_sub_objects(bus, ETH_OBJ_PATH, ETH_PORT_INTERFACE)
    local lower_port_type
    local current_group_id
    local ncsi_setting = create_base_setting()
    for _, port_o in pairs(port_objs) do
        lower_port_type = string.lower(port_o.Type)
        if string.find(lower_port_type, type, 1, true) and port_o.DevicePortId == tonumber(portNumber) then
            current_group_id = port_o.CurrentGroupId
            if current_group_id == 0 then
                ncsi_setting = get_netinterface_data()
                return ncsi_setting
            else
                ncsi_setting = get_out_group_data(manager_id, current_group_id)
                return ncsi_setting
            end
        end
    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