-- 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 log = require 'mc.logging'
local cjson = require 'cjson'
local context = require 'mc.context'
local redfish_utils = require 'redfish_utils'
local vos = require 'utils.vos'

local UMS_PATH <const> = '/bmc/kepler/Managers/1/Ums/Ums_SP_00'
local UMS_INTERFACE <const> = 'bmc.kepler.Managers.Ums'

local RFPROP_ODATA_CONTEXT <const> = "@odata.context"
local RFPROP_ODATA_ID <const> = "@odata.id"
local RFPROP_ODATA_TYPE <const> = "@odata.type"
local RFPROP_ID <const> = "Id"
local RFPROP_NAME <const> = "Name"

-- 加密情况下的 osinstall 文件存放路径
local SP_OSINSTALL_PARA_PATH <const> = "/data/opt/bmc/sp/sposinstallpara"
local SP_RAID_CUR_CFG_PATH <const> = "/data/sp/spforbmc/operate/spraid/export"
-- ums 目录下的 osinstall 文件名
local UMS_OSINSTALL_FILE_NAME <const> = "osinstall.json"

local UMS_TRANS_FILE_ID_SP_RESULT <const> = 1
local UMS_TRANS_FILE_ID_SP_DIALOG <const> = 2
local UMS_TRANS_FILE_ID_SP_OSINSTALL <const> = 3
local UMS_TRANS_FILE_ID_SP_FWUPDATE <const> = 4
local UMS_TRANS_FILE_ID_SP_NETDEV <const> = 5
local UMS_TRANS_FILE_ID_SP_RAID <const> = 6
local UMS_TRANS_FILE_ID_SP_CFG <const> = 7
local UMS_TRANS_FILE_ID_SP_INFO <const> = 8
local UMS_TRANS_FILE_ID_SP_SCHEMA <const> = 9
local UMS_TRANS_FILE_ID_SP_RAID_EXPORT <const> = 10
local UMS_TRANS_FILE_ID_SP_DIAGNOSE <const> = 11
local UMS_TRANS_FILE_ID_SP_DRIVE_ERASE <const> = 12
local UMS_TRANS_FILE_ID_SP_SYSTEM_ERASE<const> = 13
local UMS_TRANS_FILE_ID_SP_ENCRYPTED_OSINSTALL <const> = 14

local FILE_TYPE_TABLE <const> = {
    [UMS_TRANS_FILE_ID_SP_OSINSTALL] = "OSInstall",
    [UMS_TRANS_FILE_ID_SP_RAID] = "RAIDCfg",
    [UMS_TRANS_FILE_ID_SP_FWUPDATE] = "FWUpdateCfg",
    [UMS_TRANS_FILE_ID_SP_CFG] = "SPCfg",
    [UMS_TRANS_FILE_ID_SP_SYSTEM_ERASE] = "SystemEraseCfg"
}

local ODATA_INFO_TABLE <const> = {
    ["DeviceInfo"] = {
        [RFPROP_ODATA_CONTEXT] = "/redfish/v1/$metadata#HwDeviceInfo.HwDeviceInfo",
        [RFPROP_ODATA_ID] = "/redfish/v1/Managers/%s/SPService/DeviceInfo",
        [RFPROP_ODATA_TYPE] = "#HwDeviceInfo.v1_0_0.HwDeviceInfo",
        [RFPROP_ID] = "DeviceInfo",
        [RFPROP_NAME] = "Device Info"
    },
    ["SPRAID"] = {
        [RFPROP_ODATA_CONTEXT] = "/redfish/v1/$metadata#HwSPRAID.HwSPRAID",
        [RFPROP_ODATA_ID] = "/redfish/v1/Managers/%s/SPService/SPRAID",
        [RFPROP_ODATA_TYPE] = "#HwSPRAID.v1_0_0.HwSPRAID",
        [RFPROP_ID] = nil,  -- 由uri.memberid替代
        [RFPROP_NAME] = "SP RAID"
    },
    ["SPRAIDCurrentConfigurations"] = {
        [RFPROP_ODATA_CONTEXT] = "/redfish/v1/$metadata#HwSPRAIDCurrentConfigurations.HwSPRAIDCurrentConfigurations",
        [RFPROP_ODATA_ID] = "/redfish/v1/Managers/%s/SPService/SPRAIDCurrentConfigurations",
        [RFPROP_ODATA_TYPE] = "#HwSPRAIDCurrentConfigurations.v1_0_0.HwSPRAIDCurrentConfigurations",
        [RFPROP_ID] = nil,  -- 由uri.memberid替代
        [RFPROP_NAME] = "SP RAID Current Configurations"
    },
    ["SPCfg"] = {
        [RFPROP_ODATA_CONTEXT] = "/redfish/v1/$metadata#HwSPCfg.HwSPCfg",
        [RFPROP_ODATA_ID] = "/redfish/v1/Managers/%s/SPService/SPCfg",
        [RFPROP_ODATA_TYPE] = "#HwSPCfg.v1_0_0.HwSPCfg",
        [RFPROP_ID] = nil,  -- 由uri.memberid替代
        [RFPROP_NAME] = "SP Config"
    },
    ["SPOSInstallPara"] = {
        [RFPROP_ODATA_CONTEXT] = "/redfish/v1/$metadata#HwSPOSInstallPara.HwSPOSInstallPara",
        [RFPROP_ODATA_ID] = "/redfish/v1/Managers/%s/SPService/SPOSInstallPara",
        [RFPROP_ODATA_TYPE] = "#HwSPOSInstallPara.v1_0_0.HwSPOSInstallPara",
        [RFPROP_ID] = nil,  -- 由uri.memberid替代
        [RFPROP_NAME] = "SP OS Install Parameter"
    },
    ["SPDiagnose"] = {
        [RFPROP_ODATA_CONTEXT] = "/redfish/v1/$metadata#HwSPDiagnose.HwSPDiagnose",
        [RFPROP_ODATA_ID] = "/redfish/v1/Managers/%s/SPService/SPDiagnose",
        [RFPROP_ODATA_TYPE] = "#HwSPDiagnose.v1_0_0.HwSPDiagnose",
        [RFPROP_ID] = nil,  -- 由uri.memberid替代
        [RFPROP_NAME] = "SP Diagnose"
    },
    ["SPDriveErase"] = {
        [RFPROP_ODATA_CONTEXT] = "/redfish/v1/$metadata#HwSPDriveErase.HwSPDriveErase",
        [RFPROP_ODATA_ID] = "/redfish/v1/Managers/%s/SPService/SPDriveErase",
        [RFPROP_ODATA_TYPE] = "#HwSPDriveErase.v1_0_0.HwSPDriveErase",
        [RFPROP_ID] = nil,  -- 由uri.memberid替代
        [RFPROP_NAME] = "SP Drive Erase"
    },
    ["SPResult"] = {
        [RFPROP_ODATA_CONTEXT] = "/redfish/v1/$metadata#hwSPResult.HwSPResult",
        [RFPROP_ODATA_ID] = "/redfish/v1/Managers/%s/SPService/SPResult",
        [RFPROP_ODATA_TYPE] = "#hwSPResult.v1_0_0.HwSPResult",
        [RFPROP_ID] = nil,  -- 由uri.memberid替代
        [RFPROP_NAME] = "SP Result"
    },
    ["SystemErase"] = {
        [RFPROP_ODATA_CONTEXT] = "/redfish/v1/$metadata#HwSystemErase.HwSystemErase",
        [RFPROP_ODATA_ID] = "/redfish/v1/Managers/%s/SPService/SystemErase",
        [RFPROP_ODATA_TYPE] = "#HwSystemErase.v1_0_0.HwSystemErase",
        [RFPROP_ID] = nil,  -- 由uri.memberid替代
        [RFPROP_NAME] = "SP System Erase"
    },
    ["Plugin"] = {
        [RFPROP_ODATA_CONTEXT] = "/redfish/v1/$metadata#SPPlugin.SPPlugin",
        [RFPROP_ODATA_ID] = "/redfish/v1/Managers/%s/SPService/Plugins",
        [RFPROP_ODATA_TYPE] = "#SPPlugin.v1_0_0.SPPlugin",
        [RFPROP_ID] = nil,  -- 由uri.memberid替代
        [RFPROP_NAME] = "SP Plugin"
    }
}

local m = {}

-- 获取 UMS 对象
local function get_ums_object()
    local is_ok, ums_obj

    is_ok, ums_obj = pcall(mdb.get_object, bus, UMS_PATH, UMS_INTERFACE)
    if not is_ok then
        log:error('Get ums object failed, err(%s)', ums_obj)
        return nil
    end

    return ums_obj
end

-- 用pcall调用GetFileList，异常情况下返回空table
local function get_file_list(path_id)
    local is_ok, ums_obj, rsp

    ums_obj = get_ums_object()
    if not ums_obj then
        log:error('Get ums obj failed, path_id(%s)', path_id)
        return {}
    end

    is_ok, rsp = ums_obj.pcall:GetFileList(context.new(), path_id)
    if not is_ok then
        log:error('Get file list failed, path_id(%s), err(%s)', path_id, rsp)
        return {}
    end

    return rsp.Files
end

m.get_file_list = get_file_list

local function get_spservice_one_filelist(path_id)
    local item_array = cjson.json_object_new_array()
    local filenames = get_file_list(path_id)

    for _, filename in ipairs(filenames) do
        local item_obj = cjson.json_object_new_object()
        item_obj.Type = FILE_TYPE_TABLE[path_id]
        item_obj.Name = filename

        item_array[#item_array + 1] = item_obj
    end

    return item_array
end

function m.get_spservice_filelist()
    local filelist = cjson.json_object_new_array()

    for _, item in ipairs(get_spservice_one_filelist(UMS_TRANS_FILE_ID_SP_OSINSTALL)) do
        filelist[#filelist + 1] = item
    end

    for _, item in ipairs(get_spservice_one_filelist(UMS_TRANS_FILE_ID_SP_RAID)) do
        filelist[#filelist + 1] = item
    end

    for _, item in ipairs(get_spservice_one_filelist(UMS_TRANS_FILE_ID_SP_FWUPDATE)) do
        filelist[#filelist + 1] = item
    end

    for _, item in ipairs(get_spservice_one_filelist(UMS_TRANS_FILE_ID_SP_CFG)) do
        filelist[#filelist + 1] = item
    end

    for _, item in ipairs(get_spservice_one_filelist(UMS_TRANS_FILE_ID_SP_SYSTEM_ERASE)) do
        filelist[#filelist + 1] = item
    end

    return filelist
end

function m.get_spfwupdate_messages(message_id, message_args)
    local message_array = cjson.json_object_new_array()

    if not message_id or #message_id == 0 then
        return message_array
    end

    local error_message = redfish_utils.create_error_message(message_id, nil, message_args)
    message_array[#message_array + 1] = error_message
    return message_array
end

local function system_erase_result_str_to_bool(json_obj)
    if json_obj and json_obj.SystemErase and json_obj.SystemErase.SystemEraseFinished then
        json_obj.SystemErase.SystemEraseFinished = (
            json_obj.SystemErase.SystemEraseFinished == "true" and true or false)
    end
end

local function get_common_rsp(interface_name, uri, content)
    local ret = cjson.json_object_new_object()

    local odata_info = ODATA_INFO_TABLE[interface_name]
    if not odata_info then
        return ret
    end

    local managerid = uri.managerid
    local memberid = uri.memberid

    local odata_context = odata_info[RFPROP_ODATA_CONTEXT]
    local odata_id = string.format(odata_info[RFPROP_ODATA_ID], managerid)
    if memberid then
        odata_id = odata_id .. "/" .. memberid
    end

    local odata_type = odata_info[RFPROP_ODATA_TYPE]
    local id = odata_info[RFPROP_ID] or memberid
    local name = odata_info[RFPROP_NAME]

    ret[RFPROP_ODATA_CONTEXT] = odata_context
    ret[RFPROP_ODATA_ID] = odata_id
    ret[RFPROP_ODATA_TYPE] = odata_type
    ret[RFPROP_ID] = id
    ret[RFPROP_NAME] = name

    if not content or #content == 0 then
        return ret
    end

    local is_ok, json_obj = pcall(cjson.json_object_ordered_decode, content)
    if not is_ok then
        log:error("Decode content failed, uri:%s, err(%s)", uri, json_obj)
        return ret
    end

    if interface_name == "SystemErase" then
        system_erase_result_str_to_bool(json_obj)
    end

    for k, v in pairs(json_obj) do
        ret[k] = v
    end

    return ret
end

function m.is_deviceinfo_valid(content)
    if not content then
        error(custom_messages.UMSIsExclusivelyUsed())
    end
    return true
end

function m.get_device_info_rsp(uri, content)
    return get_common_rsp("DeviceInfo", uri, content)
end

function m.get_raid_rsp(uri, content)
    return get_common_rsp("SPRAID", uri, content)
end

function m.get_raid_current_configurations_rsp(uri, content)
    return get_common_rsp("SPRAIDCurrentConfigurations", uri, content)
end

function m.get_cfg_rsp(uri, content)
    return get_common_rsp("SPCfg", uri, content)
end

function m.get_osinstall_rsp(uri, content)
    uri.memberid = uri.memberid or "1"
    return get_common_rsp("SPOSInstallPara", uri, content)
end

function m.get_diagnose_rsp(uri, content)
    return get_common_rsp("SPDiagnose", uri, content)
end

function m.get_driveerase_rsp(uri, content)
    return get_common_rsp("SPDriveErase", uri, content)
end

function m.get_result_rsp(uri, content)
    return get_common_rsp("SPResult", uri, content)
end

function m.get_system_erase_rsp(uri, content)
    return get_common_rsp("SystemErase", uri, content)
end

function m.get_plugin_rsp(uri, content)
    local rsp_body = get_common_rsp("Plugin", uri, content)
    rsp_body.Id = rsp_body.Name
    return rsp_body
end

function m.check_osinstall_file_exist()
    -- 先检查 pme 目录下的 sposinstallpara 文件是否存在
    local res = vos.get_file_accessible(SP_OSINSTALL_PARA_PATH)
    if res then
        return true
    end

    local is_ok, ums_obj, rsp

    ums_obj = get_ums_object()
    if not ums_obj then
        log:error('Get ums obj failed.')
        return false
    end

    -- 再检查 ums 下 osinstall.json 文件是否存在
    is_ok, rsp = ums_obj.pcall:CheckFileExist(context.new(), UMS_TRANS_FILE_ID_SP_OSINSTALL, UMS_OSINSTALL_FILE_NAME)
    if not is_ok then
        log:error('Check file exist failed, file(%s), err(%s)', UMS_OSINSTALL_FILE_NAME, rsp)
        return false
    end

    return rsp.Result
end

function m.get_cur_cfg_members(m_id)
    -- 先检查目录是否存在
    local res = vos.get_file_accessible(SP_RAID_CUR_CFG_PATH)
    if not res then
        return {0, {}}
    end
    local is_ok, ums_obj, rsp
    ums_obj = get_ums_object()
    if not ums_obj then
        log:error('Get ums obj failed.')
        return {0, {}}
    end

    is_ok, rsp = ums_obj.pcall:GetFileList(context.new(), UMS_TRANS_FILE_ID_SP_RAID_EXPORT)
    if not is_ok then
        log:error("Get file list failed")
        return {0, {}}
    end
    local cur_cfg_members = {}
    for _, v in pairs(rsp.Files) do
        local file_name = v:gsub("_current_cfg%.json$", "")
        cur_cfg_members[#cur_cfg_members + 1] =
          {['@odata.id'] = '/redfish/v1/Managers/' .. m_id .. '/SPService/SPRAIDCurrentConfigurations/' ..  file_name}
    end
    return {#cur_cfg_members, cur_cfg_members}
end

return m
