-- 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 network_core = require('network.core')
local context = require('mc.context')
local m_error_custom = require('messages.custom')

local IPMC_USER_PASSWORD_LEN <const> = 21

-- 用户资源树
local ACCOUNT_MDB_PATH <const> = '/bmc/kepler/AccountService/Accounts'
local ACCOUNT_MDB_INTF <const> = 'bmc.kepler.AccountService.ManagerAccount'
local ACCOUNT_POLICY_MDB_INTF <const> = 'bmc.kepler.AccountService.AccountPolicy'
local LOCAL_ACCOUNT_POLICY_MDB_PATH <const> = '/bmc/kepler/AccountService/AccountPolicies/Local'
local OEM_ACCOUNT_POLICY_MDB_PATH <const> = '/bmc/kepler/AccountService/AccountPolicies/OemAccount'

local m = {}

local function is_repeated_substring(str)
    local len = #str
    for i = 2, len / 2 do
        if len % i == 0 then
            local sub = str:sub(1, i)
            if sub:rep(len / i) == str then
                return true
            end
        end
    end
    return false
end

function m.get_pwd(valid_path, path)
    if valid_path and (type(path) ~= 'string' or #path == 0) then
        return { -5, '' }
    end

    local status, password = libroutemapper_utils.get_line_without_echo('Password:')
    if status ~= 0 or #password > 512 then
        return { -1, password }
    end

    local pw_len = #password
    for i = 1, pw_len do
        if password:byte(i) < 0x20 or password:byte(i) > 0x7E then
            return { -2, password }
        end
    end

    local password_confirm
    status, password_confirm = libroutemapper_utils.get_line_without_echo('Confirm password:')
    if status ~= 0 or password ~= password_confirm then
        return { -3, password }
    end

    if pw_len == 0 then
        return { -5, password }
    end

    if is_repeated_substring(password) then
        if
            not mapper_interface.check_the_input_operation(
                bus,
                'WARNING: Using a password consisting of '
                    .. 'only repeated string may have many adverse effects, such as aa, ababab, and abcdabcd.\n'
                    .. 'Do you want to continue?[Y/N]:'
            )
        then
            return { -4, password }
        end
    end
    local res = {}
    for i = 1, pw_len do
        res[i] = password:byte(i)
    end

    return { 0, res }
end

function m.reset_pwd(need_reset, user_name)
    if not need_reset then
        return { 0, '' }
    end
    local hint = string.format('Reset user[%s]\'s Password:', user_name)
    local status, password = libroutemapper_utils.get_line_without_echo(hint)
    if status ~= 0 or #password >= IPMC_USER_PASSWORD_LEN then
        return { -1, password }
    end

    local pw_len = #password
    for i = 1, pw_len do
        if password:byte(i) < 0x20 or password:byte(i) > 0x7E then
            return { -2, password }
        end
    end

    local password_confirm
    status, password_confirm = libroutemapper_utils.get_line_without_echo('Confirm password:')
    if status ~= 0 or password ~= password_confirm then
        return { -3, password }
    end

    if is_repeated_substring(password) then
        if
            not mapper_interface.check_the_input_operation(
                bus,
                'WARNING: Using a password consisting of '
                    .. 'only repeated string may have many adverse effects, such as aa, ababab, and abcdabcd.\n'
                    .. 'Do you want to continue?[Y/N]:'
            )
        then
            return { -4, password }
        end
    end
    local res = {}
    for i = 1, pw_len do
        res[i] = password:byte(i)
    end

    return { 0, res }
end

local function is_have_user_mgmt(privileges)
    for _, value in ipairs(privileges) do
        if value == 'UserMgmt' then
            return true
        end
    end

    return false
end

local function get_account_visible_map()
    local local_account_policy = mdb.get_object(bus, LOCAL_ACCOUNT_POLICY_MDB_PATH, ACCOUNT_POLICY_MDB_INTF)
    local oem_account_policy = mdb.get_object(bus, OEM_ACCOUNT_POLICY_MDB_PATH, ACCOUNT_POLICY_MDB_INTF)
    local account_visible_map = {}
    account_visible_map['Local'] = local_account_policy.Visible
    account_visible_map['OEM'] = oem_account_policy.Visible
    return account_visible_map
end

---获取当前BMC系统的所有本地用户的资源树路径,按照用户Id属性升序排列
function m.get_paths(Context)
    local paths = {}
    local tmp_paths = {}

    local have_user_mgmt = is_have_user_mgmt(Context.Privilege)
    local account_objs = mdb.get_sub_objects(bus, ACCOUNT_MDB_PATH, ACCOUNT_MDB_INTF)
    local account_visible_map = get_account_visible_map()
    if have_user_mgmt then
        for path, account in pairs(account_objs) do
            if not account_visible_map[account.AccountType] then
                goto continue
            end
            local is_visible = account_visible_map[account.AccountType]
            if is_visible then
                tmp_paths[account.Id] = path
            end
            ::continue::
        end
    else
        for path, account in pairs(account_objs) do
            if account.UserName == Context.UserName then
                tmp_paths[account.Id] = path
            end
        end
    end

    for _, path in pairs(tmp_paths) do
        paths[#paths + 1] = path
    end
    return paths
end

-- 校验远程导入地址是否合规
local function check_remote_file_path_valid(path, protocal)
    -- path 格式 protocal://name:pwd@ip/file_path 或 https://ip/file_path
    local reverse_pos = string.find(string.reverse(path), '@') -- 逆向找@符号,默认文件路径中不支持@(https下不带@是正常的)
    if protocal ~= 'https:' and reverse_pos == nil then
        return false
    end
    local remote_path_with_ip
    if reverse_pos == nil then
        remote_path_with_ip = string.sub(path, 8 + 1, #path) -- https:// 8个字符
    else
        remote_path_with_ip = string.sub(path, #path - reverse_pos + 2, #path) -- +2用于抵消双向1的差异
    end
    -- remote_path_with_ip 格式 ip/file_path
    local sub_pos = string.find(remote_path_with_ip, '/')
    local ip = string.sub(remote_path_with_ip, 1, sub_pos - 1)
    -- 校验是否合法地址
    if network_core.verify_host_addr(ip) == -1 then
        return false
    end
    return true
end

-- 校验远程导入地址是否合规
function m.check_path_valid(option, path)
    -- 非导入内容,无path或不必在此校验path
    if option ~= 'import' then
        return true
    end
    if not path or #path == 0 then
        return false
    end
    local URL_HEAD_TABLE = { 'https:', 'sftp:', 'nfs:', 'cifs:', 'scp:' } -- url文件路径
    for _, v in pairs(URL_HEAD_TABLE) do
        if string.sub(path, 1, #v) == v then
            return check_remote_file_path_valid(path, v)
        end
    end

    return true
end

function m.is_permitted(ctx, path, permission)
    if not ctx then
        -- 探测场景适配
        return true
    end
    local obj = mdb.get_object(bus, '/bmc/kepler/Managers/1/Security/File', 'bmc.kepler.Managers.Security.File')
    local ok, ret = pcall(function()
        return obj:IsPermitted(context.get_context(), path, permission)
    end)
    if not ok then
        return false
    end
    return ret.Result
end

function m.get_role_id(ctx)
    if not ctx then
        return -1
    end
    local accounts_obj =
        mdb.get_object(bus, '/bmc/kepler/AccountService/Accounts', 'bmc.kepler.AccountService.ManagerAccounts')
    local ok, ret = pcall(function()
        return accounts_obj:GetIdByUserName(context.get_context(), ctx.UserName)
    end)
    if not ok then
        return -1
    end

    local account_obj = mdb.get_object(
        bus,
        '/bmc/kepler/AccountService/Accounts/' .. tostring(ret.AccountId),
        'bmc.kepler.AccountService.ManagerAccount'
    )
    return account_obj.RoleId
end

function m.is_import_action_permitted(ctx, path, permission)
    if m.get_role_id(ctx) == 4 or m.is_permitted(ctx, path, permission) then
        return true
    end
    return false
end

function m.is_allow_operate(id, username, islog)
    if not id or type(id) ~= 'number' then
        if islog then
            error(m_error_custom.UserNotExist(username))
        end
        return false
    end
    -- 23号用户固定为框内通信用户,不支持cli操作
    if id == 23 then
        if islog then
            error(m_error_custom.UserNotExist(username))
        end
        return false
    end
    return true
end

return m