-- Copyright (c) 2025 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'

TestIndVrdManager = {}

function TestIndVrdManager:setUp()
    -- 清除已加载的模块
    package.loaded['independent_vrd.ind_vrd_manager'] = nil
    package.loaded['independent_vrd.ind_vrd_defs'] = nil

    -- Mock defs
    package.loaded['independent_vrd.ind_vrd_defs'] = {
        RET = {
            OK = 0,
            ERR = 0xFF
        }
    }

    -- Mock mdb
    self.mdb_calls = {}
    local test_self = self
    package.loaded['mc.mdb'] = {
        get_object = function(bus, path, interface)
            table.insert(test_self.mdb_calls, { func = 'get_object', path = path })
            return {
                Add = function(self, ctx, param, flag, a, b)
                    table.insert(test_self.mdb_calls, { func = 'Add', param = param })
                end
            }
        end,
        get_cached_object = function(bus, path, interface)
            table.insert(test_self.mdb_calls, { func = 'get_cached_object', path = path })
            return {
                Version = ''
            }
        end
    }

    -- Mock context
    package.loaded['mc.context'] = {
        new = function()
            return {}
        end
    }

    -- Mock common
    package.loaded['common'] = {
        skynet = {
            fork = function(callback)
                callback() -- Execute immediately for testing
            end,
            sleep = function(ms) end
        }
    }

    -- Mock fw_manager
    package.loaded['fw_manager'] = {
        get_device_name_by_position = function(self, board_type, position)
            return 'TestDevice'
        end,
        get_node_id_by_position = function(self, board_type, position)
            return 'TestNodeId'
        end,
        get_related_item_by_position = function(self, board_type, position)
            return 'TestRelatedItem'
        end
    }

    -- Create mock chip objects
    self.mock_chip1 = {
        obj = { 
            BoardType = 'TestBoard',
            get_property_source = function()
                local source = {
                    [1] = {
                        Path = '/bmc/kerpler/Accessor/Accessor_ValidVrd_0101'
                    }
                }
                return source
            end
        },
        position = 'pos1',
        get_version = function(self)
            return '1.2.3'
        end,
        upgrade = function(self, dir)
            return 0 -- OK
        end,
        valid_vrd = function(self)
            return 0 -- defs.RET.OK
        end
    }

    self.mock_chip2 = {
        obj = { 
            BoardType = 'TestBoard',
            get_property_source = function()
                local source = {
                    [1] = {
                        Path = '/bmc/kerpler/Accessor/Accessor_ValidVrd_0101'
                    }
                }
                return source
            end
        },
        position = 'pos2',
        get_version = function(self)
            return '4.5.6'
        end,
        upgrade = function(self, dir)
            return 0 -- OK
        end,
        valid_vrd = function(self)
            return 0 -- defs.RET.OK
        end
    }

    -- Load module
    local ind_vrd_manager = require 'independent_vrd.ind_vrd_manager'
    self.manager_class = ind_vrd_manager
end

-- Test: new creates manager object
function TestIndVrdManager:test_new_creates_object()
    local mgr = self.manager_class.new('test-uid-001', 'TestBoard', 'SW-123', 'pos1')

    lu.assertNotNil(mgr)
    lu.assertEquals('test-uid-001', mgr.UID)
    lu.assertEquals('TestBoard', mgr.BoardType)
    lu.assertEquals('SW-123', mgr.SoftwareId)
    lu.assertEquals('pos1', mgr.position)
    lu.assertNotNil(mgr.vrd_chips)
    lu.assertEquals(false, mgr.objects_complete)
end

-- Test: new with default software_id
function TestIndVrdManager:test_new_default_software_id()
    local mgr = self.manager_class.new('test-uid-002', 'TestBoard', nil, 'pos2')

    lu.assertEquals('', mgr.SoftwareId)
end

-- Test: add_chip success
function TestIndVrdManager:test_add_chip_success()
    local mgr = self.manager_class.new('test-uid-001', 'TestBoard', 'SW-123', 'pos1')

    local result = mgr:add_chip(1, self.mock_chip1)

    lu.assertEquals(true, result)
    lu.assertNotNil(mgr.vrd_chips[1])
    lu.assertEquals(self.mock_chip1, mgr.vrd_chips[1])
end

-- Test: add_chip with invalid vrd_id
function TestIndVrdManager:test_add_chip_invalid_vrd_id()
    local mgr = self.manager_class.new('test-uid-001', 'TestBoard', 'SW-123', 'pos1')

    local result = mgr:add_chip(nil, self.mock_chip1)

    lu.assertEquals(false, result)
end

-- Test: add_chip with invalid chip_obj
function TestIndVrdManager:test_add_chip_invalid_chip_obj()
    local mgr = self.manager_class.new('test-uid-001', 'TestBoard', 'SW-123', 'pos1')

    local result = mgr:add_chip(1, nil)

    lu.assertEquals(false, result)
end

-- Test: add_chip multiple chips
function TestIndVrdManager:test_add_chip_multiple()
    local mgr = self.manager_class.new('test-uid-001', 'TestBoard', 'SW-123', 'pos1')

    mgr:add_chip(1, self.mock_chip1)
    mgr:add_chip(2, self.mock_chip2)

    lu.assertNotNil(mgr.vrd_chips[1])
    lu.assertNotNil(mgr.vrd_chips[2])
end

-- Test: get_chip_count with no chips
function TestIndVrdManager:test_get_chip_count_zero()
    local mgr = self.manager_class.new('test-uid-001', 'TestBoard', 'SW-123', 'pos1')

    lu.assertEquals(0, mgr:get_chip_count())
end

-- Test: get_chip_count with chips
function TestIndVrdManager:test_get_chip_count_multiple()
    local mgr = self.manager_class.new('test-uid-001', 'TestBoard', 'SW-123', 'pos1')
    mgr:add_chip(1, self.mock_chip1)
    mgr:add_chip(2, self.mock_chip2)

    lu.assertEquals(2, mgr:get_chip_count())
end

-- Test: get_version with no chips
function TestIndVrdManager:test_get_version_no_chips()
    local mgr = self.manager_class.new('test-uid-001', 'TestBoard', 'SW-123', 'pos1')

    local version = mgr:get_all_version()

    lu.assertEquals('', version)
end

-- Test: get_version with single chip
function TestIndVrdManager:test_get_version_single_chip()
    local mgr = self.manager_class.new('test-uid-001', 'TestBoard', 'SW-123', 'pos1')
    mgr:add_chip(1, self.mock_chip1)

    local version = mgr:get_all_version()

    lu.assertEquals('1.2.3', version)
end

-- Test: get_version with multiple chips sorted
function TestIndVrdManager:test_get_version_multiple_chips()
    local mgr = self.manager_class.new('test-uid-001', 'TestBoard', 'SW-123', 'pos1')
    mgr:add_chip(2, self.mock_chip2)
    mgr:add_chip(1, self.mock_chip1)

    local version = mgr:get_all_version()

    lu.assertEquals('1.2.3.4.5.6', version)
end

-- Test: get_version with empty version chip
function TestIndVrdManager:test_get_version_with_empty_version()
    local mgr = self.manager_class.new('test-uid-001', 'TestBoard', 'SW-123', 'pos1')

    local empty_chip = {
        get_version = function(self)
            return ''
        end
    }

    mgr:add_chip(1, self.mock_chip1)
    mgr:add_chip(2, empty_chip)

    local version = mgr:get_all_version()

    lu.assertEquals('1.2.3.-', version)
end

-- Test: register_firmware_info success
function TestIndVrdManager:test_register_firmware_info_success()
    local mgr = self.manager_class.new('test-uid-001', 'TestBoard', 'SW-123', 'pos1')
    mgr:add_chip(1, self.mock_chip1)

    local mock_bus = {}
    mgr:register_firmware_info(mock_bus)

    -- Check mdb calls
    local found_add = false
    for _, call in ipairs(self.mdb_calls) do
        if call.func == 'Add' then
            found_add = true
            lu.assertNotNil(call.param)
            lu.assertEquals('VRD_TestBoard_pos1', call.param.Id)
            lu.assertEquals('1.2.3', call.param.Version)
            lu.assertEquals('SW-123', call.param.SoftwareId)
        end
    end
    lu.assertTrue(found_add)
end

-- Test: register_firmware_info already registered
function TestIndVrdManager:test_register_firmware_info_already_registered()
    local mgr = self.manager_class.new('test-uid-001', 'TestBoard', 'SW-123', 'pos1')
    mgr:add_chip(1, self.mock_chip1)

    local mock_bus = {}
    mgr:register_firmware_info(mock_bus)

    -- Should call Add (no early return in current implementation)
    local found_add = false
    for _, call in ipairs(self.mdb_calls) do
        if call.func == 'Add' then
            found_add = true
        end
    end
    lu.assertTrue(found_add)
end

-- Test: register_firmware_info with empty version
function TestIndVrdManager:test_register_firmware_info_empty_version()
    local mgr = self.manager_class.new('test-uid-001', 'TestBoard', 'SW-123', 'pos1')

    local mock_bus = {}
    mgr:register_firmware_info(mock_bus)

    -- Should call Add even with empty version
    local found_add = false
    for _, call in ipairs(self.mdb_calls) do
        if call.func == 'Add' then
            found_add = true
        end
    end
    lu.assertTrue(found_add)
end

-- Test: update_firmware_version
function TestIndVrdManager:test_update_firmware_version()
    local mgr = self.manager_class.new('test-uid-001', 'TestBoard', 'SW-123', 'pos1')
    mgr:add_chip(1, self.mock_chip1)

    local mock_bus = {}
    mgr:update_firmware_version(mock_bus)

    -- Check that get_cached_object was called
    local found_get = false
    for _, call in ipairs(self.mdb_calls) do
        if call.func == 'get_cached_object' then
            found_get = true
            lu.assertTrue(call.path:find('VRD_TestBoard_pos1') ~= nil)
        end
    end
    lu.assertTrue(found_get)
end

-- Test: upgrade with no chips
function TestIndVrdManager:test_upgrade_no_chips()
    local mgr = self.manager_class.new('test-uid-001', 'TestBoard', 'SW-123', 'pos1')

    local result = mgr:vrd_upgrade('/test/dir', nil)

    lu.assertEquals(0xFF, result)
end

-- Test: upgrade single chip success
function TestIndVrdManager:test_upgrade_single_chip()
    local mgr = self.manager_class.new('test-uid-001', 'TestBoard', 'SW-123', 'pos1')
    mgr:add_chip(1, self.mock_chip1)

    local result = mgr:vrd_upgrade('/test/dir', nil)

    lu.assertEquals(0, result)
end

-- Test: upgrade multiple chips success
function TestIndVrdManager:test_upgrade_multiple_chips()
    local mgr = self.manager_class.new('test-uid-001', 'TestBoard', 'SW-123', 'pos1')
    mgr:add_chip(1, self.mock_chip1)
    mgr:add_chip(2, self.mock_chip2)

    local result = mgr:vrd_upgrade('/test/dir', nil)

    lu.assertEquals(0, result)
end

-- Test: upgrade with progress callback
function TestIndVrdManager:test_upgrade_with_progress_callback()
    local mgr = self.manager_class.new('test-uid-001', 'TestBoard', 'SW-123', 'pos1')
    mgr:add_chip(1, self.mock_chip1)
    mgr:add_chip(2, self.mock_chip2)

    local progress_calls = {}
    local callback = function(chip_idx, percent)
        table.insert(progress_calls, { idx = chip_idx, percent = percent })
    end

    local result = mgr:vrd_upgrade('/test/dir', callback)

    lu.assertEquals(0, result)
    lu.assertTrue(#progress_calls > 0)

    -- Check that both chips were updated
    local has_chip1 = false
    local has_chip2 = false
    for _, call in ipairs(progress_calls) do
        if call.idx == 1 then has_chip1 = true end
        if call.idx == 2 then has_chip2 = true end
    end
    lu.assertTrue(has_chip1)
    lu.assertTrue(has_chip2)
end

-- Test: upgrade chip failure
function TestIndVrdManager:test_upgrade_chip_failure()
    local mgr = self.manager_class.new('test-uid-001', 'TestBoard', 'SW-123', 'pos1')

    local failing_chip = {
        obj = { BoardType = 'TestBoard' },
        position = 'pos1',
        upgrade = function(self, dir)
            return 0xFF -- ERR
        end,
        get_version = function(self)
            return '1.0.0'
        end
    }

    mgr:add_chip(1, failing_chip)

    local result = mgr:vrd_upgrade('/test/dir', nil)

    lu.assertEquals(0xFF, result)
end

-- Test: upgrade ensures 100% progress
function TestIndVrdManager:test_upgrade_ensures_100_percent()
    local mgr = self.manager_class.new('test-uid-001', 'TestBoard', 'SW-123', 'pos1')
    mgr:add_chip(1, self.mock_chip1)

    local progress_calls = {}
    local callback = function(chip_idx, percent)
        table.insert(progress_calls, { idx = chip_idx, percent = percent })
    end

    mgr:vrd_upgrade('/test/dir', callback)

    -- Check that last call for chip 1 is 100%
    local last_chip1_percent = nil
    for _, call in ipairs(progress_calls) do
        if call.idx == 1 then
            last_chip1_percent = call.percent
        end
    end
    lu.assertEquals(100, last_chip1_percent)
end

function TestIndVrdManager:tearDown()
    package.loaded['independent_vrd.ind_vrd_manager'] = nil
    package.loaded['independent_vrd.ind_vrd_defs'] = nil
    package.loaded['mc.mdb'] = nil
    package.loaded['mc.context'] = nil
    package.loaded['common'] = nil
    package.loaded['fw_manager'] = nil
end