-- 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.

-- Description: 装备测试
local class = require 'mc.class'
local log = require 'mc.logging'
local mdb = require 'mc.mdb'
local ctx = require 'mc.context'
local skynet = require 'skynet'
local ncsi_core = require 'ncsi.ncsi_core'
local manufacture_app = class()

local STATUS_COMPLETE = "Complete"
local STATUS_TESTING = "Testing"
local STATUS_UNSTART = "Unstart"

local RESULT_SUCCEED = "Succeed"
local RESULT_FAILED = "Failed"
local RESULT_NON = "Non"

local NCSI_CABLE_PATH <const> = '/bmc/kepler/Systems/1/NCSICapabilities'
local NCSI_CABLE_INTERFACE <const> = 'bmc.kepler.Systems.NCSICapabilities'

local NCSI_DISABLED = 0
local NCSI_ENABLED = 1
local bmc_eth_id = 1
function manufacture_app.new(app)
    return setmetatable({ app = app }, manufacture_app)
end

function manufacture_app:init()
    self.description = ''
    self.test_in_process_flag = false
    self:register_rpc_methods()
end

local function check_ncsi(bus)
    local ok, obj = pcall(mdb.get_object, bus, NCSI_CABLE_PATH, NCSI_CABLE_INTERFACE)
    if not ok then
        log:error('[network_adapter] get object ncsi cable failed')
        return
    end
    if obj and obj.PCIeNCSIEnabled == 1 then
        return true
    end
end

local function switch_port(eth_obj, eth_id, bus, eth_name, channel_id)
    local net_mode = 'Fixed'
    local vlan_state = false
    local vlan_id = 0

    local ok, _ = pcall(eth_obj.SetNetworkConfig_PACKED, bus, ctx.new(),
        net_mode, eth_id, vlan_state, vlan_id)
    if not ok then
        log:error('[network_adapter] ncsi switch mgmt_port%s failed', eth_id)
        return
    end
    -- 初始化
    local package_id
    ok, package_id = ncsi_core.ncsi_paramter_init(channel_id, eth_name, NCSI_ENABLED)
    if not ok then
        log:info('[network_adapter] ncsi_paramter_init failed')
        return
    end
    -- 取值0, 1
    local link_status
    ok, link_status = ncsi_core.ncsi_get_link_status(package_id, channel_id, eth_name, eth_id)
    if not ok then
        log:info('[network_adapter] ncsi: ncsi_get_link_status failed, eth_id: %s', eth_id)
        return
    end
    log:info('[network_adapter] ncsi: link_status: %s', link_status)
    -- 切换恢复
    ok, _ = pcall(eth_obj.SetNetworkConfig_PACKED, bus, ctx.new(),
        net_mode, bmc_eth_id, vlan_state, vlan_id)
    if not ok then
        log:error('[network_adapter] ncsi: reset port failed')
    end
end

function manufacture_app:verify_ncsi(bus)
    local ok = check_ncsi(bus)
    if not ok then
        return
    end
    local ncsi_service = self.app.ncsi_service
    if not ncsi_service then
        return
    end
    local ncsi_info = ncsi_service.ncsi_info
    -- 前置需求不完备，这里默认切换第一个PCIe口，即注册过去第二个网口
    local eth_id = 2
    local eth_obj = ncsi_info.ncsi_get_eth_obj()
    local eht_port_obj = ncsi_info:ncsi_get_eth_port_obj(eth_id)
    if not eth_obj and not eht_port_obj then
        return
    end
    local db = ncsi_service.db
    local nc_info = db:select(db.NcsiNCInfo):first()
    local port_info = db:select(db.NcsiNCPortInfo):where({ MgmtPortId = eth_id }):first()
    local pre_active_port = nc_info.ActivePort
    local active_port = db:select(db.NcsiNCPortInfo):where({ PortId = pre_active_port }):first()
    if active_port and active_port ~= 0 then
        bmc_eth_id = active_port.MgmtPortId
    end

    if not port_info then
        return
    end
    -- 管理组id 默认0
    local eth_name = "eth" .. (nc_info and nc_info.EthId or '0')
    local channel_id = port_info.ChannelId
    local link_status = switch_port(eth_obj, eth_id, bus, eth_name, channel_id)
    local link_status_str = link_status == 0 and 'Disconnected' or 'Connected'
    return link_status_str
end

function manufacture_app:dft_ncsi_start(obj)
    if obj.Status == STATUS_TESTING then
        log:info("[network_adapter] ncsi test in process, please wait")
        return
    end
    obj.Status = STATUS_TESTING
    skynet.fork(function()
        self.test_in_process_flag = true
        local ret = self:verify_ncsi(self.app.bus)
        if ret then
            self.description = ret
        end
        self.test_in_process_flag = false
    end)
end

function manufacture_app:dft_ncsi_stop(obj)
    if obj.Status == STATUS_TESTING then
        log:info("[network_adapter] ncsi test in process, please wait")
        return
    end
    self.test_in_process_flag = false
    obj.Status = STATUS_UNSTART
end

function manufacture_app:dft_ncsi_get_result(obj)
    if obj.Status == STATUS_UNSTART then
        self.description = ''
        return RESULT_SUCCEED, self.description
    end

    if self.test_in_process_flag then
        log:info("[network_adapter] ncsi test in process, please wait")
        return RESULT_NON, ""
    end
    obj.Status = STATUS_COMPLETE
    return RESULT_SUCCEED, self.description
end
function manufacture_app:register_rpc_methods()
    self:register_ncsi_test()
end

function manufacture_app:register_ncsi_test()
    self.app:ImplDftNCSIManufactureStart(function(obj, ...)
        return self:dft_ncsi_start(obj)
    end)
    self.app:ImplDftNCSIManufactureStop(function(obj, ...)
        return self:dft_ncsi_stop(obj)
    end)
    self.app:ImplDftNCSIManufactureGetResult(function(obj, ...)
        return self:dft_ncsi_get_result(obj)
    end)
end

return manufacture_app
