-- 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: test the external RPC interfaces of sensor.
local skynet = require 'skynet'
local log = require 'mc.logging'
local ctx = require 'mc.context'
local mdb = require 'mc.mdb'
local json = require 'cjson'
local utils = require 'mc.utils'
local open_local_db = require 'sensor.local_db'
require 'skynet.manager'
require 'sensor.json_types.Sensors'
require 'sensor.json_types.DiscreteSensor'
require 'sensor.json_types.DiscreteSensorDisplay'
require 'hwdiscovery.json_types.Connector'

local sensor_interface = {}
sensor_interface.__index = sensor_interface

local SENSORS_PATH = '/bmc/kepler/Chassis/1/Sensors'
local SENSORS_INTF = 'bmc.kepler.Chassis.Sensors'

local sensor_service = 'bmc.kepler.sensor'

function sensor_interface.test_get_threshold_sensor_list(bus)
    log:info('== test get threshold sensor list start ...')
    local mobj = mdb.get_object(bus, SENSORS_PATH, SENSORS_INTF)
    local rsp = mobj:GetThresholdSensorList_PACKED(ctx.new())
    assert(#rsp.SensorList == 3, 'actual: ' .. tostring(#rsp.SensorList))
    local o = rsp.SensorList[3]
    assert(o.SensorName == 'SYS 12V_2', 'actual: ' .. o.SensorName)
    assert(o.SensorReading == '11.940', 'actual: ' .. o.SensorReading)
    assert(o.SensorStatus == 'ok', 'actual: ' .. o.SensorStatus)
end

function sensor_interface.test_get_all_sensor_list(bus)
    log:info('== test get all sensor list start ...')
    local mobj = mdb.get_object(bus, SENSORS_PATH, SENSORS_INTF)
    local rsp = mobj:GetSensorList_PACKED(ctx.new())
    assert(#rsp.SensorList == 6, 'actual: ' .. tostring(#rsp.SensorList))
    local o = rsp.SensorList[6]
    assert(o.SensorName == 'Sel Status', 'actual: ' .. o.SensorName)
    assert(o.SensorReading == '0x0', 'actual: ' .. o.SensorReading)

    -- 测试SensorNumber分配是否合理
    table.sort(rsp.SensorList, function (a, b)
        return a.SensorNumber < b.SensorNumber
    end)
    o = rsp.SensorList[1]
    assert(o.SensorName == 'Inlet Temp', 'actual: ' .. o.SensorName)
    for idx, obj in pairs(rsp.SensorList) do
        assert(obj.SensorNumber == idx, 'actual: ' .. obj.SensorNumber)
    end
end

local function test_mock_sensor_temperature_normal(mobj)
    log:info('==== test mock temperature normal reading ...')
    -- 查询当前门限传感器
    local rsp = mobj:GetThresholdSensorList_PACKED(ctx.new())
    assert(#rsp.SensorList == 3, 'actual: ' .. tostring(#rsp.SensorList))
    local o = rsp.SensorList[1]
    assert(o.SensorName == 'Inlet Temp', 'actual: ' .. o.SensorName)
    assert(o.SensorReading == '35.000', 'actual: ' .. o.SensorReading)

    -- 模拟传感器值
    mobj:MockSensor(ctx.new('IT', 'Admin', '127.0.0.1'), 1, 'Inlet Temp', '30')
    rsp = mobj:GetThresholdSensorList_PACKED(ctx.new())
    assert(#rsp.SensorList == 3, 'actual: ' .. tostring(#rsp.SensorList))
    o = rsp.SensorList[1]
    assert(o.SensorName == 'Inlet Temp', 'actual: ' .. o.SensorName)
    assert(o.SensorReading == '30.000', 'actual: ' .. o.SensorReading)

    -- 恢复单个模拟
    mobj:MockSensor(ctx.new('IT', 'Admin', '127.0.0.1'), 1, 'Inlet Temp', 'stop')
    rsp = mobj:GetThresholdSensorList_PACKED(ctx.new())
    assert(#rsp.SensorList == 3, 'actual: ' .. tostring(#rsp.SensorList))
    o = rsp.SensorList[1]
    assert(o.SensorName == 'Inlet Temp', 'actual: ' .. o.SensorName)
    assert(o.SensorReading == '35.000', 'actual: ' .. o.SensorReading)
end

local function test_mock_sensor_temperature_abnormal(mobj, bus)
    log:info('==== test mock temperature abnormal reading ...')
    -- 查询当前门限传感器
    local rsp = mobj:GetThresholdSensorList_PACKED(ctx.new())
    assert(#rsp.SensorList == 3, 'actual: ' .. tostring(#rsp.SensorList))
    local o = rsp.SensorList[1]
    assert(o.SensorName == 'Inlet Temp', 'actual: ' .. o.SensorName)
    assert(o.SensorReading == '35.000', 'actual: ' .. o.SensorReading)

    -- 模拟传感器值
    mobj:MockSensor(ctx.new('IT', 'Admin', '127.0.0.1'), 1, 'Inlet Temp', '48')
    rsp = mobj:GetThresholdSensorList_PACKED(ctx.new())
    assert(#rsp.SensorList == 3, 'actual: ' .. tostring(#rsp.SensorList))
    o = rsp.SensorList[1]
    assert(o.SensorName == 'Inlet Temp', 'actual: ' .. o.SensorName)
    assert(o.SensorReading == '48.000', 'actual: ' .. o.SensorReading)

    -- 模拟值产生了一般告警
    local obj_path = '/bmc/kepler/Systems/1/ThresholdSensors/ThresholdSensor_InletTemp_0101'
    local obj_intf = 'bmc.kepler.Systems.ThresholdSensorDisplay'
    local common = require 'sensor.sensor_common'
    local as = common.assert_state:unpack(string.pack('I2', 0))
    as.assert_noncritical_ugh = 1
    local status = string.unpack('I2', common.assert_state:pack(as))
    local obj = mdb.get_object(bus, obj_path, obj_intf)
    assert(obj.Health == 'Minor', 'actual: ' .. obj.Health)
    assert(obj.AssertStatus == status, 'actual: ' .. obj.AssertStatus .. ', expected: ' .. status)
end

local function test_mock_sensor_voltage_normal(mobj)
    log:info('==== test mock voltage normal reading ...')
    -- 查询当前门限传感器
    local rsp = mobj:GetThresholdSensorList_PACKED(ctx.new())
    assert(#rsp.SensorList == 3, 'actual: ' .. tostring(#rsp.SensorList))
    local o = rsp.SensorList[3]
    assert(o.SensorName == 'SYS 12V_2', 'actual: ' .. o.SensorName)
    assert(o.SensorReading == '11.940', 'actual: ' .. o.SensorReading)

    -- 模拟传感器值：电压值一般是小数，有整数和分数区分
    mobj:MockSensor(ctx.new('IT', 'Admin', '127.0.0.1'), 1, 'SYS 12V_2', '13')
    rsp = mobj:GetThresholdSensorList_PACKED(ctx.new())
    assert(#rsp.SensorList == 3, 'actual: ' .. tostring(#rsp.SensorList))
    o = rsp.SensorList[3]
    assert(o.SensorName == 'SYS 12V_2', 'actual: ' .. o.SensorName)
    assert(o.SensorReading == '13.020', 'actual: ' .. o.SensorReading)

    -- 恢复单个模拟
    mobj:MockSensor(ctx.new('IT', 'Admin', '127.0.0.1'), 1, 'SYS 12V_2', 'stop')
    rsp = mobj:GetThresholdSensorList_PACKED(ctx.new())
    assert(#rsp.SensorList == 3, 'actual: ' .. tostring(#rsp.SensorList))
    o = rsp.SensorList[3]
    assert(o.SensorName == 'SYS 12V_2', 'actual: ' .. o.SensorName)
    assert(o.SensorReading == '11.940', 'actual: ' .. o.SensorReading)
end

local function test_mock_sensor_voltage_abnormal(mobj, bus)
    log:info('==== test mock voltage abnormal reading ...')
    -- 查询当前门限传感器
    local rsp = mobj:GetThresholdSensorList_PACKED(ctx.new())
    assert(#rsp.SensorList == 3, 'actual: ' .. tostring(#rsp.SensorList))
    local o = rsp.SensorList[3]
    assert(o.SensorName == 'SYS 12V_2', 'actual: ' .. o.SensorName)
    assert(o.SensorReading == '11.940', 'actual: ' .. o.SensorReading)

    -- 模拟传感器值
    mobj:MockSensor(ctx.new('IT', 'Admin', '127.0.0.1'), 1, 'SYS 12V_2', '14')
    rsp = mobj:GetThresholdSensorList_PACKED(ctx.new())
    assert(#rsp.SensorList == 3, 'actual: ' .. tostring(#rsp.SensorList))
    o = rsp.SensorList[3]
    assert(o.SensorName == 'SYS 12V_2', 'actual: ' .. o.SensorName)
    assert(o.SensorReading == '13.980', 'actual: ' .. o.SensorReading)

    -- 模拟值产生了一般告警
    local obj_path = '/bmc/kepler/Systems/1/ThresholdSensors/ThresholdSensor_12V0VCC2_0101'
    local obj_intf = 'bmc.kepler.Systems.ThresholdSensorDisplay'
    local common = require 'sensor.sensor_common'
    local as = common.assert_state:unpack(string.pack('I2', 0))
    as.assert_critical_ugh = 1
    as.assert_nonrecoverable_ugh = 1
    local status = string.unpack('I2', common.assert_state:pack(as))
    local obj = mdb.get_object(bus, obj_path, obj_intf)
    assert(obj.Health == 'Critical', 'actual: ' .. obj.Health)
    assert(obj.AssertStatus == status, 'actual: ' .. obj.AssertStatus .. ', expected: ' .. status)
end

local function test_mock_sensor_resume_all(mobj, bus)
    log:info('==== test mock stop all sensors ...')
    -- 取消传感器的全部模拟
    mobj:MockSensor(ctx.new('IT', 'Admin', '127.0.0.1'), 0, '', '')
    local rsp = mobj:GetThresholdSensorList_PACKED(ctx.new())
    assert(#rsp.SensorList == 3, 'actual: ' .. tostring(#rsp.SensorList))

    -- 查看温度传感器的值和状态
    local o = rsp.SensorList[1]
    assert(o.SensorName == 'Inlet Temp', 'actual: ' .. o.SensorName)
    assert(o.SensorReading == '35.000', 'actual: ' .. o.SensorReading)
    local obj_path = '/bmc/kepler/Systems/1/ThresholdSensors/ThresholdSensor_InletTemp_0101'
    local obj_intf = 'bmc.kepler.Systems.ThresholdSensorDisplay'
    local obj = mdb.get_object(bus, obj_path, obj_intf)
    assert(obj.Health == 'OK', 'actual: ' .. obj.Health)

    -- 查看电压传感器的值和状态
    o = rsp.SensorList[3]
    assert(o.SensorName == 'SYS 12V_2', 'actual: ' .. o.SensorName)
    assert(o.SensorReading == '11.940', 'actual: ' .. o.SensorReading)
    obj_path = '/bmc/kepler/Systems/1/ThresholdSensors/ThresholdSensor_12V0VCC2_0101'
    obj_intf = 'bmc.kepler.Systems.ThresholdSensorDisplay'
    obj = mdb.get_object(bus, obj_path, obj_intf)
    assert(obj.Health == 'OK', 'actual: ' .. obj.Health)
end

function sensor_interface.test_mock_threshold_sensor(bus)
    log:info('== test mock threshold sensor start ...')
    local mobj = mdb.get_object(bus, SENSORS_PATH, SENSORS_INTF)

    -- 模拟温度传感器正常值
    test_mock_sensor_temperature_normal(mobj)

    -- 模拟温度传感器告警值
    test_mock_sensor_temperature_abnormal(mobj, bus)

    -- 模拟电压传感器正常值
    test_mock_sensor_voltage_normal(mobj)

    -- 模拟电压传感器高净值
    test_mock_sensor_voltage_abnormal(mobj, bus)

    -- 全部恢复模拟
    test_mock_sensor_resume_all(mobj, bus)
end

function sensor_interface.test_mock_discrete_sensor(bus)
    log:info('== test mock discrete sensor start ...')
    local mobj = mdb.get_object(bus, SENSORS_PATH, SENSORS_INTF)
    local obj_path = '/bmc/kepler/Systems/1/DiscreteSensors/DiscreteSensor_BootError_00'
    local obj_intf = 'bmc.kepler.Systems.DiscreteSensorDisplay'

    -- 1. 测试产生NO_BOOT_DEV故障(0)
    skynet.call('sensor', 'lua', 'boot_error', 0)
    skynet.sleep(30)
    -- 资源树查看传感器，AssertStatus 预期为 0x8001
    local obj = mdb.get_object(bus, obj_path, obj_intf)
    assert(obj.AssertStatus == 0x8001, 'actual: ' .. obj.AssertStatus)

    -- 2. 开始模拟，设置传感器状态为 0x8004，AssertStatus 预期为 0x8004
    mobj:MockSensor(ctx.new('IT', 'Admin', '127.0.0.1'), 1, 'Boot Error', '0x8004')
    obj = mdb.get_object(bus, obj_path, obj_intf)
    assert(obj.AssertStatus == 0x8004, 'actual: ' .. obj.AssertStatus)

    -- 3. 测试产生NO_BOOT_DISK故障(1)
    skynet.call('sensor', 'lua', 'boot_error', 1)
    skynet.sleep(30)
    -- 资源树查看传感器，由于仍处于模拟状态，AssertStatus 预期仍为 0x8004
    obj = mdb.get_object(bus, obj_path, obj_intf)
    assert(obj.AssertStatus == 0x8004, 'actual: ' .. obj.AssertStatus)

    -- 4. 取消模拟，由于产生NO_BOOT_DISK故障，AssertStatus 预期为 0x8002
    mobj:MockSensor(ctx.new('IT', 'Admin', '127.0.0.1'), 1, 'Boot Error', 'stop')

    obj = mdb.get_object(bus, obj_path, obj_intf)
    assert(obj.AssertStatus == 0x8002, 'actual: ' .. obj.AssertStatus)

    -- 5. 恢复测试
    skynet.call('sensor', 'lua', 'boot_error',  4294967295)
    skynet.sleep(30)
    -- 资源树查看传感器，AssertStatus 预期恢复为 0x8000
    obj = mdb.get_object(bus, obj_path, obj_intf)
    assert(obj.AssertStatus == 0x8000, 'actual: ' .. obj.AssertStatus)
end

function sensor_interface.test_on_dump(bus)
    log:info('== test dump sensor start ...')
    -- 回调函数不支持代理对象访问，这地方直接使用busctl访问方式进行测试
    local path = '/bmc/kepler/sensor/MicroComponent'
    local intf = 'bmc.kepler.MicroComponent.Debug'
    local dump = string.format('%s/%s/dump', os.getenv('PROJECT_DIR'), skynet.getenv('TEST_DATA_DIR'))
    local c = ctx.new('IT', 'Admin', '127.0.0.1')
    -- 无效路径测试
    bus:call(sensor_service, path, intf, 'Dump', 'a{ss}s', c, '/tmp/dump')
    local f = io.open('/tmp/dump/sensor_info.txt', 'r')
    -- 有效路径
    assert(not f)
    bus:call(sensor_service, path, intf, 'Dump', 'a{ss}s', c, dump)
    f = io.open(dump .. '/sensor_info.txt', 'r')
    assert(f)
    f:close()
    f = io.open(dump .. '/sdr_info.txt', 'r')
    assert(f)
    f:close()
    f = io.open(dump .. '/sensor.db', 'r')
    assert(f)
    f:close()
end

local function do_import_and_export(bus, data, type)
    -- 当前回调函数不支持代理对象访问，这地方直接使用busctl访问方式进行测试
    local path = '/bmc/kepler/sensor/MicroComponent'
    local intf = 'bmc.kepler.MicroComponent.ConfigManage'
    local c = ctx.new('IT', 'Admin', '127.0.0.1')

    local ret = pcall(bus.call, bus, sensor_service, path, intf, 'Import', 'a{ss}ss', c, data, type)
    assert(ret)

    skynet.sleep(10)
    ret = bus:call(sensor_service, path, intf, 'Export', 'a{ss}s', c, type)
    assert(ret)
    return json.decode(ret).ConfigData
end

function sensor_interface.test_on_import_and_export(bus)
    log:info('== test sensor import and export config start ...')
    -- 验证业务配置项导入
    local data = json.encode({
        ConfigData = {
            Sel = {QuerySelMaxNum = {Value = 1000, Import = true},
                   SelRecordMode = {Value = 'RotateAfterFull', Import = true}},
            SdrDev = {DynamicSensorNumBase = {Value = 10, Import = true}},
            Pef = {PefEnabled = {Value = false, Import = true}}}
    })
    local type = 'configuration'
    data = do_import_and_export(bus, data, type)
    assert(data.Sel)
    assert(data.Sel.QuerySelMaxNum == 1000, 'actual: ' .. data.Sel.QuerySelMaxNum)
    assert(data.Sel.SelRecordMode == 'RotateAfterFull', 'actual: ' .. data.Sel.QuerySelMaxNum)
    assert(data.SdrDev)
    assert(data.SdrDev.DynamicSensorNumBase == 10, 'actual: ' .. data.SdrDev.DynamicSensorNumBase)
    assert(not data.Pef.PefEnabled)

    -- 补充验证Sel定制项仅定制其中一项的场景
    data = json.encode({
        ConfigData = {
            Sel = {QuerySelMaxNum = {Value = 1000, Import = true}},
            SdrDev = {DynamicSensorNumBase = {Value = 10, Import = true}},
            Pef = {PefEnabled = {Value = false, Import = true}}}
    })
    type = 'configuration'
    data = do_import_and_export(bus, data, type)

    -- 验证装备定制化配置导入
    data = json.encode({
        ConfigData = {
            CustomSettings = {
                BMCSet_QuerySELMaxNumValue = {Value = 2000, Import = true},
                BMCSet_SEL_Mode = {Value = 'ClearAfterFull', Import = true},
                BMCSet_CustomDynamicSensorNumBase = {Value = 1, Import = true},
                BMCSet_PEF_Enable = {Value = 'on', Import = true}
            }
        }
    })
    type = 'custom'
    data = do_import_and_export(bus, data, type)
    local custom_settings = data.CustomSettings
    assert(custom_settings)
    assert(custom_settings.BMCSet_QuerySELMaxNumValue == 2000,
        'actual: ' .. custom_settings.BMCSet_QuerySELMaxNumValue)
    assert(custom_settings.BMCSet_SEL_Mode == 'ClearAfterFull',
        'actual: ' .. custom_settings.BMCSet_QuerySELMaxNumValue)
    assert(custom_settings.BMCSet_CustomDynamicSensorNumBase == 1,
        'actual: ' .. custom_settings.BMCSet_CustomDynamicSensorNumBase)
    assert(custom_settings.BMCSet_PEF_Enable == 'on', 'actual: ' .. custom_settings.BMCSet_PEF_Enable)
end

function sensor_interface.test_management_health(bus)
    log:info('== test management health start ...')

    -- 测试1：测试传感器状态异常（传感器编号为1）
    skynet.call('sensor', 'lua', 'management_health', 1, 1, 1)
    skynet.sleep(30)

    -- 资源树判断传感器的Health，预期为1
    local path = '/bmc/kepler/Systems/1/DiscreteSensors/DiscreteSensor_MngmntHealth_0101'
    local intf = 'bmc.kepler.Systems.DiscreteSensorDisplay'
    local mobj = mdb.get_object(bus, path, intf)
    assert(mobj.Health == 'Minor', 'actual: ' .. mobj.Health)

    -- 测试2：测试传感器状态正常（传感器编号为1）
    skynet.call('sensor', 'lua', 'management_health', 1, 1, 0)
    skynet.sleep(30)

    -- 资源树判断传感器的Health，预期为1
    mobj = mdb.get_object(bus, path, intf)
    assert(mobj.Health == 'OK', 'actual: ' .. mobj.Health)

    -- 数据库生成一条最新的SEL
    local db = open_local_db(skynet.getenv('TEST_DATA_DIR') .. '/sensor.db', nil, 'poweroff')
    local sel = db:select(db.IpmiSelList):order_by(db.IpmiSelList.RecordId, true):first().__datas
    local dir = sel.SelEventType >> 7
    assert(sel.SensorType == 0x28, 'actual: ' .. sel.SensorType)
    assert(dir == 0x01, 'actual dir: ' .. dir .. ', actual type: ' .. sel.SelEventType)
    assert(sel.SelData2 == 0x01, 'actual: ' .. sel.SelData2)
    assert(sel.SelData3 == 0x01, 'actual: ' .. sel.SelData3)
    db.db:close()
end

function sensor_interface.test_connector_out(bus)
    log:info('== test connector out start ...')

    -- 获取connector
    local ret, connector = pcall(mdb.get_object, bus, '/bmc/kepler/Connector/Connector_Sensor_0_1_01',
        'bmc.kepler.Connector')
    assert(ret)
    assert(connector)

    -- 模拟硬件拔出，不在位
    connector.Presence = 0
    skynet.sleep(200)  -- 等待对象卸载

    local mobj = mdb.get_object(bus, SENSORS_PATH, SENSORS_INTF)
    local rsp = mobj:GetSensorList_PACKED(ctx.new())
    assert(#rsp.SensorList == 1, 'actual: ' .. tostring(#rsp.SensorList))

    -- 模拟硬件插入，恢复在位
    connector.Presence = 1
    skynet.sleep(200)  -- 等待对象注册

    rsp = mobj:GetSensorList_PACKED(ctx.new())
    assert(#rsp.SensorList == 6, 'actual: ' .. tostring(#rsp.SensorList))
end

function sensor_interface.test_dump_sdr(bus)
    log:info('== test dump sdr start ...')
    local mobj = mdb.get_object(bus, SENSORS_PATH, SENSORS_INTF)
    for _, v in ipairs({'LAN', 'BT'}) do
        local rsp = mobj:DumpSDR_PACKED(ctx.new(), v)
        assert(rsp)

        -- 校验本地文件是否生成SDR
        local ok, f = pcall(io.open, '/dev/shm/sensor/ipmi_sdr.' .. v:lower(), 'rb')
        assert(ok)
        assert(f)

        -- 校验SDR缓存文件是否是可解析格式
        local data = f:read('a')
        f:close()
        assert(data)
        local reservation_id, count, records = skynet.unpack(data)
        assert(reservation_id >= 0, 'actual: ' .. reservation_id)
        assert(count > 0, 'actual: ' .. count)
        assert(type(records) == 'table' and next(records))

        -- 清理环境
        utils.remove_file('/dev/shm/sensor/ipmi_sdr.' .. v:lower())
    end
    log:info('== test dump sdr completed')
end

function sensor_interface.test_entry(bus)
    log:info('================ test sensor rpc interface start ================')

    sensor_interface.test_get_threshold_sensor_list(bus)
    sensor_interface.test_get_all_sensor_list(bus)
    sensor_interface.test_mock_threshold_sensor(bus)
    sensor_interface.test_mock_discrete_sensor(bus)
    sensor_interface.test_on_dump(bus)
    sensor_interface.test_on_import_and_export(bus)
    sensor_interface.test_management_health(bus)
    sensor_interface.test_connector_out(bus)
    sensor_interface.test_dump_sdr(bus)

    log:info('================ test sensor rpc interface complete ================')
end

return sensor_interface
