-- 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 mdb_service = require 'mc.mdb.mdb_service'
local gsub = string.gsub
local utils = require 'mc.utils'
local utils_core = require 'utils.core'
local file_sec = require 'utils.file'
local safe_close = utils.safe_close_file

local skynet = require 'skynet'

local m = {}

function m.trim(s)
    return gsub(gsub(s, '^%s+', ''), '%s+$', '')
end

function m.get_valid_string_property(val)
    if val == '' or val == 'N/A' or val == 'NA' or val == 'Unknown' or val == 'null' then
        return null
    end

    return val
end

function m.is_valid_managers_id(id)
    local path = '/bmc/kepler/Managers/' .. id
    local ok, rsp = pcall(mdb_service.is_valid_path, bus, path)
    if not ok then
        log:error('is_valid_managers_id failed, err(%s)', rsp.message)
        error(rsp)
    end

    return rsp.Result
end

function m.is_valid_system_id(id)
    local path = '/bmc/kepler/Systems/' .. id

    local ok, rsp = pcall(mdb_service.is_valid_path, bus, path)
    if not ok then
        log:error('Invalid SystemId(%s), err(%s)', id, rsp.message)
        error(rsp)
    end

    return rsp.Result
end

function m.is_valid_chassis_id(id)
    local path = '/bmc/kepler/Chassis/' .. id

    local ok, rsp = pcall(mdb_service.is_valid_path, bus, path)
    if not ok then
        log:error('Invalid Chassis(%s), err(%s)', id, rsp.message)
        error(rsp)
    end

    return rsp.Result
end

function m.is_valid_path(path, ignore_case)
    local ok, rsp = pcall(mdb_service.is_valid_path, bus, path, ignore_case or false)
    if not ok then
        log:info('Invalid Path, err(%s)', rsp.message)
        error(rsp)
    end

    return rsp.Result
end

function m.cstr_to_luastr(cstr)
    -- 从C获取的字符串是以\0结束的，需要以非贪婪模式获取\0前的字符
    local s = cstr or ''
    return string.match(s, '(.-)\0') or s
end

function m.is_path_exist(interface, querys)
    local params = { interface, cjson.encode(querys), true }

    local ok, rsp = pcall(mdb_service.get_path, bus, table.unpack(params))
    if not ok then
        return false
    end

    return rsp.Path ~= ''
end

function m.is_valid_board_id(paths, id)
    for _, v in ipairs(paths) do
        local ok, obj = pcall(mdb.get_object, bus, v, 'bmc.kepler.Systems.Board')
        if not ok then
            return false
        end
        local nodeid = gsub(obj.NodeId, "%s+", "")
        if string.lower(nodeid) == string.lower(id) then
            return true
        end
    end
    return false
end

function m.get_status(health, state, is_oem)
    if health == 255 then
        return null
    end

    local status = {}
    status.State = state or null

    local health_severity = {
        [0] = { 'OK', 'Informational' },
        [1] = { 'Warning', 'Minor' },
        [2] = { 'Warning', 'Major' },
        [3] = { 'Critical', 'Critical' }
    }

    local hs = health_severity[health]
    status.Health = hs and hs[1] or null

    if is_oem then
        status.Oem = {}
        status.Oem["{{OemIdentifier}}"] = {}
        status.Oem["{{OemIdentifier}}"].Severity = hs and hs[2] or null
    else
        status.Severity = hs and hs[2] or null
    end

    return status
end

function m.get_associated_resource(protocol, resource_id)
    if protocol ~= 6 then
        return null
    end

    return string.format('CPU%s', resource_id)
end

function m.is_valid_bios_attr_id(id)
    id = string.lower(id)
    local obj = mdb.get_object(bus, '/bmc/kepler/Systems/1/Bios', 'bmc.kepler.Systems.Bios')
    local version = obj.RegistryVersion
    local a, b, c = string.match(version, 'V(%d).(%d)(%d)')
    if a ~= nil and b ~= nil and c ~= nil then
        return id == string.format('v%s_%s_%s', a, b, c)
    else
        return false
    end
end

function m.is_valid_bios_attrjson_id(id)
    id = string.lower(id)
    local obj = mdb.get_object(bus, '/bmc/kepler/Systems/1/Bios', 'bmc.kepler.Systems.Bios')
    local version = obj.RegistryVersion
    local a, b, c = string.match(version, 'V(%d).(%d)(%d)')
    if a ~= nil and b ~= nil and c ~= nil then
        return id == string.format('v%s_%s_%s.json', a, b, c)
    else
        return false
    end
end

function m.is_valid_event_id(id)
    id = string.lower(id)
    local obj = mdb.get_object(bus, '/bmc/kepler/Systems/1/Events', 'bmc.kepler.Systems.Events')
    local major_version = obj.MajorVersion
    local minor_version = obj.MinorVersion
    local aux_version = obj.AuxVersion
    if major_version and minor_version and aux_version then
        return id == string.format('v%s_%s_%s', major_version, minor_version, aux_version)
    else
        return false
    end
end

function m.is_valid_event_message_id(id)
    local obj = mdb.get_object(bus, '/bmc/kepler/Systems/1/Events', 'bmc.kepler.Systems.Events')
    local major_version = obj.MajorVersion
    local minor_version = obj.MinorVersion
    local aux_version = obj.AuxVersion
    if major_version and minor_version and aux_version then
        return id == string.format('v%s_%s_%s.json', major_version, minor_version, aux_version)
    else
        return false
    end
end

function m.unique_array(arr)
    local set = {}
    local res = {}
    for _, v in ipairs(arr) do
        if not set[v] then
            res[#res + 1] = v
            set[v] = true
        end
    end
    return res
end

function m.split_string(str, delimiter)
    return utils.split(str, delimiter)
end

-- 将二维数组扁平化为一维数组
function m.flatten_2d_table(t)
    local ok, ret = pcall(function ()
        local ret = {}
        for _, v in ipairs(t) do
            for _, vv in ipairs(v) do
                ret[#ret + 1] = vv
            end
        end
        return ret
    end)
    if not ok then
        log:error('Flatten table failed, err=%s', tostring(ret))
        return {}
    end
    return ret
end

-- 去掉首字母转换成数字
function m.get_valid_lane(val)
    if val == '' or val == 'N/A' or val == 'NA' or val == 'Unknown' or val == 'null' then
        return null
    end

    return tonumber(string.sub(val, 2))
end

-- 判断字符串是否以特定的字符为后缀
function m.ends_with(str, ending)
    return str:sub(-#ending) == ending
end

function m.get_content_from_tpl(path, file_type)
    local RESOURCE_PREFIX = skynet.getenv('RESOURCE_PREFIX') or
                                '/opt/bmc/apps/redfish/interface_config/static_resource'
    local RESOURCE_SUFFIX = file_type == 'json' and '/index.json' or '/index.xml'
    -- 请求url不区分大小写 全部转换成小写防止获取资源失败
    path = string.lower(path)
    -- 部分路径以.json结尾 此时不需要拼接后缀
    local suffix = m.ends_with(path, '.json') and '' or RESOURCE_SUFFIX
    -- 拼接资源路径
    local res_path = RESOURCE_PREFIX .. path .. suffix

    if not utils_core.is_file(res_path) then
        return
    end

    -- 读取json或xml文件并解析
    local file = file_sec.open_s(res_path, 'r')
    if not file then
        return
    end
    local content = safe_close(file, function()
        return file:read('*all')
    end)

    if file_type ~= 'json' then
        return content
    end

    local is_ok, json_obj = pcall(cjson.json_object_ordered_decode, content)
    if not is_ok then
        json_obj = cjson.json_object_new_object()
    end

    return json_obj
end

function m.get_xml_content(path)
    path = path:lower()
    if path:find('%$metadata') then
        path = path:gsub('%$', '')
    end
    return m.get_content_from_tpl(path, 'xml')
end

-- 读取对应路径下的index.json文件并解析
function m.get_json_content(path, pattern)
    local content = m.get_content_from_tpl(path, 'json')
    if pattern and not content then
        local pattern_tmp = string.gsub(pattern, ':([a-z]+)', 'template') -- 将url中:id这类模式匹配的字符串替换成template
        content = m.get_content_from_tpl(pattern_tmp, 'json')
        if not content then
            pattern_tmp = string.gsub(pattern_tmp, 'template', '1', 1) -- 静态资源路径中的systemid和managerid这类写死为1
            content = m.get_content_from_tpl(pattern_tmp, 'json')
        end
    end
    return content or cjson.json_object_new_object()
end

-- 对于传入的带时区的时间，需要相反操作
-- 如传入2024-11-26T08:04:33+06:00,需要-6h回到UTC+00:00时区时间
local OP_MAP = {
    ['+'] = function (a, b)
        return a - b
    end,
    ['-'] = function (a, b)
        return a + b
    end
}

-- 处理BMC时区的影响
-- 如BMC时区+06:00,os.time处理时间戳后需要+6h回到UTC+00:00时区时间
local LOCAL_OP_MAP = {
    ['-'] = function (a, b)
        return a - b
    end,
    ['+'] = function (a, b)
        return a + b
    end
}

-- @param date_time 形如"2024-11-26T08:04:33+06:00"的字符串
-- @param local_offset 本地时区偏移量，例如"+08:00"
-- @return 时间戳
function m.str2timestamp(date_time, local_offset)
    if not date_time or #date_time == 0 then
        log:error("Input data cannot be empty.")
        return 0
    end
    local ok, y, mon, d, _hour, _min, _sec, op, h, _m = pcall(function()
        return string.match(date_time, "(%d+)-(%d+)-(%d+)T(%d+):(%d+):(%d+)(.)(%d+):(%d+)")
    end)
    if not ok then
        log:error("The format of the input data is incorrect.")
        return 0
    end
    local timestamp = utils.get_timestamp(y, mon, d, _hour, _min, _sec)
    local diff_time = h * 3600 + _m * 60
    timestamp = OP_MAP[op](timestamp, diff_time)

    if not local_offset or #local_offset == 0 or local_offset == cjson.null then
        return timestamp
    end
    ok, op, h, _m = pcall(string.match, local_offset, "(.)(%d+):(%d+)")
    if not ok then
        log:error("The format of the local_offset is incorrect.")
        return 0
    end
    diff_time = h * 3600 + _m * 60
    return LOCAL_OP_MAP[op](timestamp, diff_time)
end

function m.get_registry_prefix()
    return custom_messages.RegistryPrefix
end

function m.get_min_pcie_type(pcie_type1, pcie_type2)
    if string.match(pcie_type1, "^Gen[1-6]$") == nil or string.match(pcie_type2, "^Gen[1-6]$") == nil then
        return null
    elseif pcie_type1 < pcie_type2 then
        return pcie_type1
    else
        return pcie_type2
    end
end

return m
