-- 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 class = require 'mc.class'
local singleton = require 'mc.singleton'
local log = require 'mc.logging'
local mdb = require 'mc.mdb'
local drive_collection = require 'drive.drive_collection'

local metric_collect = class()

function metric_collect:ctor(bus)
    self.bus = bus
end

function metric_collect:get_drive_data_collection_items(obj)
    -- 对象不在位,或初始化未完成时,或未通过Raid管理的直通盘无需触发采集
    local ok, result = pcall(mdb.get_object, self.bus, obj.path, 'bmc.kepler.Systems.Storage.Drive')
    if not ok or result.Presence ~= 1 or result.SerialNumber == '' or result.SerialNumber == 'N/A' then
        return '', {}, {}, {}
    end
    local Classification = {
        { PropName = 'Manufacturer', PropVal = result.Manufacturer },
        { PropName = 'Model', PropVal = result.Model },
        { PropName = 'CapacityMiB', PropVal = tostring(result.CapacityMiB) },
        { PropName = 'MediaType', PropVal = tostring(result.MediaType) },
        { PropName = 'Protocol', PropVal = tostring(result.Protocol) },
        { PropName = 'Revision', PropVal = result.Revision },
        { PropName = 'ChassisId', PropVal = '1' }
    }
    local Identification = {
        { PropName = 'SerialNumber', PropVal = result.SerialNumber },
        { PropName = 'SlotNumber', PropVal = tostring(result.SlotNumber) },
        { PropName = 'Name', PropVal = result.Name },
        { PropName = 'ComNodeId', PropVal = result.NodeId },
        { PropName = 'PhysicalLocation', PropVal = "HDD Plane" }
    }
    local MetricName = {
        'disk.temperature', 'disk.hddhealth', 'disk.mediaerrorcount', 'disk.prefailerrorcount',
        'disk.othererrorcount', 'disk.smart_item', 'disk.asc', 'disk.ascq'
    }
    return 'Disk', Classification, Identification, MetricName
end

local function get_log_prop(drive, prop_name, one_double_quotation)
    if drive.diagnose_info and drive.diagnose_info[prop_name] then
        local str_info = tostring(drive.diagnose_info[prop_name])
        if not one_double_quotation then
            str_info = '"' .. string.gsub(str_info, '"', '""') .. '"'
        end
        return str_info
    end

    return ''
end

local function get_temperature(data, disk_obj)
    table.insert(data, tostring(disk_obj.TemperatureCelsius))
end

local function get_hddhealth(data, disk_obj)
    table.insert(data, tostring(disk_obj.Health))
end

local function get_mediaerrorcount(data, disk_obj)
    table.insert(data, tostring(disk_obj.MediaErrorCount))
end

local function get_prefailerrorcount(data, disk_obj)
    table.insert(data, tostring(disk_obj.PredictedFailCount))
end

local function get_othererrorcount(data, disk_obj)
    table.insert(data, tostring(disk_obj.OtherErrorCount))
end

local function get_smart_item(data, disk_obj)
    table.insert(data, get_log_prop(disk_obj, 'smart_item', false))
end

local function get_asc(data, disk_obj)
    table.insert(data, get_log_prop(disk_obj, 'asc', true))
end

local function get_ascq(data, disk_obj)
    table.insert(data, get_log_prop(disk_obj, 'ascq', true))
end

local function get_error_log_count(data, disk_obj)
    table.insert(data, get_log_prop(disk_obj, 'error_log_count', true))
end

local function get_error_log(data, disk_obj)
    table.insert(data, get_log_prop(disk_obj, 'error_log', false))
end

local function get_selftest_failure_count(data, disk_obj)
    table.insert(data, get_log_prop(disk_obj, 'selftest_failure_count', false))
end

local function get_selftest_log(data, disk_obj)
    table.insert(data, get_log_prop(disk_obj, 'selftest_log', false))
end

local function get_phy_event_icrc_count(data, disk_obj)
    table.insert(data, get_log_prop(disk_obj, 'phy_event_icrc_count', true))
end

local function get_glist_count(data, disk_obj)
    table.insert(data, get_log_prop(disk_obj, 'glist_count', true))
end

local function get_glist(data, disk_obj)
    table.insert(data, get_log_prop(disk_obj, 'glist', false))
end

local function get_plist_count(data, disk_obj)
    table.insert(data, get_log_prop(disk_obj, 'plist_count', true))
end

local function get_plist(data, disk_obj)
    table.insert(data, get_log_prop(disk_obj, 'plist', false))
end

local function get_critical_event_count(data, disk_obj)
    table.insert(data, get_log_prop(disk_obj, 'critical_event_count', true))
end

local function get_critical_event_log(data, disk_obj)
    table.insert(data, get_log_prop(disk_obj, 'critical_event_log', false))
end

local function get_error_count_log(data, disk_obj)
    table.insert(data, get_log_prop(disk_obj, 'error_count_log', false))
end

local function get_temperature_log(data, disk_obj)
    table.insert(data, get_log_prop(disk_obj, 'temperature_log', false))
end

local function get_informational_exceptions_log(data, disk_obj)
    table.insert(data, get_log_prop(disk_obj, 'informational_exceptions_log', false))
end

local function get_background_medium_scan_count(data, disk_obj)
    table.insert(data, get_log_prop(disk_obj, 'background_medium_scan_count', true))
end

local function get_background_medium_scan_log(data, disk_obj)
    table.insert(data, get_log_prop(disk_obj, 'background_medium_scan_log', false))
end

local function get_protocol_specific_port_log(data, disk_obj)
    table.insert(data, get_log_prop(disk_obj, 'protocol_specific_port_log', false))
end

local function get_solid_state_media_log(data, disk_obj)
    table.insert(data, get_log_prop(disk_obj, 'solid_state_media_log', false))
end

local function get_general_statistics_log(data, disk_obj)
    table.insert(data, get_log_prop(disk_obj, 'general_statistics_log', false))
end

local function get_vendor_specific_log(data, disk_obj)
    table.insert(data, get_log_prop(disk_obj, 'vendor_specific_log', false))
end

local disk_metric_func_table = {
    ['disk.temperature']                    = { metric_func = get_temperature },
    ['disk.hddhealth']                      = { metric_func = get_hddhealth },
    ['disk.mediaerrorcount']                = { metric_func = get_mediaerrorcount },
    ['disk.prefailerrorcount']              = { metric_func = get_prefailerrorcount },
    ['disk.othererrorcount']                = { metric_func = get_othererrorcount },
    ['disk.smart_item']                     = { metric_func = get_smart_item },
    ['disk.asc']                            = { metric_func = get_asc },
    ['disk.ascq']                           = { metric_func = get_ascq },
    ['disk.error_log_count']                = { metric_func = get_error_log_count },
    ['disk.error_log']                      = { metric_func = get_error_log },
    ['disk.selftest_failure_count']         = { metric_func = get_selftest_failure_count },
    ['disk.selftest_log']                   = { metric_func = get_selftest_log },
    ['disk.phy_event_icrc_count']           = { metric_func = get_phy_event_icrc_count },
    ['disk.glist_count']                    = { metric_func = get_glist_count },
    ['disk.glist']                          = { metric_func = get_glist },
    ['disk.plist_count']                    = { metric_func = get_plist_count },
    ['disk.plist']                          = { metric_func = get_plist },
    ['disk.critical_event_count']           = { metric_func = get_critical_event_count },
    ['disk.critical_event_log']             = { metric_func = get_critical_event_log },
    ['disk.error_count_log']                = { metric_func = get_error_count_log },
    ['disk.temperature_log']                = { metric_func = get_temperature_log },
    ['disk.informational_exceptions_log']   = { metric_func = get_informational_exceptions_log },
    ['disk.background_medium_scan_count']   = { metric_func = get_background_medium_scan_count },
    ['disk.background_medium_scan_log']     = { metric_func = get_background_medium_scan_log },
    ['disk.protocol_specific_port_log']     = { metric_func = get_protocol_specific_port_log },
    ['disk.solid_state_media_log']          = { metric_func = get_solid_state_media_log },
    ['disk.general_statistics_log']         = { metric_func = get_general_statistics_log },
    ['disk.vendor_specific_log']            = { metric_func = get_vendor_specific_log }
}

local function get_metric_data(drive_obj, metric_name)
    local data = {}
    if disk_metric_func_table[metric_name] ~= nil then
        disk_metric_func_table[metric_name].metric_func(data, drive_obj)
    else
        log:notice('invalid metric, metric_name = %s', metric_name)
    end
    return data
end

function metric_collect:get_drive_data_collection_data(obj, metric_name)
    local ok, disk_obj = pcall(mdb.get_object, self.bus, obj.path, 'bmc.kepler.Systems.Storage.Drive')
    if not ok or not disk_obj then
        log:notice('[Storage] Can not get path obj, path = %s', obj.path)
        return {}
    end
    local drive = drive_collection.get_instance():get_drive(disk_obj.Name)
    if not drive then
        log:notice('[Storage] Can not get drive obj, name = %s', disk_obj.Name)
        return {}
    end

    local Val = {}
    for _, value in ipairs(metric_name) do
        local data = get_metric_data(drive, value)
        if #data ~= 0 then
            table.insert(Val, { MetricName = value, Data = data })
        end
    end
    return Val
end

return singleton(metric_collect)