-- 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_file = require 'utils.file'
local utils = require 'mc.utils'
local cjson = require 'cjson'

local TYPE_T<const> = {'redfish', 'cli', 'snmp', 'web_backend'}
local METHOD_T<const> = {'get', 'patch', 'post', 'delete'}

local M = {}
M.__index = M

function M.new(t, m)
    return setmetatable({
        type = nil,
        cases = {},
        tree = t,
        mapper = m
    }, M)
end

function M:parse(path)
    if string.match(path, '.+%.(.+)$') ~= 'json' then
        print(string.format('The file %s is incorrect format.', path))
        return false
    end

    local f = utils_file.open_s(path, 'r')
    if not f then
        print(string.format('Open %s failed.', path))
        return false
    end

    local buf = f:read('a')
    local data = cjson.json_object_ordered_decode(buf)
    if not data then
        print(string.format('The file %s is incorrect format.', path))
        return false
    end

    if not data.cases then
        print(string.format('The file %s lacks \'cases\'.', path))
        return false
    end
    local type = string.match(path, '/test/unit/cases/([%a_]*)/.+')
    if not utils.array_contains(TYPE_T, type) then
        print('The interface type is wrong.')
        return false
    end

    self.type = type

    if data.mocks then
        for k, v in pairs(data.mocks) do
            self.tree.mocks[k] = cjson.json_object_to_table(v)
        end
    end

    if data.invokes then
        for k, v in pairs(data.invokes) do
            self.tree.invokes[k] = cjson.json_object_to_table(v)
        end
    end

    for _, v in pairs(data.cases) do
        self.cases[#self.cases + 1] = v
    end

    f:close()
    return true
end

local function execute_redfish(input, mapper)
    local errs, rsp_body = mapper:match(input.uri, input.method, {
        body = input.reqBody ~= nil and input.reqBody or {},
        query = {},
        user = {Interface = 'Redfish', UserName = 'Administrator', ClientIp = '0.0.0.0', AccountId = 1}
    })
    if string.lower(input.method) == 'patch' then
        errs, rsp_body = mapper:match(input.uri, 'GET', {
            body = {},
            query = {},
            user = {Interface = 'Redfish', UserName = 'Administrator', ClientIp = '0.0.0.0', AccountId = 1}
        })
    end

    return errs, rsp_body
end

local function execute_web(input, mapper)
    local errs, rsp_body = mapper:match(input.uri, input.method, {
        body = input.reqBody ~= nil and input.reqBody or {},
        query = {},
        user = {Interface = 'WEB', UserName = 'Administrator', ClientIp = '0.0.0.0', AccountId = 1}
    })
    if string.lower(input.method) == 'patch' then
        errs, rsp_body = mapper:match(input.uri, 'GET', {
            body = {},
            query = {},
            user = {Interface = 'WEB', UserName = 'Administrator', ClientIp = '0.0.0.0', AccountId = 1}
        })
    end

    return errs, rsp_body
end

local function execute_snmp(input, mapper)

end

local function execute_cli(input, mapper)

end

local execute_table = {
    ['redfish'] = execute_redfish,
    ['web_backend'] = execute_web,
    ['snmp'] = execute_snmp,
    ['cli'] = execute_cli,
}

function M:execute(input)
    if not input.uri or not input.method then
        print('The input lacks \'uri\' or \'method\'.')
        return
    end

    if type(input.method) ~= 'string' or not utils.array_contains(METHOD_T, string.lower(input.method)) then
        print(string.format('The method type: %s is wrong.', input.method))
        return
    end

    -- 根据不同的接口类型进入不同处理函数
    return execute_table[self.type](input, self.mapper)
end

function M:cleanup(action)
    if not action then
        return
    end

    if action == 'This' then
        -- 清理本用例中的桩
        self.tree:recover_stub()
    elseif action == 'All' then
        -- 清理之前的所有桩
        self.tree:clear_stub()
    end
end

local function compare(output, rsp_body)
    if not output then
        print('The cases lack \'output\'.')
        return false
    end

    -- assert默认为Equal
    local assert = output.assert == nil and 'Equal' or output.assert
    if not rsp_body then
        if assert == 'Equal' then
            return false
        else
            return true
        end
    end

    local res = cjson.json_object_is_equal(output.expect, rsp_body)
    if not res then
        print(cjson.json_object_ordered_encode_pretty(rsp_body))
        print(cjson.json_object_ordered_encode_pretty(output.expect))
    end

    return res
end

function M:run_one(case)
    if not case.name or not case.input or not case.output then
        print('The case lacks \'name\' or \'input\' or \'output\'.')
        return false
    end

    local t_case = cjson.json_object_to_table(case)

    self.tree:mock(t_case.mocks) -- 资源树打桩
    self.tree:invoke(t_case.invokes) -- 资源树变化打桩

    local _, rsp_body = self:execute(t_case.input)
    local res = compare(case.output, rsp_body)

    self:cleanup(t_case.cleanup) -- 清理资源树桩
    if not res then
        print(t_case.name .. ' failed')
        return false
    end

    print(t_case.name .. ' successfully')
    return res
end

function M:run()
    local res = true
    for _, v in pairs(self.cases) do
        -- 对桩进行备份，以便清理桩
        self.tree:backup_stub()
        res = res and self:run_one(v)
    end

    -- 一个用例文件结束后清理tree对象
    self.tree:clear()

    return res
end

return M
