-- 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 utils = require 'mc.utils'
local log = require 'mc.logging'
local cjson = require 'cjson'
local mdb_service = require 'mc.mdb.mdb_service'

local task_mgnt = require 'redfish.protocol.task_mgnt'
local MDB_GET_PATH<const> = 'GetPath'
local CONTROLLER_INTERFACE = 'bmc.kepler.Systems.Storage.Controller'
local VOLUME_INTERFACE = 'bmc.kepler.Systems.Storage.Volume'
local DISK_ARRAY_INTERFACE = 'bmc.kepler.Systems.Storage.DiskArray'
local DRIVE_INTERFACE = 'bmc.kepler.Systems.Storage.Drive'
local STORAGE_MDB_SERVICE <const> = 'bmc.kepler.storage'

-- GetPath函数返回的无效路径
local INVALID_PATH<const> = ''

local BBU_ENABLED<const> = 1

local PMC_3152_8I_SMART_RAID = 64
local PMC_2100_8I_SMART_HBA = 65
local HI1880_SP186_M_16i = 96
local HI1880_SP186_M_8i = 113

local BIT_OFFSET_BBU_VOLTAGE_LOW = 0
local BIT_OFFSET_BBU_NEEDS_TO_BE_REPLACED = 1
local BIT_OFFSET_BBU_LEARN_CYCLE_FAILED = 2
local BIT_OFFSET_BBU_LEARN_CYCLE_TIMEOUT = 3
local BIT_OFFSET_BBU_PACK_IS_ABOUT_TO_FAIL = 4
local BIT_OFFSET_BBU_REMAINING_CAPACITY_LOW = 5
local BIT_OFFSET_BBU_NO_SPACE_FOR_CACHE_OFFLOAD = 6

local BIT_OFFSET_MEMORY_CORRECTABLE_ERROR = 1
local BIT_OFFSET_MEMORY_UNCORRECTABLE_ERROR = 2
local BIT_OFFSET_ECC_ERROR = 3
local BIT_OFFSET_COMMUNICATION_LOST = 4

local function use_sub_array(type_id)
    return type_id == PMC_3152_8I_SMART_RAID or type_id == PMC_2100_8I_SMART_HBA or
        (type_id >= HI1880_SP186_M_16i and type_id <= HI1880_SP186_M_8i)
end

local SML_LOGIC_DRIVE_RAID_LEVEL_E = {
    RAID_LEVEL_0 = 0,
    RAID_LEVEL_1 = 1,
    RAID_LEVEL_2 = 2,
    RAID_LEVEL_3 = 3,
    RAID_LEVEL_4 = 4,
    RAID_LEVEL_5 = 5,
    RAID_LEVEL_6 = 6,
    RAID_LEVEL_10 = 10,
    RAID_LEVEL_1ADM = 11,
    RAID_LEVEL_10ADM = 12,
    RAID_LEVEL_1TRIPLE = 13,
    RAID_LEVEL_10TRIPLE = 14,
    RAID_LEVEL_1E = 17,
    RAID_LEVEL_20 = 20,
    RAID_LEVEL_30 = 30,
    RAID_LEVEL_40 = 40,
    RAID_LEVEL_50 = 50,
    RAID_LEVEL_60 = 60,
    RAID_LEVEL_DELETED = 254,
    RAID_LEVEL_UNKNOWN = 255
}

local SML_LD_STATE_E = {
    LD_STATE_OFFLINE            = 0,
    LD_STATE_PARTIALLY_DEGRADED = 1,
    LD_STATE_DEGRADED           = 2,
    LD_STATE_OPTIMAL            = 3,
    LD_STATE_FAILED             = 4,
    LD_STATE_NOT_CONFIGURED     = 5,
    LD_STATE_INTERIM_RECOVERY   = 6,
    LD_STATE_READY_FOR_RECOVERY = 7,
    LD_STATE_RECOVERYING        = 8,
    LD_STATE_WRONG_DRIVE_REPLACED       = 9,
    LD_STATE_DRVIE_IMPROPERLY_CONNECTED = 10,
    LD_STATE_EXPANDING                  = 11,
    LD_STATE_NOT_YET_AVAILABLE          = 12,
    LD_STATE_QUEUED_FOR_EXPANSION       = 13,
    LD_STATE_DISABLED_FROM_SCSIID_CONFLICT = 14,
    LD_STATE_EJECTED                       = 15,
    LD_STATE_ERASE_IN_PROGRESS             = 16,
    LD_STATE_UNUSED                        = 17,
    LD_STATE_READY_TO_PERFORM_PREDICTIVE_SPARE_REBUILD = 18,
    LD_STATE_RPI_IN_PROGRESS = 19,
    LD_STATE_RPI_QUEUED = 20,
    LD_STATE_ENCRYPTED_VOLUME_WITHOUT_KEY = 21,
    LD_STATE_ENCRYPTION_MIGRATION = 22,
    LD_STATE_ENCRYPTED_VOLUME_REKEYING = 23,
    LD_STATE_ENCRYPTED_VOLUME_ENCRYPTION_OFF = 24,
    LD_STATE_VOLUME_ENCODE_REQUESTED = 25,
    LD_STATE_ENCRYPTED_VOLUME_REKEY_REQUESTED = 26,
    LD_STATE_UNSUPPORTED_ON_THIS_CONTROLLER = 27,
    LD_STATE_NOT_FORMATTED = 28, -- 未格式化
    LD_STATE_FORMATTING = 29, -- 正在格式化
    LD_STATE_SANITIZING = 30, -- 正在进行数据销毁
    LD_STATE_INITIALIZING = 31,
    LD_STATE_INITIALIZEFAIL = 32,
    LD_STATE_DELETING = 33,
    LD_STATE_DELETEFAIL = 34,
    LD_STATE_WRITE_PROTECT = 35,
    LD_STATE_UNKNOWN = 0xFF,
}

local PD_STATE_STR = {
    UNCONFIGURED_GOOD   = "OK",
    UNCONFIGURED_BAD    = "OK",
    HOT_SPARE           = "Hotspare",
    FAILED              = "OK",
    REBUILD             = "Rebuild",
    ONLINE              = "OK",
    COPYBACK            = "OK",
    SYSTEM              = "OK",
    SHIELD_UNCONFIGURED = "Fail",
    SHIELD_HOT_SPARE    = "Fail",
    SHIELD_CONFIGURED   = "Fail",
    FOREIGN             = "Fail",
    ACTIVE              = "OK",
    STANDBY             = "OK",
    SLEEP               = "OK",
    DST                 = "OK",
    SMART               = "OK",
    SCT                 = "OK",
    EPD                 = "OK",
    RAW                 = "OK",
    READY               = "OK",
    NOT_SUPPORTED       = "Fail",
    PREDICTIVE_FAILURE  = "OK",
    DIAGNOSING          = "OK",
    INCOMPATIBLE        = "Fail",
    ERASING             = "OK",
    UNKNOWN             = "Unknown"
}

local PD_STATE = {
    [0] = 'UNCONFIGURED_GOOD',
    [1] = 'UNCONFIGURED_BAD',
    [2] = 'HOT_SPARE',
    [3] = 'OFFLINE',
    [4] = 'FAILED',
    [5] = 'REBUILD',
    [6] = 'ONLINE',
    [7] = 'COPYBACK',
    [8] = 'SYSTEM',
    [9] = 'SHIELD_UNCONFIGURED',
    [10] = 'SHIELD_HOT_SPARE',
    [11] = 'SHIELD_CONFIGURED',
    [12] = 'FOREIGN',
    [13] = 'ACTIVE',
    [14] = 'STANDBY',
    [15] = 'SLEEP',
    [16] = 'DST',
    [17] = 'SMART',
    [18] = 'SCT',
    [19] = 'EPD',
    [20] = 'RAW',
    [21] = 'READY',
    [22] = 'NOT_SUPPORTED',
    [23] = 'PREDICTIVE_FAILURE',
    [24] = 'DIAGNOSING',
    [25] = 'INCOMPATIBLE',
    [26] = 'ERASING',
    [255] = 'UNKNOWN'
}

local SML_LD_STATE_LEVEL_E = {
    LEVEL_NORMAL = 0,
    LEVEL_WARNING = 1,
    LEVEL_CRITICAL = 2,
    LEVEL_UNKNOWN = 0xFF
}

local SML_BBU_STATE_LEVEL_E = {
    LEVEL_OK = 0,
    LEVEL_MINOR = 1,
    LEVEL_MAJOR = 2,
    LEVEL_CRITICAL = 3
}
local STORAGE_INFO_INVALID_STRING<const> = "N/A"

local m = {}

local function get_real_spans(ref_disk_array_list)
    local span_1880 = {}
    local span_broadcom = {}
    for _, v in pairs(ref_disk_array_list) do
        table.insert((v >= 0x8000) and span_1880 or span_broadcom, v)
    end

    return #span_1880 == 0 and span_broadcom or span_1880
end

local function has_invalid_prefix(id_str)
    -- 输入的id如果包括0作为前缀则认为非法，例如：01，012
    if #id_str > 1 and id_str:sub(1, 1) == '0' then
        return true
    end
    return false
end

function m.is_valid_storage_id(storageid)
    local id = string.match(string.lower(storageid), '^raidstorage(%d+)$')
    if id == nil or has_invalid_prefix(id) then
        log:info('Raid controller id match failed')
        return false
    end

    local filter = {Id = tonumber(id)}
    local ok, rsp = pcall(mdb_service.get_path, bus, CONTROLLER_INTERFACE, cjson.encode(filter), false)
    if not ok then
        log:info('Verify storageid(%s) failed, err(%s)', storageid, rsp.message)
        return false
    end

    if rsp.Path == INVALID_PATH then
        log:info('Get invalid raid controller path')
        return false
    end

    return true
end

function m.is_valid_storage_controller_id(storagecontrollerid, storageid)
    local id = storagecontrollerid
    if id == nil or id ~= string.match(string.lower(storageid), '^raidstorage(%d+)$') or has_invalid_prefix(id) then
        log:info('Raid storagecontroller id match failed')
        return false
    end

    local filter = {Id = tonumber(id)}
    local ok, rsp = pcall(mdb_service.get_path, bus, CONTROLLER_INTERFACE, cjson.encode(filter), false)
    if not ok then
        log:info('Verify storagecontrollerid(%s) failed, err(%s)', storagecontrollerid, rsp.message)
        return false
    end

    if rsp.Path == INVALID_PATH then
        log:info('Get invalid raid controller path')
        return false
    end

    return true
end

function m.is_valid_volume_id(volumeid)
    local id = string.match(string.lower(volumeid), '^logicaldrive(%d+)$')
    if id == nil or has_invalid_prefix(id) then
        log:info('Volume id match failed')
        return false
    end

    local filter = {Id = tonumber(id)}
    local ok, rsp = pcall(mdb_service.get_path, bus, VOLUME_INTERFACE, cjson.encode(filter), false)
    if not ok then
        log:info('Verify volumeid(%s) failed, err(%s)', volumeid, rsp.message)
        return false
    end
    if rsp.Path == INVALID_PATH then
        log:info('Get invalid volume path')
        return false
    end

    return true
end

function m.get_controller_ids(data)
    if not data then
        return
    end

    local ids = {}
    for _, v in ipairs(data) do
        local obj = mdb.get_object(bus, v, CONTROLLER_INTERFACE)
        ids[#ids + 1] = 'RAIDStorage' .. obj.Id
    end
    return ids
end

function m.get_volume_ids(data)
    if not data then
        return
    end

    local ids = {}
    for _, v in ipairs(data) do
        local obj = mdb.get_object(bus, v, 'bmc.kepler.Systems.Storage.Volume')
        ids[#ids + 1] = 'LogicalDrive' .. obj.Id
    end
    return ids
end

function m.if_unknown_to_null(str)
    local unknown = {
        ['N/A'] = true,
        ['Undefined'] = true,
        ['Unknown'] = true,
    }
    if unknown[str] or str == nil or string.len(str) == 0 then
        return cjson.null
    end

    return str
end

function m.get_supported_device_protocols(device_interface)
    local device_intf = m.if_unknown_to_null(device_interface)
    if device_intf == cjson.null then
        return device_intf
    end

    local s = utils.split(device_intf, ' ')
    return s[1]
end

function m.get_speed_gbps(device_interface)
    local device_intf = m.if_unknown_to_null(device_interface)
    if device_intf == cjson.null then
        return device_intf
    end

    local s = utils.split(device_intf, ' ')
    if s[2] == nil then
        return cjson.null
    end

    return tonumber(string.sub(s[2], 1, -2))
end

local function raid_type_to_raid_level(raid_type)
    if raid_type == SML_LOGIC_DRIVE_RAID_LEVEL_E.RAID_LEVEL_1E then
        return 'RAID1E'
    elseif raid_type == SML_LOGIC_DRIVE_RAID_LEVEL_E.RAID_LEVEL_1ADM then
        return 'RAID1(ADM)'
    elseif raid_type == SML_LOGIC_DRIVE_RAID_LEVEL_E.RAID_LEVEL_10ADM then
        return 'RAID10(ADM)'
    elseif raid_type == SML_LOGIC_DRIVE_RAID_LEVEL_E.RAID_LEVEL_1TRIPLE then
        return 'RAID1Triple'
    elseif raid_type == SML_LOGIC_DRIVE_RAID_LEVEL_E.RAID_LEVEL_10TRIPLE then
        return 'RAID10Triple'
    elseif raid_type <= SML_LOGIC_DRIVE_RAID_LEVEL_E.RAID_LEVEL_60 then
        return 'RAID' .. raid_type
    end

    return nil
end

function m.get_volumes_raid_level(raid_type)
    local raid_level = raid_type_to_raid_level(raid_type)
    if raid_level == nil then
        return cjson.null
    end
    return raid_level
end

function m.get_volumes_raid_type(raid_level)
    if raid_level == 'RAID1(ADM)' then
        return SML_LOGIC_DRIVE_RAID_LEVEL_E.RAID_LEVEL_1ADM
    elseif raid_level == 'RAID10(ADM)' then
        return SML_LOGIC_DRIVE_RAID_LEVEL_E.RAID_LEVEL_10ADM
    elseif raid_level == 'RAID1Triple' then
        return SML_LOGIC_DRIVE_RAID_LEVEL_E.RAID_LEVEL_1TRIPLE
    elseif raid_level == 'RAID10Triple' then
        return SML_LOGIC_DRIVE_RAID_LEVEL_E.RAID_LEVEL_10TRIPLE
    elseif not raid_level then
        return 255
    else
        local raid_type = raid_level:match('RAID(%d+)')
        return raid_type
    end
end

local function get_disk_array_path(ref_controller_id, id)
    local filter = cjson.encode({Id = id, RefControllerId = ref_controller_id})
    local ok, rsp = pcall(mdb_service.get_path, bus, DISK_ARRAY_INTERFACE, filter, false)
    if not ok then
        log:info(" Get disk array path failed, ret: %s", rsp)
        return
    end

    return rsp.Path
end

local function get_disk_array_obj(mdb, ref_controller_id, id)
    local path = get_disk_array_path(ref_controller_id, id)
    if path == nil then
        return nil
    end

    local ok, obj = pcall(function ()
        return mdb.get_object(bus, path, DISK_ARRAY_INTERFACE)
    end)
    if not ok then
        log:info('Get disk array object failed, ret = %s', obj)
        return nil
    end

    return obj
end

local function get_drivers(disk_array_obj)
    local drives = {}
    local prefix = '/redfish/v1/Chassis/1/Drives/HDDPlane'
    local ref_drives = disk_array_obj.RefDrives
    for _, driver in pairs(ref_drives) do
        local odata_id = {}
        odata_id['@odata.id'] = prefix .. driver
        table.insert(drives, odata_id)
    end
    return drives
end

local function get_extend_span_info(raid_type, array_obj, span, index)
    local raid_level = raid_type_to_raid_level(raid_type)
    if raid_level == nil then
        return
    end
    -- 子属性中ArrayID，UsedSpaceMiB，FreeSpaceMiB, FreeBlocksSpaceMib仅RAID10/50/60/10(ADM)/10Triple支持
    local allow_raid_level<const> = {'RAID10', 'RAID50', 'RAID60', 'RAID10(ADM)', 'RAID10Triple'}
    for _, level in pairs(allow_raid_level) do
        if raid_level == level then
            span['ArrayID'] = array_obj.Id
            span['UsedSpaceMiB'] = array_obj.UsedSpaceMiB
            span['FreeSpaceMiB'] = array_obj.TotalFreeSpaceMiB
            span['FreeBlocksSpaceMiB'] = array_obj.FreeBlocksSpaceMiB
        end
    end
end

function m.get_spans(raid_type, ref_disk_array_list, ref_controller_id)
    local span_lists = {}
    local span_list = get_real_spans(ref_disk_array_list)
    for k, disk_array_id in ipairs(span_list) do
        local obj = get_disk_array_obj(mdb, ref_controller_id, disk_array_id)
        if obj == nil then
            goto continue
        end
        local span = {}
        span['SpanName'] = string.format('Span%s', k - 1)
        span['Drives'] = get_drivers(obj)
        get_extend_span_info(raid_type, obj, span, k - 1)
        table.insert(span_lists, span)
        ::continue::
    end

    return span_lists
end

function m.get_location_indicator_state(firmware_status, predictive_failure, in_a_failed_array)
    if predictive_failure == 1 then
        return "PredictiveFailureAnalysis"
    elseif in_a_failed_array == 1 then
        return "InAFailedArray"
    else
        return PD_STATE_STR[PD_STATE[firmware_status]]
    end
end

local function get_volume_span_table(volume_paths)
    local res = {}
    for _,volume_path in pairs(volume_paths) do
        local volume_obj = mdb.get_object(bus, volume_path, VOLUME_INTERFACE)
        local cur_volume_id = volume_obj.Id
        local ref_disk_array_list = {}
        for _,v in pairs(volume_obj.RefDiskArrayList) do
            table.insert(ref_disk_array_list, v)
        end
        local real_ref_disk_array_list = get_real_spans(ref_disk_array_list)

        local item = {}
        item["volume_id"] = cur_volume_id
        item["disk_array"] = real_ref_disk_array_list
        table.insert(res, item)
    end
    return res
end

function m.get_span_name(disk_array_id, volume_paths)
    local volume_disk_array_table = get_volume_span_table(volume_paths)
    for _, item in ipairs(volume_disk_array_table) do
        local disk_array = item.disk_array
        for i, cur_disk_array_id in pairs(disk_array) do
            if disk_array_id == cur_disk_array_id then
                return string.format("Span%s", i - 1)
            end
        end
    end
end

function m.get_span_number(ref_disk_array_list)
    local span_1880 = {}
    local span_broadcom = {}
    for _, disk_array_id in pairs(ref_disk_array_list) do
        if disk_array_id >= 0x8000 then
            table.insert(span_1880, disk_array_id)
        else
            table.insert(span_broadcom, disk_array_id)
        end
    end

    return (#span_1880 == 0) and #span_broadcom or #span_1880
end

local function filter_duplicates(t)
    local set = {}
    local result = {}
    for _, v in ipairs(t) do
        if not set[v] then
            set[v] = true
            table.insert(result, v)
        end
    end
    return result
end

function m.get_num_drive_per_span(ref_disk_array_list, ref_drive_list)
    local span_num = #get_real_spans(ref_disk_array_list)
    local drive_num = #filter_duplicates(ref_drive_list)
    if span_num == 0 then
        return cjson.null
    end

    return math.floor(drive_num / span_num)
end

function m.get_volume_type(raid_type)
    if raid_type == SML_LOGIC_DRIVE_RAID_LEVEL_E.RAID_LEVEL_0 then
        return "NonRedundant"
    elseif raid_type == SML_LOGIC_DRIVE_RAID_LEVEL_E.RAID_LEVEL_1 or
        raid_type == SML_LOGIC_DRIVE_RAID_LEVEL_E.RAID_LEVEL_1ADM or
        raid_type == SML_LOGIC_DRIVE_RAID_LEVEL_E.RAID_LEVEL_1TRIPLE  then
        return "Mirrored"
    elseif raid_type == SML_LOGIC_DRIVE_RAID_LEVEL_E.RAID_LEVEL_5 or
        raid_type == SML_LOGIC_DRIVE_RAID_LEVEL_E.RAID_LEVEL_6 then
        return "StripedWithParity"
    elseif raid_type == SML_LOGIC_DRIVE_RAID_LEVEL_E.RAID_LEVEL_10 or
        raid_type == SML_LOGIC_DRIVE_RAID_LEVEL_E.RAID_LEVEL_10ADM or
        raid_type == SML_LOGIC_DRIVE_RAID_LEVEL_E.RAID_LEVEL_10TRIPLE then
        return "SpannedMirrors"
    elseif raid_type == SML_LOGIC_DRIVE_RAID_LEVEL_E.RAID_LEVEL_50 or
        raid_type == SML_LOGIC_DRIVE_RAID_LEVEL_E.RAID_LEVEL_60 then
        return "SpannedStripesWithParity"
    else
        log:info('Get volume type failed, invalid raid type: %d', raid_type)
        return cjson.null
    end
end

function m.get_status_state(state)
    local state_map = {
        [SML_LD_STATE_E.LD_STATE_OFFLINE] = "StandbyOffline",
        [SML_LD_STATE_E.LD_STATE_PARTIALLY_DEGRADED] = "Quiesced",
        [SML_LD_STATE_E.LD_STATE_DEGRADED] = "Quiesced",
        [SML_LD_STATE_E.LD_STATE_OPTIMAL] = "Enabled",
        [SML_LD_STATE_E.LD_STATE_FAILED] = "Quiesced",
        [SML_LD_STATE_E.LD_STATE_NOT_CONFIGURED] = "Disabled",
        [SML_LD_STATE_E.LD_STATE_INTERIM_RECOVERY] = "Quiesced",
        [SML_LD_STATE_E.LD_STATE_READY_FOR_RECOVERY] = "Quiesced",
        [SML_LD_STATE_E.LD_STATE_RECOVERYING] = "Updating",
        [SML_LD_STATE_E.LD_STATE_WRONG_DRIVE_REPLACED] = "Quiesced",
        [SML_LD_STATE_E.LD_STATE_DRVIE_IMPROPERLY_CONNECTED] = "Quiesced",
        [SML_LD_STATE_E.LD_STATE_EXPANDING] = "Updating",
        [SML_LD_STATE_E.LD_STATE_NOT_YET_AVAILABLE] = "Disabled",
        [SML_LD_STATE_E.LD_STATE_QUEUED_FOR_EXPANSION] = "Enabled",
        [SML_LD_STATE_E.LD_STATE_DISABLED_FROM_SCSIID_CONFLICT] = "Disabled",
        [SML_LD_STATE_E.LD_STATE_EJECTED] = "StandbyOffline",
        [SML_LD_STATE_E.LD_STATE_ERASE_IN_PROGRESS] = "StandbyOffline",
        [SML_LD_STATE_E.LD_STATE_UNUSED] = "Disabled",
        [SML_LD_STATE_E.LD_STATE_READY_TO_PERFORM_PREDICTIVE_SPARE_REBUILD] = "Quiesced",
        [SML_LD_STATE_E.LD_STATE_RPI_IN_PROGRESS] = "Updating",
        [SML_LD_STATE_E.LD_STATE_RPI_QUEUED] = "Enabled",
        [SML_LD_STATE_E.LD_STATE_ENCRYPTED_VOLUME_WITHOUT_KEY] = "StandbyOffline",
        [SML_LD_STATE_E.LD_STATE_ENCRYPTION_MIGRATION] = "Updating",
        [SML_LD_STATE_E.LD_STATE_ENCRYPTED_VOLUME_REKEYING] = "Updating",
        [SML_LD_STATE_E.LD_STATE_ENCRYPTED_VOLUME_ENCRYPTION_OFF] = "StandbyOffline",
        [SML_LD_STATE_E.LD_STATE_VOLUME_ENCODE_REQUESTED] = "Enabled",
        [SML_LD_STATE_E.LD_STATE_ENCRYPTED_VOLUME_REKEY_REQUESTED] = "Enabled",
        [SML_LD_STATE_E.LD_STATE_UNSUPPORTED_ON_THIS_CONTROLLER] = "UnavailableOffline",
        [SML_LD_STATE_E.LD_STATE_NOT_FORMATTED] = "Enabled",
        [SML_LD_STATE_E.LD_STATE_FORMATTING] = "Updating",
        [SML_LD_STATE_E.LD_STATE_SANITIZING] = "Updating",
        [SML_LD_STATE_E.LD_STATE_INITIALIZING] = "Updating",
        [SML_LD_STATE_E.LD_STATE_INITIALIZEFAIL] = "Enabled",
        [SML_LD_STATE_E.LD_STATE_DELETING] = "Updating",
        [SML_LD_STATE_E.LD_STATE_DELETEFAIL] = "Enabled",
        [SML_LD_STATE_E.LD_STATE_WRITE_PROTECT] = "Enabled",
    }

    local converted_state = state_map[state]
    if converted_state == nil then
        log:info('Get status state failed, state: %d', state)
        return cjson.null
    end

    return converted_state
end

local function get_level(state)
    local state_map = {
        [SML_LD_STATE_E.LD_STATE_OFFLINE] = SML_LD_STATE_LEVEL_E.LEVEL_CRITICAL,
        [SML_LD_STATE_E.LD_STATE_PARTIALLY_DEGRADED] = SML_LD_STATE_LEVEL_E.LEVEL_WARNING,
        [SML_LD_STATE_E.LD_STATE_DEGRADED] = SML_LD_STATE_LEVEL_E.LEVEL_WARNING,
        [SML_LD_STATE_E.LD_STATE_OPTIMAL] = SML_LD_STATE_LEVEL_E.LEVEL_NORMAL,
        [SML_LD_STATE_E.LD_STATE_FAILED] = SML_LD_STATE_LEVEL_E.LEVEL_CRITICAL,
        [SML_LD_STATE_E.LD_STATE_NOT_CONFIGURED] = SML_LD_STATE_LEVEL_E.LEVEL_WARNING,
        [SML_LD_STATE_E.LD_STATE_INTERIM_RECOVERY] = SML_LD_STATE_LEVEL_E.LEVEL_WARNING,
        [SML_LD_STATE_E.LD_STATE_READY_FOR_RECOVERY] = SML_LD_STATE_LEVEL_E.LEVEL_WARNING,
        [SML_LD_STATE_E.LD_STATE_RECOVERYING] = SML_LD_STATE_LEVEL_E.LEVEL_WARNING,
        [SML_LD_STATE_E.LD_STATE_WRONG_DRIVE_REPLACED] = SML_LD_STATE_LEVEL_E.LEVEL_WARNING,
        [SML_LD_STATE_E.LD_STATE_DRVIE_IMPROPERLY_CONNECTED] = SML_LD_STATE_LEVEL_E.LEVEL_WARNING,
        [SML_LD_STATE_E.LD_STATE_EXPANDING] = SML_LD_STATE_LEVEL_E.LEVEL_WARNING,
        [SML_LD_STATE_E.LD_STATE_NOT_YET_AVAILABLE] = SML_LD_STATE_LEVEL_E.LEVEL_WARNING,
        [SML_LD_STATE_E.LD_STATE_QUEUED_FOR_EXPANSION] = SML_LD_STATE_LEVEL_E.LEVEL_WARNING,
        [SML_LD_STATE_E.LD_STATE_DISABLED_FROM_SCSIID_CONFLICT] = SML_LD_STATE_LEVEL_E.LEVEL_CRITICAL,
        [SML_LD_STATE_E.LD_STATE_EJECTED] = SML_LD_STATE_LEVEL_E.LEVEL_WARNING,
        [SML_LD_STATE_E.LD_STATE_ERASE_IN_PROGRESS] = SML_LD_STATE_LEVEL_E.LEVEL_WARNING,
        [SML_LD_STATE_E.LD_STATE_UNUSED] = SML_LD_STATE_LEVEL_E.LEVEL_NORMAL,
        [SML_LD_STATE_E.LD_STATE_READY_TO_PERFORM_PREDICTIVE_SPARE_REBUILD] = SML_LD_STATE_LEVEL_E.LEVEL_NORMAL,
        [SML_LD_STATE_E.LD_STATE_RPI_IN_PROGRESS] = SML_LD_STATE_LEVEL_E.LEVEL_WARNING,
        [SML_LD_STATE_E.LD_STATE_RPI_QUEUED] = SML_LD_STATE_LEVEL_E.LEVEL_WARNING,
        [SML_LD_STATE_E.LD_STATE_ENCRYPTED_VOLUME_WITHOUT_KEY] = SML_LD_STATE_LEVEL_E.LEVEL_NORMAL,
        [SML_LD_STATE_E.LD_STATE_ENCRYPTION_MIGRATION] = SML_LD_STATE_LEVEL_E.LEVEL_NORMAL,
        [SML_LD_STATE_E.LD_STATE_ENCRYPTED_VOLUME_REKEYING] = SML_LD_STATE_LEVEL_E.LEVEL_NORMAL,
        [SML_LD_STATE_E.LD_STATE_ENCRYPTED_VOLUME_ENCRYPTION_OFF] = SML_LD_STATE_LEVEL_E.LEVEL_CRITICAL,
        [SML_LD_STATE_E.LD_STATE_VOLUME_ENCODE_REQUESTED] = SML_LD_STATE_LEVEL_E.LEVEL_NORMAL,
        [SML_LD_STATE_E.LD_STATE_ENCRYPTED_VOLUME_REKEY_REQUESTED] = SML_LD_STATE_LEVEL_E.LEVEL_NORMAL,
        [SML_LD_STATE_E.LD_STATE_UNSUPPORTED_ON_THIS_CONTROLLER] = SML_LD_STATE_LEVEL_E.LEVEL_CRITICAL,
        [SML_LD_STATE_E.LD_STATE_NOT_FORMATTED] = SML_LD_STATE_LEVEL_E.LEVEL_NORMAL,
        [SML_LD_STATE_E.LD_STATE_FORMATTING] = SML_LD_STATE_LEVEL_E.LEVEL_NORMAL,
        [SML_LD_STATE_E.LD_STATE_SANITIZING] = SML_LD_STATE_LEVEL_E.LEVEL_NORMAL,
        [SML_LD_STATE_E.LD_STATE_INITIALIZING] = SML_LD_STATE_LEVEL_E.LEVEL_NORMAL,
        [SML_LD_STATE_E.LD_STATE_INITIALIZEFAIL] = SML_LD_STATE_LEVEL_E.LEVEL_NORMAL,
        [SML_LD_STATE_E.LD_STATE_DELETING] = SML_LD_STATE_LEVEL_E.LEVEL_NORMAL,
        [SML_LD_STATE_E.LD_STATE_DELETEFAIL] = SML_LD_STATE_LEVEL_E.LEVEL_NORMAL,
        [SML_LD_STATE_E.LD_STATE_WRITE_PROTECT] = SML_LD_STATE_LEVEL_E.LEVEL_NORMAL
    }

    return state_map[state]
end


function m.get_status_severity(state)
    local level = get_level(state)
    if level == nil then
        log:info('Get level failed, state: %d', state)
        return cjson.null
    end

    if level == SML_LD_STATE_LEVEL_E.LEVEL_NORMAL then
        return "Informational"
    elseif level == SML_LD_STATE_LEVEL_E.LEVEL_WARNING or level == SML_LD_STATE_LEVEL_E.LEVEL_CRITICAL then
        return "Minor"
    else
        log:info('Get invalid severity, state: %d', state)
        return cjson.null
    end
end

function m.get_status_health(state)
    local level = get_level(state)
    if level == nil then
        log:info('Get level failed, state: %d', state)
        return cjson.null
    end

    if level == SML_LD_STATE_LEVEL_E.LEVEL_NORMAL then
        return "OK"
    elseif level == SML_LD_STATE_LEVEL_E.LEVEL_WARNING or level == SML_LD_STATE_LEVEL_E.LEVEL_CRITICAL then
        return "Warning"
    else
        log:info('Get invalid health, state: %d', state)
        return cjson.null
    end
end

local function get_drive_obj(drive_id)
    local name = "Disk" .. drive_id
    local filter = {Name = name}
    local ok, rsp = pcall(mdb_service.get_path, bus, DRIVE_INTERFACE, cjson.encode(filter), false)
    if not ok or rsp.Path == INVALID_PATH then
        log:info('Get drive path failed, err: %s', tostring(rsp.message))
        return
    end

    local ok, drive_obj = pcall(function ()
        return mdb.get_object(bus, rsp.Path, DRIVE_INTERFACE)
    end)
    if not ok then
        log:info('Get object failed, ret = %s', drive_obj)
        return nil
    end
    return drive_obj
end

function m.is_valid_refdisk_array_id(drive_id)
    local drive_obj = get_drive_obj(drive_id)
    if drive_obj == nil then
        log:info('Get drive object failed')
        return false
    end
    -- 判断Drive是否组成过逻辑盘（即RefDiskArrayId是否为65535）
    if drive_obj.RefDiskArrayId == 65535 then
        return false
    end
    return true
end

function m.get_refdisk_array_id(drive_id)
    local drive_obj = get_drive_obj(drive_id)
    if drive_obj == nil then
        log:info('Get refdisk array id failed')
        return nil
    end

    return drive_obj.RefDiskArrayId
end

function m.get_associate_ld(associate_ld)
    -- associate_ld只能有一个成员, 示例：/redfish/v1/Systems/1/Storages/RAIDStorage0/Volumes/LogicalDrive3
    local associate_uri = associate_ld[1]["@odata.id"]
    local pattern = "/redfish/v1/Systems/%d+/Storages/RAIDStorage%d+/Volumes/LogicalDrive(%d+)"
    local logical_drive = string.match(associate_uri, pattern)
    if logical_drive == nil then
        log:info('LogicalDrive match failed')
        return 0xffff
    end
    return logical_drive
end

function m.operations(progress, init_state, paths)
    -- 保留有效部分兼容定制化场景
    if init_state == 0 then
        return {}
    end
    local result = {
        OperationName = "Foreground Init",
        PercentageComplete = progress
    }
    return {result}
end

function m.get_associated_resource_task(paths)
    for _, v in ipairs(paths) do
        local obj = mdb.get_object(bus, v, 'bmc.kepler.TaskService.Task')
        local x, y = string.find(obj.Name, 'RaidController%d*_LogicalDrive%d*_init')
        if x ~= nil then
            return obj.Id
        end
    end
end

function m.get_associated_card(DeviceName, Position, PCIeCardsList)
    local associated_path = cjson.null
    local ok, obj
    for _, v in ipairs(PCIeCardsList) do
        ok, obj = pcall(mdb.get_object, bus, v, 'bmc.kepler.Systems.PCIeDevices.PCIeCard')
        if ok and obj.DeviceName == DeviceName and obj.Position == Position then
            associated_path = "/redfish/v1/Chassis/1/PCIeDevices/" .. obj.NodeID
            return associated_path
        end
    end
    return associated_path
end

local function get_volume_obj(volume_id, ref_controller_id)
    local filter = cjson.encode({Id = volume_id, RefControllerId = ref_controller_id})
    local ok, rsp = pcall(mdb_service.get_path, bus, VOLUME_INTERFACE, filter, false)
    if not ok then
        log:info(" Get volume path failed, ret: %s", rsp)
        return nil
    end

    if rsp.Path == INVALID_PATH then
        log:info('Get invalid volume path')
        return nil
    end
    local volume_obj
    ok, volume_obj = pcall(function ()
        return mdb.get_object(bus, rsp.Path, VOLUME_INTERFACE)
    end)
    if not ok then
        log:info('Get volume object failed')
        return nil
    end
    return volume_obj
end

local function get_span_count_by_ref_volume(drive)
    if not drive.RefDiskArrayId or not drive.RefVolumeList then
        return 1
    end
    local volume_obj
    for _, volume_id in pairs(drive.RefVolumeList) do
        volume_obj = get_volume_obj(volume_id, drive.RefControllerId)
        if not volume_obj or not volume_obj.RefDiskArrayList then
            goto continue
        end
        for _, ref_disk_array_id in pairs(volume_obj.RefDiskArrayList) do
            if ref_disk_array_id == drive.RefDiskArrayId then
                return volume_obj.SpanCount and volume_obj.SpanCount or 1
            end
        end
        ::continue::
    end

    log:info("Reference volume not found")
    return 1
end

local function get_free_blocks_space(free_blocks_space_arr, span_count)
    local arr = cjson.json_object_new_array()
    if not free_blocks_space_arr then
        return arr
    end

    for _, free_block in pairs(free_blocks_space_arr) do
        arr[#arr + 1] = free_block * span_count
    end
    return arr
end

function m.get_related_array(disk_array_path, drive, raid_tpye_id)
    local span_count = 1
    if not use_sub_array(raid_tpye_id) then
        span_count = get_span_count_by_ref_volume(drive)
    end
    local related_array_info = cjson.json_object_new_object()
    local obj = mdb.get_object(bus, disk_array_path, 'bmc.kepler.Systems.Storage.DiskArray')
    related_array_info['Members'] = cjson.json_object_new_array()
    related_array_info['Members'] = cjson.json_object_from_table(obj.RefDrives)
    related_array_info['FreeSpaceMiBPerDrive'] = obj.AverageDriveFreeSpaceMiB
    related_array_info['TotalFreeSpaceMiB'] = nil
    if obj.TotalFreeSpaceMiB then
        related_array_info['TotalFreeSpaceMiB'] = obj.TotalFreeSpaceMiB * span_count
    end
    related_array_info['FreeBlocksSpaceMiB'] = get_free_blocks_space(obj.FreeBlocksSpaceMiB, span_count)
    related_array_info['NumDrivePerSpan'] = obj.DriveNumPerSpan
    related_array_info['VolumeRaidLevel'] = raid_type_to_raid_level(tonumber(obj.RAIDType))
    return related_array_info
end

function m.get_consistency_check_param_period(param)
    local period = 0xffff
    if param ~= nil then
        period = param
    end
    return period
end

function m.get_consistency_check_param_rate(param)
    local rate = 0xff
    if param ~= nil then
        if param == 'Low' then
            rate = 1
        elseif param == 'Medium' then
            rate = 2
        elseif param == 'High' then
            rate = 3
        else
            rate = 4
        end
    end
    return rate
end

function m.get_consistency_check_param_auto_repaired(param)
    local auto_repaired = 0xff
    if param ~= nil then
        auto_repaired = param == false and 0 or 1
    end
    return auto_repaired
end

function m.get_consistency_check_param_delay(param)
    local delay = 0xffffffff
    if param ~= nil then
        delay = param
    end
    return delay
end

function m.get_phystatus_list(input)
    local res = {}
    local obj
    local data
    for _, path in ipairs(input) do
        for _, x in ipairs(path) do
            data = {}
            obj = mdb.get_object(bus, x, 'bmc.kepler.Systems.Storage.PhyError')
            data["PhyId"] = obj.PhyId
            data["InvalidDwordCount"] = obj.InvalidDwordCount
            data["LossDwordSyncCount"] = obj.LossDwordSyncCount
            data["PhyResetProblemCount"] = obj.PhyResetProblemCount
            data["RunningDisparityErrorCount"] = obj.RunningDisparityErrorCount
            table.insert(res, data)
        end
    end
    return res
end

function m.get_capaitancehealth(state, health)
    if state ~= BBU_ENABLED then
        return cjson.null
    end
    if health == SML_BBU_STATE_LEVEL_E.LEVEL_OK then
        return "OK"
    elseif health == SML_BBU_STATE_LEVEL_E.LEVEL_MINOR then
        return "Minor"
    elseif health == SML_BBU_STATE_LEVEL_E.LEVEL_MAJOR then
        return "Major"
    elseif health == SML_BBU_STATE_LEVEL_E.LEVEL_CRITICAL then
        return "Critical"
    else
        return cjson.null
    end
end

function m.get_capaitanceseverity(state, health)
    if state ~= BBU_ENABLED then
        return cjson.null
    end
    if health == SML_BBU_STATE_LEVEL_E.LEVEL_OK then
        return "Informational"
    elseif health == SML_BBU_STATE_LEVEL_E.LEVEL_MINOR then
        return "Minor"
    elseif health == SML_BBU_STATE_LEVEL_E.LEVEL_MAJOR then
        return "Major"
    elseif health == SML_BBU_STATE_LEVEL_E.LEVEL_CRITICAL then
        return "Critical"
    else
        return cjson.null
    end
end

function m.get_capaitancefaultdetails(healthstatus)
    if healthstatus == 0 then
        return cjson.null
    end

    local healthstatus_str = ''
    if healthstatus & (1 << BIT_OFFSET_BBU_VOLTAGE_LOW) ~= 0 then
        healthstatus_str = healthstatus_str .. "Voltage Low,"
    end
    if healthstatus & (1 << BIT_OFFSET_BBU_NEEDS_TO_BE_REPLACED) ~= 0 then
        healthstatus_str = healthstatus_str .. "Battery Needs To Be Replaced,"
    end
    if healthstatus & (1 << BIT_OFFSET_BBU_LEARN_CYCLE_FAILED) ~= 0 then
        healthstatus_str = healthstatus_str .. "Learn Cycle Failed,"
    end
    if healthstatus & (1 << BIT_OFFSET_BBU_LEARN_CYCLE_TIMEOUT) ~= 0 then
        healthstatus_str = healthstatus_str .. "Learn Cycle Timeout,"
    end
    if healthstatus & (1 << BIT_OFFSET_BBU_PACK_IS_ABOUT_TO_FAIL) ~= 0 then
        healthstatus_str = healthstatus_str .. "Pack is about to fail,"
    end
    if healthstatus & (1 << BIT_OFFSET_BBU_REMAINING_CAPACITY_LOW) ~= 0 then
        healthstatus_str = healthstatus_str .. "Remaining Capacity Low,"
    end
    if healthstatus & (1 << BIT_OFFSET_BBU_NO_SPACE_FOR_CACHE_OFFLOAD) ~= 0 then
        healthstatus_str = healthstatus_str .. "No Space for Cache Offload,"
    end

    if #healthstatus_str == 0 then
        return cjson.null
    end
    healthstatus_str = string.sub(healthstatus_str, 1, -2)
    return healthstatus_str
end

function m.get_policywritable(policy, policylist)
    if not next(policylist) then
        return false
    end
    return policy
end

function m.get_faultdetails(faultcode)
    if faultcode == 0xFFFF or faultcode == 0 then
        return cjson.null
    end

    local health_str = ''
    if faultcode & (1 << BIT_OFFSET_MEMORY_CORRECTABLE_ERROR) ~= 0 then
        health_str = health_str .. "Memory uncorrectable errors,"
    end
    if faultcode & (1 << BIT_OFFSET_MEMORY_UNCORRECTABLE_ERROR) ~= 0 then
        health_str = health_str .. "Memory ECC errors,"
    end
    if faultcode & (1 << BIT_OFFSET_ECC_ERROR) ~= 0 then
        health_str = health_str .. "NVRAM uncorrectable errors,"
    end
    if faultcode & (1 << BIT_OFFSET_COMMUNICATION_LOST) ~= 0 then
        health_str = health_str .. "Raid communication loss,"
    end

    if #health_str == 0 then
        return cjson.null
    end
    health_str = string.sub(health_str, 1, -2)
    return health_str
end

return m
