-- 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 m = {}

-- @function 判断是否有group_domain，并传递正确的group_name和group_folder
-- @param    GroupName    请求体中传入的GroupName
-- @param    GroupFolder  请求体中传入的GroupFolder
-- @param    GroupDomain  请求体中传入的GroupDomain
-- @return   flag         GroupName、GroupFolder、GroupDomain校验结果
-- @return   group_name   拆解比对成功后实际的group_name
-- @return   group_folder 拆解比对成功后实际的group_folder
local function get_group_name_folder_domain(GroupName, GroupFolder, GroupDomain)
    local group_name = ''
    local group_folder = ''

    if not GroupDomain then
        group_name = GroupName
        group_folder = GroupFolder
    else
        -- 有domain，domain格式 CN=***,OU=***,DC=***...
        local cn_pos = string.find(GroupDomain, 'CN=', 1)
        local pos = string.find(GroupDomain, ',', 1)
        local name_str
        local remain_str
        if cn_pos == nil then
            name_str = GroupName or ''
            remain_str = GroupDomain
        else
            if pos == nil then
                pos = string.len(GroupDomain)
                remain_str = ''
            else
                -- 此处截取+1是为了跳过","
                remain_str = string.sub(GroupDomain, pos + 1, string.len(GroupDomain))
                pos = pos - 1
            end
            -- 此处截取+3是为了跳过"CN="字符串
            name_str = string.sub(GroupDomain, cn_pos + 3, pos)
        end

        -- 此时remain_str期望为GroupDomain去掉"CN=***,"的部分
        local dc_pos = string.find(remain_str, 'DC=', 1)
        local folder_str
        -- remain_str为"***"的格式
        if not dc_pos then
            folder_str = remain_str
        -- remain_str为"DC=***"或",DC=***"的格式
        elseif dc_pos == 1 or dc_pos == 2 then
            folder_str = ''
        -- remain_str为"***,DC=***"的格式
        else
            -- 此处截取-2是为了跳过"DC"和前面的","
            folder_str = string.sub(remain_str, 1, dc_pos - 2)
        end

        -- 若有group_name则进行匹配校验
        if not GroupName then
            group_name = name_str
        else
            group_name = GroupName
        end

        if not GroupFolder then
            group_folder = folder_str
        else
            group_folder = GroupFolder
        end
    end

    return group_name, group_folder
end

-- @function 根据操作类型获取GroupName, GroupFolder参数
-- @param    op_types   各组的操作类型
-- @param    new_groups 各组的输入数据
-- @return   groups     待设置或新增的各组属性
function m.get_param_with_op_type(op_types, origin_groups, new_groups)
    local groups = {}
    for i = 1, 5 do
        local group_info = {}
        -- 无操作和删除操作，不考虑输入值
        if op_types[i] == 'none' or op_types[i] == 'delete' then
            goto continue
        end
        -- 实际输入值和校验后的值不一致，代表校验有失败
        if origin_groups[i].GroupName ~= new_groups[i].GroupName or
            origin_groups[i].GroupFolder ~= new_groups[i].GroupFolder then
            log:error("Group%d GroupName or GroupFolder check with GroupDomain failed", i - 1)
            group_info.flag = false
        else
            local input_group_name = (new_groups[i] or {}).GroupName
            local input_group_folder = (new_groups[i] or {}).GroupFolder
            local input_group_domain = (new_groups[i] or {}).GroupDomain

            local group_name, group_folder =
                get_group_name_folder_domain(input_group_name, input_group_folder, input_group_domain)
            if op_types[i] == 'add' then
                group_info.group_name = group_name
                -- 新增组，group_folder为空则转换为空字符串，避免nil无法调用rpc
                group_info.group_folder = group_folder or ''
            elseif op_types[i] == 'modify' then
                group_info.group_name = group_name
                group_info.group_folder = group_folder
            end
            group_info.flag = true
        end
        ::continue::
        groups[i] = group_info
    end

    return groups
end

-- @function 根据操作类型获取GroupLoginInterface参数
-- @param    op_types   各组的操作类型
-- @param    new_groups 各组的输入数据
function m.get_login_interface_with_op_type(op_types, new_groups)
    -- 数据为空时如果新建组，登录接口为默认登录接口
    local login_interface_tab = {}
    local def_intf = {
        "Web",
        "SSH",
        "Redfish"
    }
    for i = 1, 5 do
        local input_login_intf = (new_groups[i] or {}).GroupLoginInterface
        -- 字符串null会被映射器替换成cjson.null，这里使用nil会终止遍历
        if not input_login_intf then
            login_interface_tab[i] = op_types[i] == 'add' and def_intf or null
        else
            local exist = {}
            for _, value in pairs(input_login_intf) do
                exist[value] = true
            end

            local login_intf = {}
            for k, _ in pairs(exist) do
                table.insert(login_intf, k)
            end

            login_interface_tab[i] = login_intf
        end
    end
    return login_interface_tab
end

-- @function 根据操作类型获取GroupLoginRule参数
-- @param    op_types   各组的操作类型
-- @param    new_groups 各组的输入数据
function m.get_login_rule_with_op_type(op_types, new_groups)
    -- 如果属性没写的话,框架是返回null
    local login_rule_tab = cjson.json_object_new_array()
    for i = 1, 5 do
        local login_rule = (new_groups[i] or {}).GroupLoginRule
        if not login_rule then
            login_rule_tab[i] = op_types[i] == 'add' and cjson.json_object_new_array() or null
        else
            login_rule_tab[i] = login_rule
        end
    end

    return login_rule_tab
end

local role_type_map = {
    CommonUser = 2,
    Operator = 3,
    Administrator = 4,
    CustomRole1 = 5,
    CustomRole2 = 6,
    CustomRole3 = 7,
    CustomRole4 = 8,
    CustomRole5 = 9,
    CustomRole6 = 10,
    CustomRole7 = 11,
    CustomRole8 = 12,
    CustomRole9 = 13,
    CustomRole10 = 14,
    CustomRole11 = 15,
    CustomRole12 = 16,
    CustomRole13 = 17,
    CustomRole14 = 18,
    CustomRole15 = 19,
    CustomRole16 = 20
}

local role_id_map = {
    [2] = 'CommonUser',
    [3] = 'Operator',
    [4] = 'Administrator',
    [5] = 'CustomRole1',
    [6] = 'CustomRole2',
    [7] = 'CustomRole3',
    [8] = 'CustomRole4',
    [9]  = 'CustomRole5',
    [10] = 'CustomRole6',
    [11] = 'CustomRole7',
    [12] = 'CustomRole8',
    [13] = 'CustomRole9',
    [14] = 'CustomRole10',
    [15] = 'CustomRole11',
    [16] = 'CustomRole12',
    [17] = 'CustomRole13',
    [18] = 'CustomRole14',
    [19] = 'CustomRole15',
    [20] = 'CustomRole16'
}

local REMOTE_GROUP_INFT<const> = 'bmc.kepler.AccountService.RemoteGroup'

function m.get_ldap_remote_groups(controller_id)
    local remote_group_path = '/bmc/kepler/AccountService/RemoteGroups/LDAP' .. controller_id .. '_'
    local remote_groups = cjson.json_object_new_array()

    for i = 1, 5 do
        local remote_group = cjson.json_object_new_object()
        local ok, group_obj = pcall(mdb.get_object, bus, remote_group_path .. i, REMOTE_GROUP_INFT)
        if not ok then
            remote_group.LocalRole = null
            remote_group.RemoteGroup = null
            remote_groups[i] = remote_group
        else
            remote_group.LocalRole = role_id_map[group_obj.UserRoleId]
            remote_group.RemoteGroup = (group_obj.Folder and group_obj.Folder ~= '') and
                'CN=' .. group_obj.Name .. ',' .. group_obj.Folder or
                'CN=' .. group_obj.Name
            remote_groups[i] = remote_group
        end
    end
    return remote_groups
end

function m.get_domain_from_basedn(basedn)
    local domain_tab = {}
    for str in string.gmatch(basedn, '[Dd][Cc]%s*=%s*([^,]+)') do
        table.insert(domain_tab, str)
    end
    return table.concat(domain_tab, '.')
end

-- @function 根据id获取远程用户组的信息
function m.get_remote_groups_info_with_id(remote_groups)
    local groups_info = {{}, {}, {}, {}, {}}
    local remote_group
    for id, group in pairs(groups_info) do
        remote_group = (remote_groups[id] or {}).RemoteGroup
        group.group_name = remote_group and remote_group:match("CN=([^,]*)")
        if group.group_name == nil or group.group_name == '' then
            group.folder = remote_group
        else
            local pos, len = remote_group:find('CN=' .. group.group_name)
            group.folder = pos == 1 and remote_group:sub(1 + len + 1) or
                remote_group:sub(1, #remote_group - (len + 1))
        end
        if (remote_groups[id] or {}).LocalRole == nil then
            group.role_id = null
        else
            group.role_id = role_type_map[remote_groups[id].LocalRole]
        end
    end
    return groups_info
end

function m.get_remote_group_opr(cur_groups, remote_role_mapping)
    local is_group_exist = {false, false, false, false, false}
    for _, group in ipairs(cur_groups) do
        -- 找到控制器1的远程用户组
        local group_id = string.match(group, '.*/LDAP1_(%d+)')
        is_group_exist[tonumber(group_id)] = true
    end

    local op_type = {'modify', 'modify', 'modify', 'modify', 'modify'}
    for i = 1, 5 do
        -- 使用ReqBodyOriginal原始输入来判断，避免校验不通过造成误判断
        local remote_group = (remote_role_mapping[i] or {}).RemoteGroup
        local local_role = (remote_role_mapping[i] or {}).LocalRole
        local group_name = remote_group and remote_group:match("CN=([^,]*)")

        -- Ldap组不存在且不新建Ldap组时操作类型为none
        if not is_group_exist[i] and (group_name == '' or group_name == nil) then
            op_type[i] ='none'
        elseif not is_group_exist[i] and group_name ~= nil and group_name ~= '' then
            if local_role == nil then
                local info = 'RemoteRoleMapping.' .. tostring(i) .. '.LocalRole'
                error(custom_messages.PropertyItemMissing(info))
            end
            -- 组不存在，有组名为新建
            op_type[i] = 'add'
        elseif is_group_exist[i] and group_name == '' then -- 不传用户名只修改属性，传递空用户名才是删除
            op_type[i] = 'delete'
        end
    end
    return op_type
end

return m
