-- 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 lu = require 'luaunit'
local c_object_manage = require 'mc.orm.object_manage'
local c_network_adapter_db = require 'network_adapter.db'
local test_common = require 'test_common.utils'
local c_tasks = require 'mc.orm.tasks'
local hook_tasks = require 'test_common.hook_tasks'
local app_base = require 'mc.app_base'
local dbus_sms = require 'dbus_sms'
local test_bma_1822 = require 'test_datas.bma_1822'
local test_bma_nic = require 'test_datas.bma_nic'
local test_utils = require 'test_utils'
local json = require 'cjson'
local worker = require 'worker.core'
local c_bma = require 'bma'
local object_path = require 'dbus.object_path'
local c_ipv4_address = require 'device.class.ipv4_address'
local c_ipv6_address = require 'device.class.ipv6_address'
local c_vlan = require 'device.class.vlan'
local unescape = object_path.unescape

-- 打桩BMA信号监听
local client = require 'network_adapter.client'
client.OnSmsStatusPropertiesChanged = function() end
client.OnSmBiosInterfacesAdded = function () end
client.OnSmBiosPropertiesChanged = function () end

local ipv4_obj = test_utils.ipv4_obj
local ipv6_obj = test_utils.ipv6_obj
local find_address = test_utils.find_address

TEST_bma_redfish_resource = {}

function TEST_bma_redfish_resource:setUp()
    self.bus = test_common.dbus_launch('../.dbus', nil, true)
    self.bus:request_name('bmc.kepler.network_adapter')
    self.database = c_network_adapter_db(':memory:')
    hook_tasks.hook()
    self.object_manage = c_object_manage.new(self.database, self.bus)
    self.object_manage.app = self
    self.host_agent_app = app_base.new('host_agent')
    self.bma = c_bma.new(self.bus)
    self.tasks = c_tasks.new()

    -- 初始化 bma 资源树管理模块
    self.bma_mdb_mgmt = self.bma.bma_mdb_mgmt
    self:install_dbus_emit_hook()
    self.tasks:run_all_task()

    -- 模拟 host_agent 服务，并添加一个 1822 网口
    self.work = worker.new(0)
    self.work:start_module('test_bma.mock_bma_dbus_resource')
    self.work:send('1', true)
    self:update_host_agent_and_wait(test_bma_1822.EthernetInterface)

    local position = '00010102'
    self.network_adapter_obj = test_utils.add_network_adapter(self.object_manage,
        'NetworkAdapter_1', {
            BoardID = 0x123,
            DeviceLocator = 'some locator',
            Position = 'position',
            Type = 'Physical',
            Model = 'MyMode',
            Name = "a network card",
            Bus = 5,
            Device = 1,
            Function = 0,
            DevBus = 5,
            DevDevice = 1,
            DevFunction = 1
        }, position)
    self.network_port_obj = test_utils.add_network_port(self.object_manage, 'NetworkPort_1',
        {PortID = 0, NetDevFuncType = 1}, position, self.network_adapter_obj)
    self.optical_module_obj = test_utils.add_optical_module(self.object_manage, 'OpticalModule_1',
        {FaultState = 0}, position, self.network_port_obj)
    test_utils.add_object_complete(self.object_manage, position)

    self.object_manage.mc:prepare_ok()
end

function TEST_bma_redfish_resource:CreateVLANs()
end

function TEST_bma_redfish_resource:tearDown()
    self.bus:call(self.host_agent_app.service_name, self.host_agent_app.app_path,
        self.host_agent_app.app_interface, 'exit')
    self.work:stop()

    c_bma.destroy()
    c_object_manage.destroy()
    self.database.db:close()
    hook_tasks.unhook()
    self.bus:close()
end

function TEST_bma_redfish_resource:CreateIPv4Address(SystemID, ID1, ID2, ID3, prop_setting_cb)
    return test_utils.CreateIPv4Address(SystemID, ID1, ID2, ID3, prop_setting_cb)
end

function TEST_bma_redfish_resource:CreateIPv6Address(SystemID, ID1, ID2, ID3, prop_setting_cb)
    return test_utils.CreateIPv6Address(SystemID, ID1, ID2, ID3, prop_setting_cb)
end

function TEST_bma_redfish_resource:CreateVLANIPv4Address(SystemID, ID1, ID2, ID3, ID4,
    prop_setting_cb)
    return test_utils.CreateVLANIPv4Address(SystemID, ID1, ID2, ID3, ID4, prop_setting_cb)
end

function TEST_bma_redfish_resource:CreateVLANIPv6Address(SystemID, ID1, ID2, ID3, ID4,
    prop_setting_cb)
    return test_utils.CreateVLANIPv6Address(SystemID, ID1, ID2, ID3, ID4, prop_setting_cb)
end

function TEST_bma_redfish_resource:CreateVLAN(SystemID, ID1, ID2, ID3, prop_setting_cb)
    return test_utils.CreateVLAN(SystemID, ID1, ID2, ID3, prop_setting_cb)
end

function TEST_bma_redfish_resource:install_dbus_emit_hook()
    self.result = {}
    self.bma_mdb_mgmt.on_add:on(function(path, data)
        self.result[#self.result + 1] = {type = 'on_add', path = path, data = data}
    end)

    self.bma_mdb_mgmt.on_update:on(function(path, data)
        self.result[#self.result + 1] = {type = 'on_update', path = path, data = data}
    end)

    self.bma_mdb_mgmt.on_delete:on(function(path)
        self.result[#self.result + 1] = {type = 'on_delete', path = path}
    end)
end

function TEST_bma_redfish_resource:update_host_agent_and_wait(data)
    self.result = {}
    self.bus:call(self.host_agent_app.service_name, self.host_agent_app.app_path,
        self.host_agent_app.app_interface, 'update', 's', data)
    self:wait_dbus_signal()
end

function TEST_bma_redfish_resource:wait_dbus_signal()
    self.bus.bus:run_until(function()
        return #self.result ~= 0
    end, 2000)
end

function TEST_bma_redfish_resource:test_bma_mdb_add_resource()
    local fructl = require 'infrastructure.fructl'
    local origin_get_power_status = fructl.get_power_status
    fructl.get_power_status = function()
        return 'ON'
    end
    local eth_data = json.decode(test_bma_1822.EthernetInterface)
    local sff_data = json.decode(test_bma_1822.OemEthernetInterfaceSff)

    self.result = {}

    -- 运行所有任务，触发 bma 资源树管理模块完整遍历一次所有资源
    self.tasks:run_all_task()

    -- 查看初始化构建的那张 1822 网卡是否更新了 bma 的值
    local na = self.network_adapter_obj
    local eth_huawei = eth_data.Oem.Huawei
    lu.assertNotEquals(#self.result, 0)
    lu.assertEquals(self.result[1].type, 'on_add')
    lu.assertEquals(self.result[1].path, unescape(dbus_sms.to_bmc_url(eth_data['@odata.id'])))
    lu.assertEquals(na.DisplayName, eth_huawei.NICName)
    lu.assertEquals(na.RootBDF, eth_huawei.BDFNumber.RootBDF)

    -- 模拟加入光模块
    local op = self.optical_module_obj
    self:update_host_agent_and_wait(test_bma_1822.OemEthernetInterfaceSff)
    lu.assertEquals(#self.result, 1)
    lu.assertEquals(self.result[1].type, 'on_add')
    lu.assertEquals(self.result[1].path, unescape(dbus_sms.to_bmc_url(sff_data['@odata.id'])))
    lu.assertEquals(op.Manufacturer, sff_data.VendorName)
    fructl.get_power_status = origin_get_power_status
end

function TEST_bma_redfish_resource:test_bma_mdb_update_resource()
    local fructl = require 'infrastructure.fructl'
    local origin_get_power_status = fructl.get_power_status
    fructl.get_power_status = function()
        return 'ON'
    end
    local eth_data = json.decode(test_bma_1822.EthernetInterface)

    self.tasks:run_all_task()
    local old_ipv4_addrs = c_ipv4_address.get_addresses()

    eth_data.IPv4Addresses = {
        {
            Address = '127.0.0.0',
            AddressOrigin = 'Static',
            Gateway = {'196.128.0.0'},
            SubnetMask = '0.0.0.0'
        }
    }
    self:update_host_agent_and_wait(json.encode(eth_data))

    local new_ipv4_addrs = c_ipv4_address.get_addresses()
    lu.assertEquals(#new_ipv4_addrs, #old_ipv4_addrs + 1)
    for _, v in ipairs(new_ipv4_addrs) do
        lu.assertEquals(ipv4_obj(v), ipv4_obj(find_address(eth_data.IPv4Addresses, v.Address)))
    end

    -- 删除第一个地址
    table.remove(eth_data.IPv4Addresses, 1)
    self:update_host_agent_and_wait(json.encode(eth_data))
    local last_ipv4_addrs = c_ipv4_address.get_addresses()
    lu.assertEquals(#last_ipv4_addrs, #new_ipv4_addrs - 1)
    for _, v in ipairs(last_ipv4_addrs) do
        lu.assertEquals(ipv4_obj(v), ipv4_obj(find_address(eth_data.IPv4Addresses, v.Address)))
    end

    -- 清空地址
    eth_data.IPv4Addresses = json.null
    self:update_host_agent_and_wait(json.encode(eth_data))
    lu.assertEquals(c_ipv4_address.get_addresses(), {})
    fructl.get_power_status = origin_get_power_status
end

function TEST_bma_redfish_resource:test_bma_update_ipv6_address()
    local fructl = require 'infrastructure.fructl'
    local origin_get_power_status = fructl.get_power_status
    fructl.get_power_status = function()
        return 'ON'
    end
    local eth_data = json.decode(test_bma_1822.EthernetInterface)

    self.tasks:run_all_task()

    -- 插入一个 ipv6 地址
    eth_data.IPv6Addresses[#eth_data.IPv6Addresses + 1] = {
        Address = '8888:1871:1:0:f40:3935:cf5c:aaaa',
        AddressOrigin = 'SLAAC',
        AddressState = 'Deprecated',
        PrefixLength = '64'
    }
    self:update_host_agent_and_wait(json.encode(eth_data))

    local new_ipv6_addrs = c_ipv6_address.get_addresses()
    lu.assertEquals(#new_ipv6_addrs, #eth_data.IPv6Addresses)
    for _, v in ipairs(new_ipv6_addrs) do
        lu.assertEquals(ipv6_obj(v), ipv6_obj(find_address(eth_data.IPv6Addresses, v.Address)))
    end

    -- 修改 + 插入
    eth_data.IPv6Addresses[1].PrefixLength = '128'
    eth_data.IPv6Addresses[#eth_data.IPv6Addresses + 1] = {
        Address = '8888:1871:1:0:f40:3935:cf5c:bbb',
        AddressOrigin = 'SLAAC',
        AddressState = 'Deprecated',
        PrefixLength = '99'
    }
    self:update_host_agent_and_wait(json.encode(eth_data))
    new_ipv6_addrs = c_ipv6_address.get_addresses()
    lu.assertEquals(#new_ipv6_addrs, #eth_data.IPv6Addresses)
    for _, v in ipairs(new_ipv6_addrs) do
        lu.assertEquals(ipv6_obj(v), ipv6_obj(find_address(eth_data.IPv6Addresses, v.Address)))
    end

    -- 删除第一个地址
    table.remove(eth_data.IPv6Addresses, 1)
    self:update_host_agent_and_wait(json.encode(eth_data))
    local last_ipv6_addrs = c_ipv6_address.get_addresses()
    lu.assertEquals(#last_ipv6_addrs, #eth_data.IPv6Addresses)
    for _, v in ipairs(last_ipv6_addrs) do
        lu.assertEquals(ipv6_obj(v), ipv6_obj(find_address(eth_data.IPv6Addresses, v.Address)))
    end

    -- 清空地址
    eth_data.IPv6Addresses = json.null
    self:update_host_agent_and_wait(json.encode(eth_data))
    lu.assertEquals(c_ipv6_address.get_addresses(), {})
    fructl.get_power_status = origin_get_power_status
end

function TEST_bma_redfish_resource:test_bma_to_json()
    local fructl = require 'infrastructure.fructl'
    local origin_get_power_status = fructl.get_power_status
    fructl.get_power_status = function()
        return 'ON'
    end
    local eth_data = json.decode(test_bma_1822.EthernetInterface)
    self.tasks:run_all_task()

    local path = dbus_sms.to_bmc_url(eth_data['@odata.id'])
    local json_data = json.decode(self.bus:call(self.host_agent_app.service_name, path,
        'bmc.kepler.sms', 'to_json', 'as', {}))
    local expected = test_utils.json_data_to_dbus_data(eth_data)
    lu.assertEquals(json_data, expected)
    fructl.get_power_status = origin_get_power_status
end

-- 测试 BMA 刷新到 NIC 卡资源，但是自发现之后才发现这张卡，
-- 这会触发向 BMA 主动获取一次该资源
function TEST_bma_redfish_resource:test_bma_reload_resource()
    local fructl = require 'infrastructure.fructl'
    local origin_get_power_status = fructl.get_power_status
    fructl.get_power_status = function()
        return 'ON'
    end
    -- host_agent 添加 NIC 资源
    self:update_host_agent_and_wait(test_bma_nic.EthernetInterface)
    self.tasks:run_all_task()

    -- host_agent 添加 NIC VLAN 资源
    self:update_host_agent_and_wait(test_bma_nic.VLanNetworkInterface)
    self.tasks:run_all_task()

    -- 自发现分发 NIC 卡
    local position = '00010103'
    local na = test_utils.add_network_adapter(self.object_manage, 'NetworkAdapter_1' .. position, {
        BoardID = 0x123,
        DeviceLocator = 'nic locator',
        Position = 'position',
        Type = 'Physical',
        Model = 'MyMode',
        Name = "a network card",
        Bus = 188,
        Device = 0,
        Function = 0,
        DevBus = 5,
        DevDevice = 1,
        DevFunction = 0,
        BDF = '0000:bc:00.0'
    }, position)
    local np = test_utils.add_network_port(self.object_manage, 'NetworkPort_1' .. position,
        {PortID = 0, NetDevFuncType = 1}, position, na)
    self.tasks:run_all_task()

    -- 修改 BDF，触发重新加载资源
    np.BDF = '0000:bd:00.0'
    self.tasks:run_all_task()

    local data = json.decode(test_bma_nic.EthernetInterface)
    local huawei = data.Oem.Huawei
    lu.assertEquals(na.Manufacturer, data.Manufacturer)
    lu.assertEquals(na.DisplayName, huawei.NICName)

    lu.assertEquals(np.Name, huawei.NICName)
    lu.assertEquals(np.OSLinkStatus, data.LinkStatus)
    lu.assertEquals(np.MACAddress, string.upper(data.MACAddress))

    local vlan_data = json.decode(test_bma_nic.VLanNetworkInterface)
    local vlans = c_vlan.get_vlans(np.PortID, np.NetworkAdapterId)
    lu.assertEquals(#vlans, 1)
    lu.assertEquals(vlans[1].VLANId, vlan_data.VLANId)
    fructl.get_power_status = origin_get_power_status
end
