-- 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.

local orm_object_manage = require('mc.orm.object_manage')
local c_storageconfig = require('storageconfig.storageconfig_object')
local controller_collection = require('controller.controller_collection')
local rpc_service_controller = require('rpc_services.rpc_service_controller')
local drive_collection = require('drive.drive_collection')
local rpc_service_drive = require('rpc_services.rpc_service_drive')
local volume_collection = require('volume.volume_collection')
local rpc_service_volume = require('rpc_services.rpc_service_volume')
local array_collection = require('array.array_collection')
local battery_collection = require('battery.battery_collection')
local pd_identify_service = require('pd.pd_identify_service')
local link_volume_array_drive_service = require('link_volume_array_drive_service')
local test_common = require('test_common.utils')
local mctp_service = require('mctp.mctp_service')
local bus_monitor_service = require('bus_monitor_service')
local c_storage_service = require('storage.service')
local ctrl_commu_loss_monitor = require('ctrl_commu_loss_monitor')
local mctp_lib = require('mctp_lib')
local worker = require('worker.core')
local sml = require('sml')
local hook_sml_core = require('hook_sml_core')
local tasks = require('tasks')
local app_base = require('mc.app_base')
local hook_tasks = require('hook_tasks')
local types = require('sml.types')
local class = require('mc.class')
local model = require('class.model')
local c_drives_object = require('drives.drives_object')
local storage_bus = require('storage_bus')
local open_db = require('storage.db')

TestStorage = class(c_storage_service)

local function start_mock_service(app_name, mode_module)
    local app = app_base.new(app_name)
    local work = worker.new(1)
    work:start_module(mode_module)
    work:send('1', true)
    work:send('2', true)
    return {
        stop = function(_, bus)
            bus:call(app.service_name, app.app_path, app.app_interface, 'exit')
            work:join()
        end,
    }
end

local function construct_db()
    local ok, datas = pcall(require, 'storage.datas')
    if not ok then
        -- 如果没有datas配置,证明当前组件不需要datas,仅打开数据库
        datas = nil
    end
    local db = open_db(':memory:', datas)
    return db
end

local function start_mctpd_service()
    return start_mock_service('mctpd', 'mock_mctpd_service')
end

local function start_mdb_service()
    return start_mock_service('Mdb', 'mock_mdb_service')
end

function mctp_lib.bdf_to_phy_addr(b, d, f)
    return 3
end

function mctp_lib.get_mctp_pcie_binding(bus)
    return { BmcEid = 1, BmcPhyAddr = 2 }
end

function TestStorage:setupClass()
    self.bus = test_common.dbus_launch('../.dbus', nil, true)
    self.bus:request_name('bmc.kepler.StorageTest')
    self.app_name = 'StorageTest'
    self.db = construct_db()
    model.init(self.bus)

    self.mctpd_app = start_mctpd_service()
    self.mdb_app = start_mdb_service()
    self.old_get_endpoint_and_transport = mctp_lib.get_endpoint_and_transport
    mctp_lib.get_endpoint_and_transport = function()
        return {}, {}
    end
end

function TestStorage:teardownClass()
    self.mctpd_app:stop(self.bus)
    self.mdb_app:stop(self.bus)
    self.bus:close()
    mctp_lib.get_endpoint_and_transport = self.old_get_endpoint_and_transport
end

function TestStorage:set_controller_data()
    self.test_controller_data = {
        operations = { pd_operations = 0, ld_operations = 0, ctrl_operations = 0 },
        properties = string.unpack(
            'I4',
            types.t_ctrl_properties:pack({ copyback_enabled = 0, smarter_copyback_enabled = 0, jbod_enabled = 0 })
        ),
        raid0_supported = 1,
        ctrl_name = 'MyName',
        ctrl_temp = 10,
        fw_version = '2.0.0',
        mode = 0,
        work_mode = { 'RAID', 'JBOD' },
        memory_size = 512,
        device_interface = 0,
        ctrl_sn = '23456',
        min_strip = 0,
        max_strip = 1,
        nvdata_version = '1.0.0.1',
        memory_ecc_count = 20,
        cache_pinned = 1,
        maint_pd_fail_history = 1,
        hardware_revision = 'V200',
        pcie_link_width = 16,
        consis_check_enabled = 1,
        consis_check_period = 230,
        consis_check_rate = 2,
        consis_check_repair = 1,
        consis_check_delay = 12,
        consis_check_totalvd = 10,
        consis_check_completedvd = 10,
        consis_check_status = 1,
        read_cache_percent = 0,
        nobattery_write_cache = 0,
    }
end

function TestStorage:set_pd_info()
    self.test_pd_info = {
        pd_model = 'model',
        pd_temperature = 255,
        pd_capacity = 123,
        pd_interface_type = 'SAS',
        pd_device_speed = 2,
        pd_link_speed = 1,
        pd_media_type = 1,
        pd_fw_status = 1,
        pd_sn = 'ZC1ALLK4',
        pd_manufacturer = 'TESTATA',
        pd_ssd_power_on_hours = 251,
        pd_fw_version = 'TN05',
        pd_rotation_speed = 7200,
        pd_block_size = 512,
        pd_sas_addr_1 = '5942533A44D5E000',
        pd_sas_addr_2 = '666666655444AASSS',
        pd_ssd_lifeleft = 55,
        pd_predictive_fail_count = 32,
        pd_media_err_count = 7,
        pd_other_err_count = 88,
        pd_patrol_status = 1,
        pd_power_state = 1,
        pd_rebuild_state = 1,
        pd_rebuild_progress = 55,
        pd_boot_priority = 1,
        pd_hotspare_type = 1,
    }
end

function TestStorage:set_pd_sas_smart_info()
    self.test_pd_sas_smart_info = {
        strip_temperature = 1,
        glist_len = 1,
        plist_len = 1,
        manufacture_data = '1',
        blocks_sent = 1,
        blocks_received = 1,
        minutes_left = 1,
    }
end

function TestStorage:set_array_info()
    self.test_array_info = {
        used_space = 500,
        total_free_space = 600,
        free_blocks_count = 2,
        free_blocks_space = { 200, 400 },
        ld_count = 2,
        ld_ids = { 0, 1 },
        pd_count = 2,
        pd_ids = { 0, 1 },
        pd_slots = { 3, 6 },
        pd_enclosures = { 0, 0 },
    }
end

function TestStorage:set_ld_info()
    self.test_ld_info = {
        last_update_timestamp = 0,
        ld_warnig_info_reported = 0,
        target_id = 0,
        name = 'N/A',
        drive_state = 0,
        raid_level = 0,
        default_read_policy = 0,
        default_write_policy = 0,
        default_cache_policy = 0,
        current_read_policy = 0,
        current_write_policy = 0,
        current_cache_policy = 0,
        current_fgi_state = 0,
        access_policy = 0,
        disk_cache_policy = 0,
        init_state = 0,
        consistent_check = 0,
        span_depth = 1,
        num_drive_per_span = 1,
        bgi_enabled = 0,
        bootable = 0,
        is_sscd = 0,
        is_epd = 0,
        strip_size = 0,
        accelerator = 0,
        size = 0,
        max_resizeable_size = 0,
        boot_priority = 0,
        cache_line_size = 0,
        ref_array = { 0, 1 },
        progress_info = {
            rebuild_state = 1,
            rebuild_progress = 0,
        },
        sscd_ld_list = 0,
        spare_pd_ids = { 0 },
        spare_pd_slots = { 3 },
        spare_pd_enclosure_ids = { 0 },
        ld_ref_array = { 0 },
    }
end

function TestStorage:set_battery_info()
    self.test_battery_info = {
        bbu_warnig_info_reported = 0,
        present = 1,
        temperature = 0,
        type = 'Capacitance1',
        pack_missing = 0,
        voltage_low = 0,
        temperature_high = 0,
        replacepack = 0,
        learn_cycle_failed = 1,
        learn_cycle_timeout = 0,
        predictive_failure = 0,
        remaining_capacity_low = 0,
        no_space = 0,
        failed = 0,
    }
end

function TestStorage:setup()
    sml.mctp_infos.clean_mctp_info()
    sml.mctp_infos.set_bmc_mctp_info(0, 0)
    hook_sml_core.hook()
    self.tasks = hook_tasks.hook()
    self.storage_bus = storage_bus.new(self)

    self.object_manager = orm_object_manage.get_instance(self.db, self.bus)
    self.c_storageconfig = c_storageconfig.new(self)
    self.c_drives_object = c_drives_object.new(self)
    self.controller_collection = controller_collection.new()
    self.rpc_service_controller = rpc_service_controller.new()
    self.drive_collection = drive_collection.new()
    self.rpc_service_drive = rpc_service_drive.new()
    self.volume_collection = volume_collection.new(self)
    self.rpc_service_volume = rpc_service_volume.new()
    self.array_collection = array_collection.new(self)
    self.link_volume_array_drive_service = link_volume_array_drive_service.new()
    self.battery_collection = battery_collection.new(self)
    self.pd_identify_service = pd_identify_service.new()
    self.bus_monitor_service = bus_monitor_service.new(self.bus)
    self.mctp_service = mctp_service.new(self.bus)
    self.ctrl_commu_loss_monitor = ctrl_commu_loss_monitor.new()

    self:set_controller_data()
    self:set_pd_info()
    self:set_pd_sas_smart_info()
    self:set_array_info()
    self:set_ld_info()
    self:set_battery_info()
    local storage_config = {
        VolumesStateAbnormal = 0,
        property_changed = {
            on = function(cb) end,
        },
    }
    self.object_manager.mc:add_object('StorageConfig', storage_config, 1)
end

function TestStorage:teardown()
    hook_sml_core.unhook()
    orm_object_manage.destroy()
    mctp_service.destroy()
    controller_collection.destroy()
    drive_collection.destroy()
    pd_identify_service.destroy()
    -- 后续 Test_Pd_Identify_* 等用例仍依赖单例上的三张表;destroy 后立刻重建,避免 nil 索引
    pd_identify_service.new(self.db)
    bus_monitor_service.destroy()
    tasks.get_instance():stop_all()

    hook_tasks.unhook()
end