-- Copyright (c) 2026 Huawei Technologies Co., Ltd.
-- openUBMC is licensed under Mulan PSL v2.

local lu = require('luaunit')
local common_def = require('common_def')
local c_volume = require('volume.volume_object')
local volume_collection = require('volume.volume_collection')
local c_drive = require('drive.drive_object')
local c_storageconfig = require('storageconfig.storageconfig_object')
local sml = require('sml')
local signal = require('mc.signal')

TestVolumeObject = {}

function TestVolumeObject:test_is_abnormal_true()
    local obj = setmetatable({}, { __index = c_volume })
    obj.State = common_def.LD_STATE.OFFLINE
    lu.assertTrue(obj:is_abnormal())

    obj.State = common_def.LD_STATE.PARTIALLY_DEGRADED
    lu.assertTrue(obj:is_abnormal())

    obj.State = common_def.LD_STATE.DEGRADED
    lu.assertTrue(obj:is_abnormal())

    obj.State = common_def.LD_STATE.FAILED
    lu.assertTrue(obj:is_abnormal())

    obj.State = common_def.LD_STATE.UNSUPPORTED_ON_THIS_CONTROLLER
    lu.assertTrue(obj:is_abnormal())

    obj.State = common_def.LD_STATE.DRVIE_IMPROPERLY_CONNECTED
    lu.assertTrue(obj:is_abnormal())

    obj.State = common_def.LD_STATE.INTERIM_RECOVERY
    lu.assertTrue(obj:is_abnormal())

    obj.State = common_def.LD_STATE.WRONG_DRIVE_REPLACED
    lu.assertTrue(obj:is_abnormal())
end

function TestVolumeObject:test_is_abnormal_false()
    local obj = setmetatable({}, { __index = c_volume })
    obj.State = common_def.LD_STATE.OPTIMAL
    lu.assertFalse(obj:is_abnormal())

    obj.State = common_def.LD_STATE.EXPANDING
    lu.assertFalse(obj:is_abnormal())
end

function TestVolumeObject:test_check_abnormal_volume_exists()
    local orig_get_instance = c_storageconfig.get_instance
    local generated = nil
    c_storageconfig.get_instance = function()
        return {
            generate_abnormal_volume_exsits = function(_, val)
                generated = val
            end,
        }
    end

    local obj = setmetatable({}, { __index = c_volume })
    obj.collection = {
        fold = function(self, fn, acc)
            return true
        end,
    }
    obj:check_abnormal_volume_exists()
    lu.assertTrue(generated)

    obj.collection = {
        fold = function(self, fn, acc)
            return false
        end,
    }
    obj:check_abnormal_volume_exists()
    lu.assertFalse(generated)

    c_storageconfig.get_instance = orig_get_instance
end

function TestVolumeObject:test_check_drive_alarm_not_degraded()
    local obj = setmetatable({}, { __index = c_volume })
    obj.State = common_def.LD_STATE.OPTIMAL
    -- cur_state ~= OPTIMAL → return early
    obj:check_drive_alarm(common_def.LD_STATE.DEGRADED)
    lu.assertTrue(true)
end

function TestVolumeObject:test_check_drive_alarm_degraded_to_optimal()
    local orig_fold = c_drive.collection
    local fold_called = false
    c_drive.collection = {
        fold = function(self, fn, acc)
            fold_called = true
            fn(acc, {
                RefVolumeList = { [1] = 1 },
                in_degraded_array = true,
                generate_in_failed_array = function() end,
            }, 1)
            return acc
        end,
    }

    local obj = setmetatable({}, { __index = c_volume })
    obj.State = common_def.LD_STATE.DEGRADED
    obj.Id = 1
    obj:check_drive_alarm(common_def.LD_STATE.OPTIMAL)
    lu.assertTrue(fold_called)

    c_drive.collection = orig_fold
end

function TestVolumeObject:test_update_volume_cache_info()
    local orig_get_sscd = sml.get_ld_sscd_caching_enable
    local orig_get_assoc = sml.get_sscd_associated_ld_list
    local orig_get_ld_assoc = sml.get_ld_associated_sscd_list

    sml.get_ld_sscd_caching_enable = function()
        return 1
    end
    sml.get_sscd_associated_ld_list = function()
        return { 1, 2 }
    end
    sml.get_ld_associated_sscd_list = function()
        return { 3 }
    end

    local obj = setmetatable({}, { __index = c_volume })
    obj.RefController = 0
    obj.VolumeId = 1
    obj.SSDCachecadeVolume = 1
    obj.SSDCachingEnable = 1

    local volume_info = {
        is_sscd = 1,
        default_cache_policy = 0,
        current_cache_policy = 1,
    }
    obj:update_volume_cache_info(volume_info)
    lu.assertEquals(obj.SSDCachingEnable, 1)
    lu.assertEquals(obj.AssociatedVolumes, { 1, 2 })
    lu.assertEquals(obj.AssociatedCacheCadeVolume, { 3 })
    lu.assertEquals(obj.DefaultCachePolicy, 'CachedIO')
    lu.assertEquals(obj.CurrentCachePolicy, 'DirectIO')

    sml.get_ld_sscd_caching_enable = orig_get_sscd
    sml.get_sscd_associated_ld_list = orig_get_assoc
    sml.get_ld_associated_sscd_list = orig_get_ld_assoc
end

TestVolumeCollection = {}

function TestVolumeCollection:test_dump_info_empty()
    local fp = {
        buf = {},
        write = function(self, s)
            table.insert(self.buf, s)
        end,
    }
    local orig_list = volume_collection.volume_list
    volume_collection.volume_list = {}
    volume_collection:dump_info(fp, 0)
    lu.assertStrContains(table.concat(fp.buf), 'Not found logical drive')
    volume_collection.volume_list = orig_list
end

function TestVolumeCollection:test_dump_info_not_found()
    local fp = {
        buf = {},
        write = function(self, s)
            table.insert(self.buf, s)
        end,
    }

    -- volume_collection is a singleton; set volume_list to empty
    local orig_list = volume_collection.volume_list
    volume_collection.volume_list = {}
    volume_collection:dump_info(fp, 0)
    lu.assertStrContains(table.concat(fp.buf), 'Not found logical drive')
    volume_collection.volume_list = orig_list
end

function TestVolumeCollection:test_dump_info_no_match()
    local fp = {
        buf = {},
        write = function(self, s)
            table.insert(self.buf, s)
        end,
    }

    local orig_list = volume_collection.volume_list
    local orig_fold = c_volume.collection

    volume_collection.volume_list = { ['1:1'] = { Id = 1, RefController = 0 } }
    c_volume.collection = {
        fold = function(self, fn, acc)
            fn(acc, { RefController = 99, dump_info = function() end }, '99:1')
            return acc
        end,
    }

    volume_collection:dump_info(fp, 0)
    lu.assertStrContains(table.concat(fp.buf), 'No logical drive in this controller')

    volume_collection.volume_list = orig_list
    c_volume.collection = orig_fold
end

-- Covers volume_collection.lua:37-38: CreateVolume callback sets ObjectName
function TestVolumeCollection:test_volume_objectname()
    local captured_objectname
    local mock_storage_app_service = {
        CreateVolume = function(self, system_id, ctrl_id, id, cb)
            local mock_obj = {}
            if cb then
                cb(mock_obj)
                captured_objectname = mock_obj.ObjectName
            end
            return mock_obj
        end,
        mc_obj_manager = { mc = { add_object = function() end } },
    }

    -- Save original state
    local orig = {
        sas = volume_collection.storage_app_service,
        oav = volume_collection.on_add_volume,
        odv = volume_collection.on_del_volume,
        ocfaa = volume_collection.on_clear_drives_failed_array_alarm,
        om = volume_collection.object_manager,
        iudl = volume_collection.init_update_drive_list,
        iuabv = volume_collection.init_update_array_by_volume,
        iusdl = volume_collection.init_update_spare_drive_list,
        suvit = volume_collection.start_update_volume_info_task,
        vl = volume_collection.volume_list,
    }

    -- Set up mocks
    volume_collection.storage_app_service = mock_storage_app_service
    volume_collection.on_add_volume = signal.new()
    volume_collection.on_del_volume = signal.new()
    volume_collection.on_clear_drives_failed_array_alarm = signal.new()
    volume_collection.object_manager = mock_storage_app_service.mc_obj_manager
    volume_collection.init_update_drive_list = function() end
    volume_collection.init_update_array_by_volume = function() end
    volume_collection.init_update_spare_drive_list = function() end
    volume_collection.start_update_volume_info_task = function() end
    volume_collection.volume_list = {}

    -- Register the callback
    volume_collection:init()

    -- Trigger the callback
    local volume_obj = {
        path = '/test/path',
        volume_id = 1,
        controller_id = 0,
        is_device_tree = false,
    }
    volume_collection.on_add_volume:emit(volume_obj)

    -- Verify
    lu.assertNotNil(captured_objectname)
    lu.assertEquals(captured_objectname, 'Volume_0_1')

    -- Restore
    volume_collection.storage_app_service = orig.sas
    volume_collection.on_add_volume = orig.oav
    volume_collection.on_del_volume = orig.odv
    volume_collection.on_clear_drives_failed_array_alarm = orig.ocfaa
    volume_collection.object_manager = orig.om
    volume_collection.init_update_drive_list = orig.iudl
    volume_collection.init_update_array_by_volume = orig.iuabv
    volume_collection.init_update_spare_drive_list = orig.iusdl
    volume_collection.start_update_volume_info_task = orig.suvit
    volume_collection.volume_list = orig.vl
end

-- Covers volume_collection.lua:39-40: CreateVolume callback sets Id/RefControllerId
function TestVolumeCollection:test_volume_object_id_and_refcontroller()
    local captured_id
    local captured_refctrl
    local captured_objectname
    local mock_storage_app_service = {
        CreateVolume = function(self, system_id, ctrl_id, id, cb)
            local mock_obj = {}
            if cb then
                cb(mock_obj)
                captured_id = mock_obj.Id
                captured_refctrl = mock_obj.RefControllerId
                captured_objectname = mock_obj.ObjectName
            end
            return mock_obj
        end,
        mc_obj_manager = { mc = { add_object = function() end } },
    }

    -- Save original state
    local orig = {
        sas = volume_collection.storage_app_service,
        oav = volume_collection.on_add_volume,
        odv = volume_collection.on_del_volume,
        ocfaa = volume_collection.on_clear_drives_failed_array_alarm,
        om = volume_collection.object_manager,
        iudl = volume_collection.init_update_drive_list,
        iuabv = volume_collection.init_update_array_by_volume,
        iusdl = volume_collection.init_update_spare_drive_list,
        suvit = volume_collection.start_update_volume_info_task,
        vl = volume_collection.volume_list,
    }

    -- Set up mocks
    volume_collection.storage_app_service = mock_storage_app_service
    volume_collection.on_add_volume = signal.new()
    volume_collection.on_del_volume = signal.new()
    volume_collection.on_clear_drives_failed_array_alarm = signal.new()
    volume_collection.object_manager = mock_storage_app_service.mc_obj_manager
    volume_collection.init_update_drive_list = function() end
    volume_collection.init_update_array_by_volume = function() end
    volume_collection.init_update_spare_drive_list = function() end
    volume_collection.start_update_volume_info_task = function() end
    volume_collection.volume_list = {}

    -- Register the callback
    volume_collection:init()

    -- Trigger the callback
    local volume_obj = {
        path = '/test/path',
        volume_id = 3,
        controller_id = 0,
        is_device_tree = false,
    }
    volume_collection.on_add_volume:emit(volume_obj)

    -- Verify: callback sets Id/RefControllerId on the mdb object
    lu.assertEquals(captured_id, 3)
    lu.assertEquals(captured_refctrl, 0)
    lu.assertEquals(captured_objectname, 'Volume_0_3')
    -- volume_list records key 'controller_id:volume_id'
    lu.assertNotNil(volume_collection.volume_list['0:3'])

    -- Restore
    volume_collection.storage_app_service = orig.sas
    volume_collection.on_add_volume = orig.oav
    volume_collection.on_del_volume = orig.odv
    volume_collection.on_clear_drives_failed_array_alarm = orig.ocfaa
    volume_collection.object_manager = orig.om
    volume_collection.init_update_drive_list = orig.iudl
    volume_collection.init_update_array_by_volume = orig.iuabv
    volume_collection.init_update_spare_drive_list = orig.iusdl
    volume_collection.start_update_volume_info_task = orig.suvit
    volume_collection.volume_list = orig.vl
end