-- 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 skynet = require 'skynet'
local class = require 'mc.class'
local class_mgnt = require 'mc.class_mgnt'
local log = require 'mc.logging'
local context = require 'mc.context'
local mc_initiator = require 'mc.initiator'
local task_mgmt = require 'mc.mdb.task_mgmt'
local task_state <const> = task_mgmt.state
local task_status <const> = task_mgmt.status
local base_messages = require 'messages.base'
local client = require 'network_adapter.client'
local common_func = require 'common.common_func'

local function get_op_initiator()
    local initiator = {}
    local ctx = context.get_context()
    if ctx and common_func:has_initiator(ctx) then
        initiator = ctx:get_initiator()
    else
        initiator = mc_initiator.new('N/A', 'N/A', 'localhost')
    end
    return initiator
end

local function update_task_status(task_id, progress, state, status, messaged_id)
    local data = {
        Progress = progress,
        State = state,
        Status = status,
        MessageId = messaged_id
    }
    local update = task_mgmt.update_task(task_id, data)
    if update ~= task_mgmt.update_code.TASK_UPDATE_SUCCESSFUL then
        log:error('update %s failed!', task_mgmt.get_task_obj(task_id).Name)
    end
end

local c_bma_rpc_port = class()

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

local NET_TYPE_LOM <const> = 1
local NET_TYPE_PCIE <const> = 3

local BUSINESSPORT_CARD_TYPE_TEAM<const> = 7

local function create_vlan_operate_log(obj, state)
    local port = obj:get_parent()
    local adapter
    if port.CreatedByDeviceObject then
        adapter = port.parent
    else
        adapter = port:get_parent()
    end
    if not adapter then
        log:error('Cannot find NetworkAdapter parent: path = %s', port.path)
        return
    end

    local type = adapter.Type
    if type == BUSINESSPORT_CARD_TYPE_TEAM then
        log:operation(get_op_initiator(), 'NetworkAdapter', 'Create VLAN on %s', port.BDF)
    elseif type == NET_TYPE_LOM or type == NET_TYPE_PCIE then
        log:operation(get_op_initiator(), 'NetworkAdapter', 'Create VLAN on %s card %s port%s %s',
            type == NET_TYPE_LOM and 'LOM' or 'PCIe', adapter.SlotNumber, port.PortID + 1,
            state and 'successfully' or 'failed')
    else
        log:error('Invalid card type is %d', type)
    end
end

local function get_redfish_message_args(body)
    return body.error and body.error['@Message.ExtendedInfo'][1].MessageArgs[1] or body.message
end

local TASK_TIMEOUT<const> = 60 -- 60秒超时

local function wait_bma_task(self_task_id, bma_task_id)
    local forward_request = json.encode({
        RequestMethod = 'GET',
        RequestUri = string.format('/redfish/v1/Sms/1/TaskService/Tasks/%s', bma_task_id)
    })

    local is_ok, ret
    local response, body
    local wait_count = 0
    while wait_count < TASK_TIMEOUT do
        is_ok, ret = client:PSmsSmsForwardRequest(context.get_context(), {SystemId = 1}, forward_request, 1)
        if not is_ok then
            log:error('Forward request failed: %s', ret)
            return
        end

        response = json.decode(ret.Response)
        body = json.decode(response.ResponseBody)
        if body.TaskState == 'Completed' then
            update_task_status(self_task_id, 100, task_state.Completed, task_status.OK)
            return true
        elseif body.TaskState ~= 'Running' then
            update_task_status(self_task_id, nil, task_state.Exception, task_status.Error, 'TaskFailed')
            return false
        end

        wait_count = wait_count + 2
        skynet.sleep(200) -- 等待两秒再查任务
    end
    log:error('Task running timeout')
    update_task_status(self_task_id, nil, task_state.Exception, task_status.Error, 'OperationTimeout')
    return false
end

local TASK_PROCESS_OK<const> = 200

local function create_vlan_request(task_id, obj, request)
    local port = obj:get_parent()
    local agent_id = port.AgentID or ''
    local id1, id2, id3 = agent_id:match('/Sms/(%d+)/.+/Systems/(%d+)/EthernetInterfaces/(.+)$')
    local forward_request = json.encode({
        RequestMethod = 'POST',
        RequestUri = string.format('/redfish/v1/Sms/%s/Systems/%s/EthernetInterfaces/%s/VLANs', id1, id2, id3),
        RequestBody = json.encode(request)
    })

    local is_ok, ret = client:PSmsSmsForwardRequest(context.get_context(), {SystemId = 1}, forward_request, 1)
    if not is_ok then
        log:error('Forward request failed: %s', ret)
        update_task_status(task_id, nil, 'Exception','Error', 'InternalError')
        return
    end

    local response = json.decode(ret.Response)
    local status_code = response.ResponseStatusCode
    local body = json.decode(response.ResponseBody)
    if status_code ~= TASK_PROCESS_OK then
        log:error('Forward request failed. status code: %s msg: %s', status_code, get_redfish_message_args(body))
        update_task_status(task_id, nil, 'Exception','Error', 'InternalError')
    else
        update_task_status(task_id, nil, 'Running')
    end

    create_vlan_operate_log(obj, status_code == TASK_PROCESS_OK and wait_bma_task(task_id, body.TaskId))
end

local MAX_VLAN_ID<const> = 4094

function c_bma_rpc_port:create_vlan(obj, _, request)
    local bma_req = {
        VLANId = tonumber(request.VLANId),
        IPv4Addresses = request.IPv4Addresses and json.decode(request.IPv4Addresses),
        OnBoot = request.OnBoot
    }

    if request.VLANEnable then
        bma_req.VLANEnable = string.lower(request.VLANEnable) == 'true' and true or false
    end

    local sms_obj = client:GetSmsSmsObject({SystemId = 1})
    if not sms_obj or not sms_obj.Registered then
        log:error('BMA not present')
        create_vlan_operate_log(obj, false)
        error(base_messages.InternalError())
    end

    if bma_req.VLANId > MAX_VLAN_ID then
        log:error('vlan id out of range, value is %s', bma_req.VLANId)
        create_vlan_operate_log(obj, false)
        error(base_messages.InternalError())
    end

    local port = obj:get_parent()
    local create, err, task_id = task_mgmt.create_task(self.bus,
        string.format('%s VLAN Creation', port.Name), obj.path, 10)  -- 10分钟
    if create ~= task_mgmt.create_code.TASK_CREATE_SUCCESSFUL then
        create_vlan_operate_log(obj, false)
        error(err)
    end

    skynet.fork_once(create_vlan_request, task_id, obj, bma_req)
    return task_id
end

local function set_vlan_operate_log(obj, request, state)
    local port = obj:get_parent()
    if not port then
        log:error('Cannot find NetworkPort parent: path = %s', obj.path)
        return
    end

    local op_msg = ''
    if request.VLANEnable ~= nil then
        op_msg = string.format('%s VLAN', request.VLANEnable and 'Open' or 'Close')
    end

    if request.IPv4Addresses and request.IPv4Addresses[1] and (#request.IPv4Addresses[1].Address > 0 or
        #request.IPv4Addresses[1].SubnetMask > 0 or #request.IPv4Addresses[1].Gateway > 0 or
        #request.IPv4Addresses[1].AddressOrigin > 0) then
        if request.VLANEnable ~= nil then
            op_msg = op_msg .. ';'
        end
        op_msg = op_msg .. 'Set IPv4 info';
    end

    local adapter
    if port.CreatedByDeviceObject then
        adapter = port.parent
    else
        adapter = port:get_parent()
    end
    if not adapter then
        log:error('Cannot find NetworkAdapter parent: path = %s', port.path)
        return
    end
    local type = adapter.Type
    if type == BUSINESSPORT_CARD_TYPE_TEAM then
        log:operation(get_op_initiator(), 'NetworkAdapter', '%s for %s VLAN %s %s', op_msg, port.BDF, obj.VLANId,
            state and 'successfully' or 'failed')
    elseif type == NET_TYPE_LOM or type == NET_TYPE_PCIE then
        log:operation(get_op_initiator(), 'NetworkAdapter', '%s for %s card %s port%s VLAN %s %s',
            op_msg, type == NET_TYPE_LOM and 'LOM' or 'PCIe', adapter.SlotNumber, port.PortID + 1, obj.VLANId,
            state and 'successfully' or 'failed')
    else
        log:error('Invalid card type is %d', type)
    end
end

local function set_vlan_request(task_id, obj, request)
    local port = obj:get_parent()
    local agent_id = port.AgentID or ''
    local id1, id2, id3 = agent_id:match('/Sms/(%d+)/.+/Systems/(%d+)/EthernetInterfaces/(.+)$')
    local forward_request = json.encode({
        RequestMethod = 'POST',
        RequestUri = string.format('/redfish/v1/Sms/%s/Systems/%s/EthernetInterfaces/%s/VLANs/%s/' ..
            'Actions/VLanNetworkInterface.Configure', id1, id2, id3, obj.VLANId),
        RequestBody = json.encode(request)
    })

    local is_ok, ret = client:PSmsSmsForwardRequest(context.get_context(), {SystemId = 1}, forward_request, 1)
    if not is_ok then
        log:error('Forward request failed: %s', ret)
        update_task_status(task_id, nil, 'Exception','Error', 'InternalError')
        return
    end

    local response = json.decode(ret.Response)
    local status_code = response.ResponseStatusCode
    local body = json.decode(response.ResponseBody)
    if status_code ~= TASK_PROCESS_OK then
        log:error('Forward request failed. status code: %s msg: %s', status_code, get_redfish_message_args(body))
        update_task_status(task_id, nil, 'Exception','Error', 'InternalError')
        return
    else
        update_task_status(task_id, nil, 'Running')
    end

    set_vlan_operate_log(obj, request, wait_bma_task(task_id, body.TaskId))
end

function c_bma_rpc_port:configure_vlan(obj, _, request)
    local bma_req = {
        IPv4Addresses = request.IPv4Addresses and json.decode(request.IPv4Addresses),
        OnBoot = request.OnBoot
    }

    if request.VLANEnable then
        bma_req.VLANEnable = string.lower(request.VLANEnable) == 'true' and true or false
    end

    local sms_obj = client:GetSmsSmsObject({SystemId = 1})
    if not sms_obj or not sms_obj.Registered then
        log:error('BMA not present')
        set_vlan_operate_log(obj, bma_req, false)
        error(base_messages.InternalError())
    end

    local port = obj:get_parent()
    local create, err, task_id = task_mgmt.create_task(self.bus,
        string.format('Set %s VLAN %s information', port.Name, obj.VLANId), obj.path, 10)  -- 10分钟
    if create ~= task_mgmt.create_code.TASK_CREATE_SUCCESSFUL then
        set_vlan_operate_log(obj, bma_req, false)
        error(err)
    end

    skynet.fork_once(set_vlan_request, task_id, obj, bma_req)
    return task_id
end

local function delete_vlan_operate_log(obj, state)
    local port = obj:get_parent()
    if not port then
        log:error('Cannot find NetworkPort parent: path = %s', obj.path)
        return
    end

    local adapter
    if port.CreatedByDeviceObject then
        adapter = port.parent
    else
        adapter = port:get_parent()
    end
    if not adapter then
        log:error('Cannot find NetworkAdapter parent: path = %s', port.path)
        return
    end
    local type = adapter.Type
    if type == BUSINESSPORT_CARD_TYPE_TEAM then
        log:operation(get_op_initiator(), 'NetworkAdapter', 'Delete %s VLAN %s %s', port.BDF, obj.VLANId,
            state and 'successfully' or 'failed')
    elseif type == NET_TYPE_LOM or type == NET_TYPE_PCIE then
        log:operation(get_op_initiator(), 'NetworkAdapter', 'Delete %s card %s port%s VLAN %s %s',
            type == NET_TYPE_LOM and 'LOM' or 'PCIe', adapter.SlotNumber, port.PortID + 1, obj.VLANId,
            state and 'successfully' or 'failed')
    else
        log:error('Invalid card type is %d', type)
    end
end

local function delete_vlan_request(task_id, obj)
    local port = obj:get_parent()
    local agent_id = port.AgentID or ''
    local id1, id2, id3 = agent_id:match('/Sms/(%d+)/.+/Systems/(%d+)/EthernetInterfaces/(.+)$')
    local forward_request = json.encode({
        RequestMethod = 'DELETE',
        RequestUri = string.format('/redfish/v1/Sms/%s/Systems/%s/EthernetInterfaces/%s/VLANs/%s',
            id1, id2, id3, obj.VLANId)
    })

    local is_ok, ret = client:PSmsSmsForwardRequest(context.get_context(), {SystemId = 1}, forward_request, 1)
    if not is_ok then
        log:error('Forward request failed: %s', ret)
        return
    end

    local response = json.decode(ret.Response)
    local status_code = response.ResponseStatusCode
    local body = json.decode(response.ResponseBody)
    if status_code ~= TASK_PROCESS_OK then
        log:error('Forward request failed. status code: %s msg: %s', status_code, get_redfish_message_args(body))
        return
    end

    delete_vlan_operate_log(obj, wait_bma_task(task_id, body.TaskId))
end

function c_bma_rpc_port:delete_vlan(obj)
    local sms_obj = client:GetSmsSmsObject({SystemId = 1})
    if not sms_obj or not sms_obj.Registered then
        log:error('BMA not present')
        delete_vlan_operate_log(obj, false)
        error(base_messages.InternalError())
    end

    local port = obj:get_parent()
    local create, err, task_id = task_mgmt.create_task(self.bus,
        string.format('%s VLAN(%s) Deletion', port.Name, obj.VLANId), obj.path, 10)  -- 10分钟
    if create ~= task_mgmt.create_code.TASK_CREATE_SUCCESSFUL then
        delete_vlan_operate_log(obj, false)
        error(err)
    end

    skynet.fork_once(delete_vlan_request, task_id, obj)
    return task_id
end

local function configure_operate_log(obj, state)
    local adapter
    if obj.CreatedByDeviceObject then
        adapter = obj.parent
    else
        adapter = obj:get_parent()
    end
    if not adapter then
        log:error('Cannot find NetworkAdapter parent: path = %s', obj.path)
        return
    end
    local type = adapter.Type
    if type == BUSINESSPORT_CARD_TYPE_TEAM then
        log:operation(get_op_initiator(), 'NetworkAdapter', 'Set %s IPv4 info %s', obj.BDF,
            state and 'successfully' or 'failed')
    elseif type == NET_TYPE_LOM or type == NET_TYPE_PCIE then
        log:operation(get_op_initiator(), 'NetworkAdapter', 'Set %s card %s port%s IPv4 info %s',
            type == NET_TYPE_LOM and 'LOM' or 'PCIe', adapter.SlotNumber, obj.PortID + 1,
            state and 'successfully' or 'failed')
    else
        log:error('Invalid card type is %d', type)
    end
end

local function configure_request(task_id, obj, request)
    local agent_id = obj.AgentID or ''
    local id1, id2, id3 = agent_id:match('/Sms/(%d+)/.+/Systems/(%d+)/EthernetInterfaces/(.+)$')
    local forward_request = json.encode({
        RequestMethod = 'POST',
        RequestUri = string.format('/redfish/v1/Sms/%s/Systems/%s/EthernetInterfaces/%s' ..
            '/Actions/EthernetInterface.Configure', id1, id2, id3),
        RequestBody = json.encode(request)
    })

    local is_ok, ret = client:PSmsSmsForwardRequest(context.get_context(), {SystemId = 1}, forward_request, 1)
    if not is_ok then
        log:error('Forward request failed: %s', ret)
        return
    end

    local response = json.decode(ret.Response)
    local status_code = response.ResponseStatusCode
    local body = json.decode(response.ResponseBody)
    if status_code ~= TASK_PROCESS_OK then
        log:error('Forward request failed. status code: %s msg: %s', status_code, get_redfish_message_args(body))
        return
    end

    configure_operate_log(obj, wait_bma_task(task_id, body.TaskId))
end

function c_bma_rpc_port:configure(obj, _, request)
    local bma_req = {
        IPv4Addresses = request.IPv4Addresses and json.decode(request.IPv4Addresses),
        LinkStatus = request.LinkStatus,
        OnBoot = request.OnBoot
    }

    local port = class_mgnt('NetworkPort')[obj.path:match('Ports/([^/]+)$')]
    local sms_obj = client:GetSmsSmsObject({SystemId = 1})
    if not sms_obj or not sms_obj.Registered then
        log:error('BMA not present')
        configure_operate_log(port, false)
        error(base_messages.InternalError())
    end

    local create, err, task_id = task_mgmt.create_task(self.bus,
        string.format('Set %s IPv4', obj.Name), obj.path, 10)  -- 10分钟
    if create ~= task_mgmt.create_code.TASK_CREATE_SUCCESSFUL then
        configure_operate_log(port, false)
        error(err)
    end

    skynet.fork_once(configure_request, task_id, port, bma_req)
    return task_id
end

return c_bma_rpc_port