-- Copyright (c) 2025 Huawei Technologies Co., Ltd.
-- openUBMC is licensed under Mulan PSL v2.
-- 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_network_adapter = require 'device.class.network_adapter'
local c_object_manage = require 'mc.orm.object_manage'
local comm_fun = require 'device.class.nic_mgmt.comm_fun'
local comm_defs = require 'device.class.nic_mgmt.comm_defs'
local log = require 'mc.logging'

TEST_network_adapter_ready_to_remove = {}

-- 通用 stub/restore 工具，参考其它单测风格
local origs = {}

local function save_orig(tbl, key)
    origs[tbl] = origs[tbl] or {}
    if not origs[tbl][key] then
        origs[tbl][key] = tbl[key]
    end
end

local function stub(tbl, key, value)
    save_orig(tbl, key)
    rawset(tbl, key, value)
end

local function restore_all()
    for tbl, keys in pairs(origs) do
        for k, v in pairs(keys) do
            tbl[k] = v
        end
    end
    origs = {}
end

-- 通过 upvalue 获取待测的局部函数 set_ready_to_remove_value
local function get_set_ready_to_remove_fn()
    local info = debug.getinfo(c_network_adapter.set_prop_on_removing, 'u')
    for i = 1, info.nups do
        local name, val = debug.getupvalue(c_network_adapter.set_prop_on_removing, i)
        if name == 'set_ready_to_remove_value' then
            return val
        end
    end
    error('set_ready_to_remove_value not found as upvalue')
end

function TEST_network_adapter_ready_to_remove:setUp()
    -- log 打桩，避免真实打日志
    stub(log, 'notice', function() end)
    stub(log, 'error', function() end)
end

function TEST_network_adapter_ready_to_remove:tearDown()
    restore_all()
end

-- 场景 1：非设备树对象，直接设置 ORM 对象属性 ReadyToRemoveValue
function TEST_network_adapter_ready_to_remove:test_set_ready_to_remove_on_orm_object_only()
    local fn = get_set_ready_to_remove_fn()
    local obj = {
        CreatedByDeviceObject = false,
        NodeId = 'node-orm'
    }

    fn(obj)

    -- READY_TO_REMOVE_VALUE 常量值为 1
    lu.assertEquals(obj.ReadyToRemoveValue, 1)
end

-- 场景 2：设备树对象但 device_path 为空，函数应直接返回
function TEST_network_adapter_ready_to_remove:test_set_ready_to_remove_device_path_missing()
    local fn = get_set_ready_to_remove_fn()

    -- mock c_object_manage 与 comm_fun，确保不会被调用到
    local get_instance_called = false
    stub(c_object_manage, 'get_instance', function()
        get_instance_called = true
        return { bus = {} }
    end)
    local get_device_obj_called = false
    stub(comm_fun, 'get_device_obj_by_path_interface', function(...)
        get_device_obj_called = true
        return {}
    end)

    local obj = {
        CreatedByDeviceObject = true,
        NodeId = 'node-no-path',
        device_path = ''
    }

    fn(obj)

    lu.assertFalse(get_instance_called)
    lu.assertFalse(get_device_obj_called)
end

-- 场景 3：设备树对象，device_path 正常，但获取 device_obj 失败
function TEST_network_adapter_ready_to_remove:test_set_ready_to_remove_device_obj_missing()
    local fn = get_set_ready_to_remove_fn()

    stub(c_object_manage, 'get_instance', function()
        return { bus = {} }
    end)

    local called_with = {}
    stub(comm_fun, 'get_device_obj_by_path_interface', function(bus, path, iface)
        table.insert(called_with, { bus = bus, path = path, iface = iface })
        return nil
    end)

    local obj = {
        CreatedByDeviceObject = true,
        NodeId = 'node-no-device',
        device_path = '/dev/card0'
    }

    fn(obj)

    lu.assertEquals(#called_with, 1)
    lu.assertEquals(called_with[1].path, '/dev/card0')
    lu.assertEquals(called_with[1].iface, comm_defs.PCIE_DEVICE_HOT_SWAP_INTERFACE)
end

-- 场景 4：pcall 内设置 ReadyToRemove 抛异常，走错误分支
function TEST_network_adapter_ready_to_remove:test_set_ready_to_remove_pcall_failed()
    local fn = get_set_ready_to_remove_fn()

    stub(c_object_manage, 'get_instance', function()
        return { bus = {} }
    end)

    local device_obj = setmetatable({}, {
        __newindex = function()
            error('inject error in __newindex')
        end
    })

    stub(comm_fun, 'get_device_obj_by_path_interface', function()
        return device_obj
    end)

    local obj = {
        CreatedByDeviceObject = true,
        NodeId = 'node-pcall-fail',
        device_path = '/dev/card1'
    }

    fn(obj)

    -- 由于 __newindex 抛异常，pcall 应返回 false，device_obj 上也不会成功设置字段
    lu.assertNil(device_obj.ReadyToRemove)
end

-- 场景 5：正常设置 ReadyToRemove 到设备树对象
function TEST_network_adapter_ready_to_remove:test_set_ready_to_remove_success()
    local fn = get_set_ready_to_remove_fn()

    local fake_bus = {}
    stub(c_object_manage, 'get_instance', function()
        return { bus = fake_bus }
    end)

    local device_obj = {}
    stub(comm_fun, 'get_device_obj_by_path_interface', function(bus, path, iface)
        lu.assertIs(bus, fake_bus)
        lu.assertEquals(path, '/dev/card2')
        lu.assertEquals(iface, comm_defs.PCIE_DEVICE_HOT_SWAP_INTERFACE)
        return device_obj
    end)

    local obj = {
        CreatedByDeviceObject = true,
        NodeId = 'node-success',
        device_path = '/dev/card2'
    }

    fn(obj)

    lu.assertEquals(device_obj.ReadyToRemove, 1)
end

