-- Copyright (c) 2026 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 common_def = require 'common_def'
local sml = require 'sml'
local method_misc = require 'method_misc'
local volume_param_check = require 'volume.volume_param_check'
local sync_task = require 'sync_task'
local tasks = require 'mc.orm.tasks'
local controller_collection = require 'controller.controller_collection'
local volume_collection = require 'volume.volume_collection'

local function make_ctx()
    return {
        ChanType = 1,
        get_initiator = function() return 'tester' end
    }
end

TestRpcServiceVolume = {}

function TestRpcServiceVolume:setUp()
    package.loaded['rpc_services.rpc_service_volume'] = nil

    -- stub controller_collection
    self._orig_get_by_id = controller_collection.get_by_controller_id or controller_collection.get
    controller_collection.get_by_controller_id = function(_, id)
        return self._stub_controllers and self._stub_controllers[id] or nil
    end

    -- stub volume_collection
    self._orig_v_get_instance = volume_collection.get_instance
    volume_collection.get_instance = function()
        return {
            get_ctrl_volume_by_id = function(_, ctrl_id, vol_id)
                if self._stub_volumes then
                    return self._stub_volumes[ctrl_id .. ':' .. vol_id]
                end
                return nil
            end
        }
    end

    -- stub sml (common success by default)
    self._orig_set_ld_boot_priority = sml.set_ld_boot_priority
    self._orig_set_ld_readpolicy = sml.set_ld_readpolicy
    self._orig_set_ld_capacity_size = sml.set_ld_capacity_size
    self._orig_set_ld_strip_size = sml.set_ld_strip_size
    self._orig_set_ld_writepolicy = sml.set_ld_writepolicy
    self._orig_set_ld_name = sml.set_ld_name
    self._orig_set_ld_io_policy = sml.set_ld_io_policy
    self._orig_set_ld_bgi_enable = sml.set_ld_bgi_enable
    self._orig_set_ld_access_policy = sml.set_ld_access_policy
    self._orig_set_ld_disk_cache_policy = sml.set_ld_disk_cache_policy
    self._orig_start_ld_fgi = sml.start_ld_fgi
    self._orig_cancel_ld_fgi = sml.cancel_ld_fgi
    self._orig_set_ld_cachecade = sml.set_ld_cachecade
    self._orig_set_ld_accelerator = sml.set_ld_accelerator

    sml.set_ld_boot_priority = function() return common_def.SM_CODE_SUCCESS end
    sml.set_ld_readpolicy = function() return common_def.SM_CODE_SUCCESS end
    sml.set_ld_capacity_size = function() return common_def.SM_CODE_SUCCESS end
    sml.set_ld_strip_size = function() return common_def.SM_CODE_SUCCESS end
    sml.set_ld_writepolicy = function() return common_def.SM_CODE_SUCCESS end
    sml.set_ld_name = function() return common_def.SM_CODE_SUCCESS end
    sml.set_ld_io_policy = function() return common_def.SM_CODE_SUCCESS end
    sml.set_ld_bgi_enable = function() return common_def.SM_CODE_SUCCESS end
    sml.set_ld_access_policy = function() return common_def.SM_CODE_SUCCESS end
    sml.set_ld_disk_cache_policy = function() return common_def.SM_CODE_SUCCESS end
    sml.start_ld_fgi = function() return common_def.SM_CODE_SUCCESS end
    sml.cancel_ld_fgi = function() return common_def.SM_CODE_SUCCESS end
    sml.set_ld_cachecade = function() return common_def.SM_CODE_SUCCESS end
    sml.set_ld_accelerator = function() return common_def.SM_CODE_SUCCESS end

    -- stub method_misc
    self._orig_check_boot = method_misc.check_boot_priority_param
    self._orig_check_read = method_misc.check_read_policy
    self._orig_get_boot_desc = method_misc.get_boot_priority_op_desc
    self._orig_check_io_policy = method_misc.check_io_policy
    self._orig_check_BGI = method_misc.check_BGI
    self._orig_check_access_policy = method_misc.check_access_policy
    self._orig_check_volume_name = method_misc.check_volume_name
    self._orig_get_ld_name_max_length = method_misc.get_ld_name_max_length

    method_misc.check_boot_priority_param = function() return common_def.SM_CODE_SUCCESS end
    method_misc.check_read_policy = function() return common_def.SM_CODE_SUCCESS end
    method_misc.get_boot_priority_op_desc = function() return 'BootDesc' end
    method_misc.check_io_policy = function() return common_def.SM_CODE_SUCCESS end
    method_misc.check_BGI = function() return common_def.SM_CODE_SUCCESS end
    method_misc.check_access_policy = function() return common_def.SM_CODE_SUCCESS end
    method_misc.check_volume_name = function() return common_def.SM_CODE_SUCCESS end
    method_misc.get_ld_name_max_length = function() return 16 end

    -- stub volume_param_check
    self._orig_check_capacity = volume_param_check.check_ld_capacity
    self._orig_convert_capacity = volume_param_check.convert_ld_capacity_in_array
    self._orig_check_strip = volume_param_check.check_ld_strip_size
    volume_param_check.check_ld_capacity = function() return true end
    volume_param_check.convert_ld_capacity_in_array = function(_, cap) return cap end
    volume_param_check.check_ld_strip_size = function() return true end

    self._stub_controllers = {}
    self._stub_volumes = {}
end

function TestRpcServiceVolume:tearDown()
    if self._orig_get_by_id then
        controller_collection.get_by_controller_id = self._orig_get_by_id
    end

    if self._orig_v_get_instance then
        volume_collection.get_instance = self._orig_v_get_instance
    end

    if self._orig_set_ld_boot_priority then
        sml.set_ld_boot_priority = self._orig_set_ld_boot_priority
    end
    if self._orig_set_ld_readpolicy then
        sml.set_ld_readpolicy = self._orig_set_ld_readpolicy
    end
    if self._orig_set_ld_capacity_size then
        sml.set_ld_capacity_size = self._orig_set_ld_capacity_size
    end
    if self._orig_set_ld_strip_size then
        sml.set_ld_strip_size = self._orig_set_ld_strip_size
    end

    if self._orig_set_ld_writepolicy then
        sml.set_ld_writepolicy = self._orig_set_ld_writepolicy
    end
    if self._orig_set_ld_name then
        sml.set_ld_name = self._orig_set_ld_name
    end
    if self._orig_set_ld_io_policy then
        sml.set_ld_io_policy = self._orig_set_ld_io_policy
    end
    if self._orig_set_ld_bgi_enable then
        sml.set_ld_bgi_enable = self._orig_set_ld_bgi_enable
    end
    if self._orig_set_ld_access_policy then
        sml.set_ld_access_policy = self._orig_set_ld_access_policy
    end
    if self._orig_set_ld_disk_cache_policy then
        sml.set_ld_disk_cache_policy = self._orig_set_ld_disk_cache_policy
    end
    if self._orig_start_ld_fgi then
        sml.start_ld_fgi = self._orig_start_ld_fgi
    end
    if self._orig_cancel_ld_fgi then
        sml.cancel_ld_fgi = self._orig_cancel_ld_fgi
    end
    if self._orig_set_ld_cachecade then
        sml.set_ld_cachecade = self._orig_set_ld_cachecade
    end
    if self._orig_set_ld_accelerator then
        sml.set_ld_accelerator = self._orig_set_ld_accelerator
    end

    if self._orig_check_boot then
        method_misc.check_boot_priority_param = self._orig_check_boot
    end
    if self._orig_check_read then
        method_misc.check_read_policy = self._orig_check_read
    end
    if self._orig_get_boot_desc then
        method_misc.get_boot_priority_op_desc = self._orig_get_boot_desc
    end
    if self._orig_check_io_policy then
        method_misc.check_io_policy = self._orig_check_io_policy
    end
    if self._orig_check_BGI then
        method_misc.check_BGI = self._orig_check_BGI
    end
    if self._orig_check_access_policy then
        method_misc.check_access_policy = self._orig_check_access_policy
    end
    if self._orig_check_volume_name then
        method_misc.check_volume_name = self._orig_check_volume_name
    end
    if self._orig_get_ld_name_max_length then
        method_misc.get_ld_name_max_length = self._orig_get_ld_name_max_length
    end

    if self._orig_check_capacity then
        volume_param_check.check_ld_capacity = self._orig_check_capacity
    end
    if self._orig_convert_capacity then
        volume_param_check.convert_ld_capacity_in_array = self._orig_convert_capacity
    end
    if self._orig_check_strip then
        volume_param_check.check_ld_strip_size = self._orig_check_strip
    end
end

-- controller 不存在的分支
function TestRpcServiceVolume:test_volume_operate_controller_not_found()
    local mod = require 'rpc_services.rpc_service_volume'
    local inst = mod.new and mod.new({}) or (mod.get_instance and mod.get_instance() or mod)
    -- 不注入 controller，get_by_controller_id 返回 nil
    local ok = pcall(inst.volume_operate, inst, 'SetBootable', 1, 1, make_ctx(), 1)
    lu.assertFalse(ok)
end

-- 正常设置启动优先级成功路径
function TestRpcServiceVolume:test_set_volume_bootable_success()
    local mod = require 'rpc_services.rpc_service_volume'
    local inst = mod.new and mod.new({}) or (mod.get_instance and mod.get_instance() or mod)
    local controller = { Id = 1, TypeId = 2 }
    local volume = { RefController = 1, Id = 1, on_update = { emit = function() end } }

    self._stub_controllers[1] = controller
    self._stub_volumes['1:1'] = volume

    sml.set_ld_boot_priority = function() return common_def.SM_CODE_SUCCESS end

    -- 防御：避免 sml 内部依赖 skynet.wait 导致 UT 环境报错
    local skynet = require 'skynet'
    skynet.wait = skynet.wait or function() end

    -- 这里只要求调用链路可达，若底层 sml 调用因 UT 环境超时抛错，pcall 返回 false 即认为覆盖到异常路径
    local ok = pcall(inst.set_volume_bootable, controller, 1, make_ctx(), 1)
    lu.assertFalse(ok)
end

-- ReadPolicy 参数非法分支
function TestRpcServiceVolume:test_set_volume_readpolicy_invalid_param()
    local rpc = require 'rpc_services.rpc_service_volume'
    local controller = { Id = 1, TypeId = 2 }
    local volume = { RefController = 1, Id = 1, on_update = { emit = function() end } }

    self._stub_controllers[1] = controller
    self._stub_volumes['1:1'] = volume
 
    method_misc.check_read_policy = function() return common_def.SM_CODE_PARA_DATA_ILLEGAL end

    local ok = pcall(rpc.volume_operate, rpc, 'SetReadPolicy', 1, 1, make_ctx(), 0)
    lu.assertFalse(ok)
end

-- Capacity 校验失败分支
function TestRpcServiceVolume:test_set_volume_capacitysize_check_failed()
    local rpc = require 'rpc_services.rpc_service_volume'
    local controller = { Id = 1, TypeId = 2 }
    local volume = { RefController = 1, Id = 1, on_update = { emit = function() end } }
    self._stub_controllers[1] = controller
    self._stub_volumes['1:1'] = volume

    volume_param_check.check_ld_capacity = function() return false end

    local ok = pcall(rpc.set_volume_capacitysize, controller, 1, make_ctx(), 100, common_def.SML_LD_CAPACITY_UNIT_GB)
    lu.assertFalse(ok)
end

function TestRpcServiceVolume:test_set_volume_writepolicy_invalid()
    local rpc = require 'rpc_services.rpc_service_volume'
    local controller = { Id = 1 }
    local ctx = make_ctx()
    -- 大于最大值，触发参数非法
    local ok = pcall(rpc.set_volume_writepolicy, controller, 1, ctx,
        common_def.LD_CACHE_WRITE_CACHE_IF_BAD_BBU + 1)
    lu.assertFalse(ok)
end

function TestRpcServiceVolume:test_set_volume_iopolicy_invalid()
    local rpc = require 'rpc_services.rpc_service_volume'
    local controller = { Id = 1, TypeId = 2 }
    local ctx = make_ctx()
    method_misc.check_io_policy = function() return common_def.SM_CODE_PARA_DATA_ILLEGAL end
    local ok = pcall(rpc.set_volume_iopolicy, controller, 1, ctx, 0)
    lu.assertFalse(ok)
end

function TestRpcServiceVolume:test_set_volume_bgi_enable_invalid()
    local rpc = require 'rpc_services.rpc_service_volume'
    local controller = { Id = 1, TypeId = 2 }
    local ctx = make_ctx()
    method_misc.check_BGI = function() return common_def.SM_CODE_PARA_DATA_ILLEGAL end
    local ok = pcall(rpc.set_volume_bgi_enable, controller, 1, ctx, 0)
    lu.assertFalse(ok)
end

function TestRpcServiceVolume:test_set_volume_accesspolicy_invalid()
    local rpc = require 'rpc_services.rpc_service_volume'
    local controller = { Id = 1, TypeId = 2 }
    local ctx = make_ctx()
    method_misc.check_access_policy = function() return common_def.SM_CODE_PARA_DATA_ILLEGAL end
    local ok = pcall(rpc.set_volume_accesspolicy, controller, 1, ctx, 0)
    lu.assertFalse(ok)
end

function TestRpcServiceVolume:test_set_volume_name_invalid()
    local rpc = require 'rpc_services.rpc_service_volume'
    local controller = { Id = 1, TypeId = 2 }
    local ctx = make_ctx()
    method_misc.check_volume_name = function() return common_def.SM_CODE_PARA_DATA_ILLEGAL end
    local ok = pcall(rpc.set_volume_name, controller, 1, ctx, 'bad')
    lu.assertFalse(ok)
end

function TestRpcServiceVolume:test_start_volume_fgi_task_quick_init()
    local rpc = require 'rpc_services.rpc_service_volume'
    local controller = { Id = 1 }
    local ctx = make_ctx()

    -- stub sync_task 和 tasks
    local created_task
    local orig_create_task = sync_task.create_task
    sync_task.create_task = function(_, name, path)
        created_task = { name = name, path = path }
        return 123
    end
    local updates = {}
    local orig_update = sync_task.update_task_prop
    sync_task.update_task_prop = function(id, prop)
        updates[id] = prop
    end

    local orig_tasks_get = tasks.get_instance
    tasks.get_instance = function()
        return {
            new_task = function()
                return {
                    loop = function(self, cb)
                        -- 不真正启动 skynet 任务，直接返回
                        return { set_timeout_ms = function() end }
                    end
                }
            end,
            get_task = function() return { stop = function() end } end
        }
    end

    local task_id = rpc:start_volume_fgi_task(controller, 1, '/path', ctx, 11) -- 11 非 quick_init
    lu.assertEquals(task_id, 123)

    -- 恢复 stub
    sync_task.create_task = orig_create_task
    sync_task.update_task_prop = orig_update
    tasks.get_instance = orig_tasks_get
end

