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

-- Desription: 升级管理
local m = {}

function m.check_system_id_valid(system_id)
    if system_id == nil then
        -- 用户不传SystemId时，入参为nil
        return true
    end
    local ok, ret = pcall(mdb_service.is_valid_path, bus, '/bmc/kepler/Systems/' .. system_id)
    if not ok or not ret.Result then
        log:error('Invalid system id (%s)', system_id)
        error(custom_messages.ResourceNotFound('SystemId', system_id))
    end
    return true
end

function m.get_expand_parameters(is_clear_conf, active_mode, upgrade_type)
    local ret_paras = {}

    if active_mode == 'ResetBMC' and is_clear_conf then
        -- BMC生效分离时，不会立即复位，不能做恢复出厂设置，与V2策略一致
        error(custom_messages.UpgradeParameterConflict('ActiveMode', active_mode, 'RestoreFactorySettings'))
    elseif is_clear_conf then
        ret_paras.RestoreFactorySettings = 'true'
    elseif active_mode then
        ret_paras.ActiveMode = active_mode
    elseif not active_mode then
        ret_paras.ActiveMode = "Immediately"
    end

    ret_paras.UpgradeType = upgrade_type

    return ret_paras
end

function m.protocal_check(uri, protocol, is_permitted, raise_error)
    if string.sub(uri, #uri - 3) ~= '.hpm' then
        return raise_error and
            error(base_messages.ActionParameterValueFormatError('******', 'ImageURI',
                'UpdateService.SimpleUpdate'))
    end

    -- 本地升级不需要传输协议字段,截取前4字节判断是否本地升级cd
    if string.sub(uri, 1, 4) == '/tmp' then
        local ok, ret = pcall(vos.get_file_accessible, uri)
        if not (ok and ret) then
            return raise_error and error(custom_messages.FileNotExist())
        end
        if not is_permitted then
            -- 近端文件权限校验不通过
            return raise_error and error(custom_messages.NoPrivilegeToOperateSpecifiedFile())
        end
        return true
    end

    if not protocol then
        return raise_error and
            error(
                base_messages.ActionParameterMissing('UpdateService.SimpleUpdate',
                    'TransferProtocol'))
    end

    if #uri <= #protocol then
        return false
    end
    local protocol_head = string.sub(uri, 1, #protocol)

    if string.upper(protocol_head) ~= protocol then
        return raise_error and error(custom_messages.FileTransferProtocolMismatch())
    end

    return true
end

function m.parallel_update_protocol_check(packages, results)
    for index, package in ipairs(packages) do
        if not m.protocal_check(package.ImageURI, package.TransferProtocol, results[index], true) then
            return false
        end
    end
    return true
end

function m.get_boards(paths)
    local boards = {}
    for _, v in ipairs(paths) do
        local item = {}
        local obj = mdb.get_object(bus, v, 'bmc.kepler.Object.Properties')
        item['Id'] = obj.ObjectIdentifier[4]
        obj = mdb.get_object(bus, v, 'bmc.kepler.Systems.Board')
        item['NodeId'] = obj.NodeId
        obj = mdb.get_object(bus, v, 'bmc.kepler.Systems.Board.Unit')
        item['Type'] = obj.Type == 'NICCard' and 'NIC' or obj.Type
        boards[#boards + 1] = item -- 1表示将元素添加到表的最后
    end
    return boards
end

function m.get_boards_and_pcie_cards(board_paths, pcie_card_paths)
    local boards_and_pcie_cards = {}
    for _, v in ipairs(board_paths) do
        local item = {}
        local obj = mdb.get_object(bus, v, 'bmc.kepler.Object.Properties')
        item['Id'] = obj.ObjectIdentifier[4]
        obj = mdb.get_object(bus, v, 'bmc.kepler.Systems.Board')
        item['NodeId'] = obj.NodeId
        obj = mdb.get_object(bus, v, 'bmc.kepler.Systems.Board.Unit')
        item['Type'] = obj.Type == 'NICCard' and 'NIC' or obj.Type
        boards_and_pcie_cards[#boards_and_pcie_cards + 1] = item -- 1表示将元素添加到表的最后
    end
    for _, v in ipairs(pcie_card_paths) do
        local item = {}
        if not string.match(v, '(.*)NPUCard') then
            local obj = mdb.get_object(bus, v, 'bmc.kepler.Object.Properties')
            item['Id'] = obj.ObjectIdentifier[4]
            obj = mdb.get_object(bus, v, 'bmc.kepler.Systems.PCIeDevices.PCIeCard')
            item['NodeId'] = obj.NodeID
            item['Type'] = "PCIeCard"
            boards_and_pcie_cards[#boards_and_pcie_cards + 1] = item -- 1表示将元素添加到表的最后
        end
    end
    return boards_and_pcie_cards
end

local function get_component_info(systemId)
    local component_info = cjson.json_object_new_array()
    local bios_obj = mdb.get_object(bus, '/bmc/kepler/Systems/' .. systemId .. '/Bios', 'bmc.kepler.Systems.Bios')
    local res = bios_obj:ExportBiosSetup(require 'mc.context'.new('Redfish', 'NA', 'NA'),
        'ComponentVersion')
    local obj = cjson.json_object_ordered_decode(res.Result)
    local order_keys = cjson.json_object_get_keys(obj.ComponentInfo)
    for _, index in pairs(order_keys) do
        component_info[index] = obj.ComponentInfo[index]
    end
    return component_info
end

local function get_bios_version(systemId)
    local version = {}
    local bios_obj = mdb.get_object(bus, '/bmc/kepler/Systems/' .. systemId .. '/Bios', 'bmc.kepler.Systems.Bios')
    local bios_version = bios_obj.Version
    local basic_version, patch_version = string.match(bios_version, '(.+).(HP.+)')
    if not basic_version or not patch_version then
        basic_version = bios_version
        patch_version = ''
    end
    version.BaseVersion = basic_version
    version.PatchVersion = patch_version
    return version
end

local function device_locator_change_to_system_id(name)
    local lower_name = string.lower(name)
    if lower_name == "bios" then
        return 1
    else
        return tonumber(string.match(lower_name, "bios(%d+)"))
    end
end

function m.get_oem_firmware_inventory_info(fw_info)
    local id, active_mode = fw_info.Id, fw_info.ActiveMode
    local data = cjson.json_object_new_object()
    data.PositionId = fw_info.Location
    data.Manufacturer = fw_info.Manufacturer
    if string.match(id, '^Bios') then
        local systemId = device_locator_change_to_system_id(id)
        local ok, component_info = pcall(function ()
            return get_component_info(systemId)
        end)
        if not ok then
            component_info = cjson.json_object_new_array()
        end
        data.ComponentInfo = component_info
        local ok, version = pcall(function ()
            return get_bios_version(systemId)
        end)
        if not ok then
            data.BaseVersion = ''
            data.PatchVersion = ''
        else
            data.BaseVersion = version.BaseVersion
            data.PatchVersion = version.PatchVersion
        end
    end
    local id_table = {}
    for w in string.gmatch(id, '[^_]+') do
        table.insert(id_table, w)
    end
    if id_table[1] == 'BCU' and id_table[2] == 'CPLD1' then
        data.SupportHotUpgrade = false
    elseif id_table[1] == 'BCU' and id_table[2] == 'CPLD2' then
        data.SupportHotUpgrade = true
    end
    if id_table[1] == 'EXU' and id_table[2] == 'CPLD1' then
        data.SupportHotUpgrade = false
    elseif id_table[1] == 'EXU' and id_table[2] == 'CPLD2' then
        data.SupportHotUpgrade = true
    end
    if id == "ActiveBMC" or string.find(id, "SR_") then
        data.ActiveMode = active_mode
        -- ActiveModeSupported在V2表示此机型是否支持升级生效分离
        -- 在V3上默认所有机型支持，此值恒为true
        data.ActiveModeSupported = true
    else
        data.ActiveMode = cjson.null
        data.ActiveModeSupported = cjson.null
    end
    return data
end

function m.get_packages(params)
    local res, package, options = {}, {}, nil
    for _, param in pairs(params) do
        options = {}
        options['TransferProtocol'] = param.TransferProtocol
        options['SystemId'] = param.SystemId
        package = {param.ImageURI, options}
        res[#res + 1] = package
    end
    return res
end

function m.get_active_policy(policy)
    if not policy or #policy == 0 then
        return {}
    end
    local res
    -- 如果有重复值也当作一个值处理
    -- 同时传入两个策略则直接取值ResetAC
    for _, value in pairs(policy) do
        if not res then
            res = value
        elseif res ~= policy then
            res = 'ResetAC'
            break
        end
    end

    return {ActivationControl = res}
end

function m.get_take_effect(effects)
    local res = cjson.json_object_new_array()
    local object
    for _, value in pairs(effects) do
        object = cjson.json_object_new_object()
        object['FirmwareType'] = value.Firmware
        res[#res + 1] = object
    end
    return res
end

function m.modify_default_version(id, version, releasedate)
    local res = version and version .. releasedate or ''
    if id == 'BackupBMCSDK' or id == 'AvailableBMCSDK' or id == 'ActiveBMCSDK' then
        if version == 'N/A' or version == '' then
            res = cjson.null
        end
    end
    return res
end

function m.get_firmware_integrity_certificate(contents)
    local res = cjson.json_object_new_array()
    local object

    for _, content in pairs(contents) do
        object = cjson.json_object_new_object()
        object["CertificateString"] = content
        res[#res + 1] = object
    end

    return res
end

function m.get_firmware_integrity_crl(contents)
    local res = cjson.json_object_new_array()
    local object
    for _, content_tb in pairs(contents) do
        for _, content in pairs(content_tb) do
            object = cjson.json_object_new_object()
            object["CRLString"] = content
            res[#res + 1] = object
        end
    end

    return res
end

return m
