-- 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 cjson = require 'cjson'

local BOARD_MAP <const> = {
    BCU = 'CpuBoard',
    CLU = 'FanBoard',
    EXU = 'ExpBoard',
    SEU = 'DiskBP',
    IEU = 'Riser',
    NIC = 'NIC',
    VRD = 'Power',
    DPU = 'SDI',
    SDI = 'SDI',
    PSR = 'Product',
    L1 = 'ExpBoard',
    PEU = 'PeuBoard',
    LinkBoard = 'LCN',
    ACU = 'NpuBoard',
    Drive = 'Drive',
    Raid = 'RAID'
}

local FIRMWARE_INVENTORY_PATH <const> = '/bmc/kepler/UpdateService/FirmwareInventory'
local FIRMWARE_INFO_INTERFACE <const> = 'bmc.kepler.UpdateService.FirmwareInfo'

local m = {}

function m.get_updating_task_uri(task_obj_list)
    for _, obj in pairs(task_obj_list) do
        if obj.State == 'New' or obj.State == 'Starting' or obj.State == 'Running' then
            return {true, '/UI/Rest/BMCSettings/UpdateService/UpdateProgress/' .. obj.Id}
        end
    end
    return {false, nil}
end

function m.get_active_task_uri(task_obj_list)
    if #task_obj_list ~= 0 then
        return '/UI/Rest/BMCSettings/UpdateService/FirmwareActive/ActiveProgress'
    end
    return nil
end

local type_to_priority = {
    ['CpuBoard_VRD'] = 1,
    ['PSU'] = 2,
    ['PowerConverter'] = 3
}
local function get_power_component_priority(name)
    local match
    match = string.match(name, "^CpuBoard(%d+) VRD$")
    if match then
        return type_to_priority['CpuBoard_VRD']
    end

    match = string.match(name, "^PSU(%d+)$")
    if match then
        return type_to_priority['PSU']
    end

    match = string.match(name, "^PowerConverter(%d+)$")
    if match then
        return type_to_priority['PowerConverter']
    end

    return nil
end

local function compare_power_components(a, b)
    local prior_a = get_power_component_priority(a.Name)
    local prior_b = get_power_component_priority(b.Name)

    if prior_a and prior_b and prior_a ~= prior_b then
        return prior_a < prior_b
    end

    local num_a = tonumber(string.match(a.Name, "%d+"))
    local num_b = tonumber(string.match(b.Name, "%d+"))
    if num_a and num_b then
        return num_a < num_b
    elseif num_a then
        return true
    elseif num_b then
        return false
    else
        return a.Name < b.Name
    end
end

local function compare_cpu_board_components(a, b)
    local tmp1, type1 = string.match(a.Name, 'CpuBoard(%d+)(%s%w+)')
    local tmp2, type2 = string.match(b.Name, 'CpuBoard(%d+)(%s%w+)')
    if not tmp1 and not tmp2 then
        return a.Name < b.Name
    elseif not tmp1 then
        return true
    elseif not tmp2 then
        return false
    elseif tonumber(tmp1) == tonumber(tmp2) then
        return type1 < type2
    else
        return tonumber(tmp1) < tonumber(tmp2)
    end
end

local function append_upgrade_resource(fw_list, version_list, index)
    for _, objs in pairs(fw_list) do
        table.sort(objs, function (a, b)
            if a.Type == 'Power' and b.Type == 'Power' then
                return compare_power_components(a, b)
            end
            if a.Type == 'CpuBoard' and b.Type == 'CpuBoard' then
                return compare_cpu_board_components(a, b)
            end
            return a.Name < b.Name
        end)
        for _, v in pairs(objs) do
            v.Id = index
            index = index + 1
            version_list[index] = v
        end
    end
end

local function insert_tmp_resource(t, name, type, version)
    table.insert(t[type], {
            Name = name,
            Type = tostring(type),
            Version = version
        }
    )
end

function m.get_firmware_list_ex(fw_list)
    -- 若 ProcessingFlow 调用 GetFirmwareList 未返回数据，则回退为遍历 FirmwareInfo 对象并筛选 Id/Name/Version
    if not fw_list then
        local ok, fw_objs = pcall(mdb.get_sub_objects, bus, FIRMWARE_INVENTORY_PATH, FIRMWARE_INFO_INTERFACE)
        if not ok or not fw_objs then
            return {}
        end

        local res = {}
        for _, obj in pairs(fw_objs) do
            res[#res + 1] = {
                Id = obj.Id,
                Name = obj.Name,
                Version = obj.Version
            }
        end
        return res
    end

    local firmware_info_map = {}
    for _, item in pairs(fw_list) do
        if item.FirmwareId and item.FirmwareInfo then
            firmware_info_map[item.FirmwareId] = cjson.decode(item.FirmwareInfo.Value)
        end
    end

    return firmware_info_map
end

function m.get_upgrade_resource(active_version, fw_obj_list)
    local version_list = {
        { Id = 0, Name = 'BMC',  Type = 'firmware', Version = active_version }
    }
    if not fw_obj_list then
        return version_list
    end

    local t_all = {}
    for _, value in pairs(BOARD_MAP) do
        t_all[value] = {}
    end
    for _, obj in pairs(fw_obj_list) do
        if string.match(obj.FirmwareType, "NIC_FIRMWARE") then
            insert_tmp_resource(t_all, obj.Name, 'NIC', obj.Version)
            goto continue
        end
        if string.match(obj.Name, "SP686C") then
            insert_tmp_resource(t_all, obj.Name, 'RAID', obj.Version)
            goto continue
        end
        if string.match(obj.Id, "HDDPlaneDisk") then
            insert_tmp_resource(t_all, string.match(obj.Id, "Disk%d+"), 'Drive', obj.Version)
            goto continue
        end

        if string.match(obj.Name, "Bios") then
            insert_tmp_resource(t_all, string.upper(obj.Id), 'CpuBoard', obj.Version)
            goto continue
        end
        if string.match(obj.Name, "Cca") then
            insert_tmp_resource(t_all, string.upper(obj.Id), 'CpuBoard', obj.Version)
            goto continue
        end
        -- PSU Name格式与其他不一样，需单独处理
        if string.match(obj.Name, "PSU") or string.match(obj.Name, "PowerConverter") then
            insert_tmp_resource(t_all, obj.Name, 'Power', obj.Version)
            goto continue
        end
        if string.match(obj.Name, "Product") then
            insert_tmp_resource(t_all, 'PSR', 'Product', obj.Version)
            goto continue
        end

        if string.match(obj.Name, "LinkBoard") then
            insert_tmp_resource(t_all, obj.Name, 'LCN', obj.Version)
            goto continue
        end

        -- 处理BMC暂未获取Retimer信息的场景,web页面无需显示
        if string.match(obj.Name, "Retimer") and obj.Version == "" then
            goto continue
        end

        if string.match(obj.Name, "Krun") then
            insert_tmp_resource(t_all, string.upper(obj.Id), 'CpuBoard', obj.Version)
            goto continue
        end

        if string.match(obj.Id, "NPU") then
            insert_tmp_resource(t_all, obj.Name, 'NpuBoard', obj.Version)
            goto continue
        end

        local split = utils.split(obj.Id, '_')
        -- 使用Id来获取对应归属单板，split后单板简称可能在第一个元素或第二个元素
        local board_name = BOARD_MAP[split[1]] or BOARD_MAP[split[2]]
        if board_name then
            if board_name == 'NpuBoard' and not string.match(obj.Id, "SR_ACU") then
                goto continue
            end
            insert_tmp_resource(t_all, obj.Name, board_name, obj.Version)
        end
        ::continue::
    end
    append_upgrade_resource(t_all, version_list, #version_list)
    return version_list
end

return m
