-- 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 json = require 'cjson'
local class = require 'mc.class'
local log = require 'mc.logging'
local task_mgmt = require 'mc.mdb.task_mgmt'
local task_state <const> = task_mgmt.state
local task_status <const> = task_mgmt.status
local client = require 'network_adapter.client'
local c_network_port = require 'device.class.network_port'
local c_network_adapter = require 'device.class.network_adapter'
local c_network_bonding_port = require 'device.class.network_bonding_port'
local common = require 'bma.rpc.common'

local BMA_SMS_TEAM_URI <const> = '/redfish/v1/Sms/1/Systems/1/EthernetInterfaces/Team'

local c_bma_rpc_bond = class()

function c_bma_rpc_bond:ctor(bus)
    self.bus = bus
end

local function find_bond_object(bond_id)
    local object
    client:ForeachNetworkBondingObjects(function(obj)
        log:info('obj.Id = %s, bond_id = %s', obj.Id, bond_id)
        if obj.Id == bond_id then
            object = obj
        end
    end)
    return object
end

-- 拼接对应的网卡网口名称
local function find_card_port(eth_name)
    -- 找到物理网口对象
    local np = c_network_port.collection:find(function(obj)
        return obj.Name == eth_name
    end)
    if not np then
        return
    end

    local na = c_network_adapter.collection:find(function(obj)
        return obj.ID == np.NetworkAdapterId
    end)

    return np, na
end

local function format_create_bond_operate_log(ports)
    if next(ports) then 
        local format_ports = ''
        local np, na
        local tmp
        local found
        for k, v in ipairs(ports) do
            np, na = find_card_port(v)
            if np and na then
                tmp = string.format('%s card %s port%s, ', na.Type == 1 and 'LOM' or 'PCIe', na.SlotNumber, 
                    np.PortID + 1)
                format_ports = format_ports .. tmp
                found = true
            end
        end
        if found then
            -- -3是为了把最后的逗号和空格去掉
            format_ports = string.sub(format_ports, 1, -3)
        end
        return string.format('Create bond with (%s)', format_ports)
    else
        return 'Create bond'
    end
end

local function checkout_mandatory_params(requst)
    -- 必选参数
    local mandatory_params = {
        'Name', 'WorkMode', 'MIILinkMonitoringFrequencyMS'
    } 

    for _, v in ipairs(mandatory_params) do
        if not requst[v] then
            return false, v
        end
    end

    return true
end

local function is_create_bond_params_valid(requst)
    local len = #requst.Name
    if len < 1 or len > 15 then
        return false, 'Name', requst.Name
    end

    local bond_type = tonumber(requst.WorkMode)
    -- 0:balance-rr；1:active-backup；2:balance-xor；3:broadcast；4:802.3ad；5:balance-tlb；6:balance-alb
    if bond_type < 0 or bond_type > 6 then
        return false, 'BondingType', requst.WorkMode
    end

    local period = tonumber(requst.MIILinkMonitoringFrequencyMS)
    if period < 0 or period > 999999999 then
        return false, 'MIILinkMonitoringFrequencyMS', requst.MIILinkMonitoringFrequencyMS
    end

    -- 此处与redfish文档保持一致，LinkStatus仅取值LinkUp或LinkDown
    if requst.LinkStatus ~= 'LinkUp' and requst.LinkStatus ~= 'LinkDown' then
        return false, 'LinkStatus', requst.LinkStatus
    end

    if requst.AddressOrigin ~= 'none' then
        return false, 'AddressOrigin', requst.AddressOrigin
    end

    if requst.OnBoot ~= 'yes' and requst.OnBoot ~= 'no' then
        return false, 'IsOnBoot', requst.OnBoot
    end

    return true
end

function c_bma_rpc_bond:create_bond(obj, ctx, requst)
    local ports = {}
    if requst.Ports then
        ports = json.decode(requst.Ports)
    end

    local operate_log_msg = format_create_bond_operate_log(ports)
    local create, err, task_id = task_mgmt.create_task(self.bus, 'Bond Creation', obj.path)
    if create ~= task_mgmt.create_code.TASK_CREATE_SUCCESSFUL then
        log:error('creat CreateBond task failed')
        log:operation(ctx:get_initiator(), 'NetworkAdapter', operate_log_msg .. ' failed')
        error(err)
    end

    if not common.is_bma_connected() then
        common.update_task_msg_bma_not_present(task_id)
        log:operation(ctx:get_initiator(), 'NetworkAdapter', operate_log_msg .. ' failed')
        return task_id
    end

    local ok, prop_name = checkout_mandatory_params(requst)
    if not ok then
        log:operation(ctx:get_initiator(), 'NetworkAdapter', operate_log_msg .. ' failed')
        task_mgmt.update_task(task_id, {State = task_state.Exception, Status = task_status.Error, 
            MessageId = 'CreateFailedMissingReqProperties', MessageArgs = {prop_name}})
        return task_id
    end

    local ok, prop_name, prop_value = is_create_bond_params_valid(requst)
    if not ok then
        log:operation(ctx:get_initiator(), 'NetworkAdapter', operate_log_msg .. ' failed')
        task_mgmt.update_task(task_id, {State = task_state.Exception, Status = task_status.Error, 
            MessageId = 'InvalidValue', MessageArgs = {prop_value, prop_name}})
        return task_id
    end

    local req = {
        RequestMethod = 'POST',
        RequestUri = BMA_SMS_TEAM_URI,
        RequestBody = json.encode({
            Name = requst.Name,
            WorkMode = requst.WorkMode,
            MIILinkMonitoringFrequencyMS = requst.MIILinkMonitoringFrequencyMS,
            Ports = ports,
            IPv4Addresses = {
                [1] = {
                    AddressOrigin = requst.AddressOrigin
                }
            },
            LinkStatus = requst.LinkStatus,
            OnBoot = requst.OnBoot
        })
    }

    common:handle_bma_task(ctx, req, task_id, common.CMD_TYPE.CREATE, operate_log_msg, find_bond_object)
    return task_id
end

function c_bma_rpc_bond:delete_bond(obj, ctx, bond_id)
    local operate_log_msg = 'Delete ' .. bond_id
    local create, err, task_id = task_mgmt.create_task(self.bus, 'Bond(' .. bond_id .. ') Deletion', obj.path)
    if create ~= task_mgmt.create_code.TASK_CREATE_SUCCESSFUL then
        log:error('creat DeleteBond task failed')
        log:operation(ctx:get_initiator(), 'NetworkAdapter', operate_log_msg .. ' failed')
        error(err)
    end

    if not common.is_bma_connected() then
        common.update_task_msg_bma_not_present(task_id)
        log:operation(ctx:get_initiator(), 'NetworkAdapter', operate_log_msg .. ' failed')
        return task_id
    end

    local req = {
        RequestMethod = 'DELETE',
        RequestUri = BMA_SMS_TEAM_URI .. '/' .. bond_id
    }

    common:handle_bma_task(ctx, req, task_id, common.CMD_TYPE.DELETE, operate_log_msg, function(bond_id)
    end)
    return task_id
end

function c_bma_rpc_bond:set_link_monitor_period(obj, ctx, bond_id, monitor_freq)
    local physical_port = c_network_port.collection:find(function(obj)
        return obj.NodeId == bond_id
    end)

    if not physical_port then
        log:error('cannot find physical_port for %s', bond_id)
        return
    end

    local operate_log_msg = string.format("Set %s link monitor frequency to %s", physical_port.BDF, monitor_freq)
    local create, err, task_id = task_mgmt.create_task(self.bus, 'Set ' .. bond_id .. ' link monitor frequency', 
        obj.path)
    if create ~= task_mgmt.create_code.TASK_CREATE_SUCCESSFUL then
        log:error('creat ConfigBond task failed')
        log:operation(ctx:get_initiator(), 'NetworkAdapter', operate_log_msg .. ' failed')
        error(err)
    end

    if not common.is_bma_connected() then
        common.update_task_msg_bma_not_present(task_id)
        log:operation(ctx:get_initiator(), 'NetworkAdapter', operate_log_msg .. ' failed')
        return task_id
    end

    local req = {
        RequestMethod = 'POST',
        RequestUri = BMA_SMS_TEAM_URI .. '/' .. bond_id .. '/Actions/NetworkBonding.Configure',
        RequestBody = json.encode({
            MIILinkMonitoringFrequencyMS = monitor_freq
        })
    }

    common:handle_bma_task(ctx, req, task_id, common.CMD_TYPE.MODIFY, operate_log_msg, function()
        physical_port.LinkMonitorPeriodMS = tonumber(monitor_freq)
    end)
    return task_id
end

return c_bma_rpc_bond