-- Copyright (c) 2026 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 replace_ok = require('route_mapper_compat').native
local log = require('mc.logging')
local http = require('http')
local cjson = require('route_mapper_cjson')
local Controller = require('http.controller')
local webrest_utils = require('webrest_utils')
local routes_root = require('web_backend.routes.root')
local task_mgnt = require('web_backend.lualib.task_mgnt')

local reply_bad_request = http.reply_bad_request
local vos, mapper_intf
if not replace_ok then
    vos = require('utils.vos')
    mapper_intf = require('route_mapper.interface')
end

local load_controller = {}

local function merge_rsp_extra_header(rsp)
    if not rsp or not rsp.header or not rsp.extra_header then
        return
    end

    for k, v in pairs(rsp.extra_header) do
        rsp.header[k] = v
    end
end

local function replace_get(_self, ctx)
    local req = {
        user = ctx.user,
        params = ctx.req.params,
        path = ctx.req.path,
        header = ctx.req.header,
        mapper_session = ctx.mapper_session,
    }
    local query = require('http.query')

    req.query = query.parse_query(ctx.req.query)

    local rsp_body = {}
    local rsp_header = {}
    local rsp = {
        body = rsp_body,
        header = rsp_header,
    }

    return _self:process_get(req, rsp)
end

local function get(_self, ctx)
    local req = { user = ctx.user, params = ctx.req.params, path = ctx.req.path, header = ctx.req.header }
    local query = require('http.query')
    req.query = query.parse_query(ctx.req.query)
    local rsp_body = {}
    local rsp_header = {}
    local rsp = { body = rsp_body, header = rsp_header }
    return _self:process_get(req, rsp)
end

local function replace_on_get(_self, req, rsp)
    if not req.user then
        req.user = {
            Interface = 'WEB',
        }
    else
        req.user.Interface = 'WEB'
    end

    local result = _self.route_mapper:execute(req.path, 'get', {
        is_auth = true,
        header = req.header,
        body = req.body,
        query = req.query,
        user = req.user,
        location_id = webrest_utils.get_location_id(),
        session = req.mapper_session,
    })
    rsp.body = result.body
    rsp.extra_header = result.headers
    rsp.status = result.status

    if #result.errors ~= 0 then
        return webrest_utils.parse_route_mapper_error(result.errors)
    end
end

local function on_get(_self, req, rsp)
    local err, extra_info
    if not req.user then
        req.user = { Interface = 'WEB' }
    else
        req.user['Interface'] = 'WEB'
    end
    err, rsp.body, extra_info = _self.worker_ctrl:process(req.path, 'get', {
        header = req.header,
        body = req.body,
        query = req.query,
        user = req.user,
        is_auth = true,
        location_id = webrest_utils.get_location_id(),
    })
    rsp.extra_header = extra_info and extra_info.RspHeader
    if err and #err ~= 0 then
        return webrest_utils.parse_route_mapper_error(err)
    end
end

local function on_get_rsp_header(_self, req, rsp)
    merge_rsp_extra_header(rsp)
end

function load_controller.get(controller)
    if not controller then
        return
    end

    if replace_ok then
        controller.get = replace_get
        controller.on_get = replace_on_get
    else
        controller.get = get
        controller.on_get = on_get
    end
    controller.on_get_rsp_header = on_get_rsp_header
end

local function patch(_self, ctx)
    local reply = _self:auth(ctx)
    if not reply:isOk() then
        return reply
    end

    local is_ok, body = pcall(cjson.json_object_ordered_decode, ctx.req.body)
    if not is_ok or not cjson.json_object_is_object(body) then
        return http.reply_bad_request('UnrecognizedRequestBody')
    end
    local req = {
        user = ctx.user,
        params = ctx.req.params,
        path = ctx.req.path,
        header = ctx.req.header,
        body = body,
        result = http.reply_ok(),
    }
    if replace_ok then
        req.mapper_session = ctx.mapper_session
    end

    local query = require('http.query')
    req.query = query.parse_query(ctx.req.query)
    local rsp_body = {}
    local rsp_header = {}
    local rsp = { body = rsp_body, header = rsp_header }
    return _self:process_patch(req, rsp)
end

local function replace_on_patch(_self, req, rsp)
    local result = _self.route_mapper:execute(req.path, 'patch', {
        header = req.header,
        body = req.body,
        query = req.query,
        user = req.user,
        session = req.mapper_session,
    })

    rsp.extra_header = result.headers
    rsp.status = result.status
    local get_result = _self.route_mapper:execute(req.path, 'get', {
        header = req.header,
        body = req.body,
        query = req.query,
        user = req.user,
        session = req.mapper_session,
    })
    rsp.body = get_result.body

    if #result.errors ~= 0 then
        local reply = webrest_utils.parse_route_mapper_error(result.errors)

        return reply
    end
end

local function on_patch(_self, req, rsp)
    local err, _, extra_info = _self.worker_ctrl:process(
        req.path,
        'patch',
        { header = req.header, body = req.body, query = req.query, user = req.user }
    )
    rsp.extra_header = extra_info and extra_info.RspHeader

    _, rsp.body = _self.worker_ctrl:process(
        req.path,
        'get',
        { header = req.header, body = req.body, query = req.query, user = req.user }
    )
    if err and #err ~= 0 then
        local result = webrest_utils.parse_route_mapper_error(err)
        return result
    end
end

local function on_patch_rsp_header(_self, req, rsp)
    merge_rsp_extra_header(rsp)
end

function load_controller.patch(controller)
    if not controller then
        return
    end

    if replace_ok then
        controller.on_patch = replace_on_patch
    else
        controller.on_patch = on_patch
    end
    controller.patch = patch
    controller.on_patch_rsp_header = on_patch_rsp_header
end

local function replace_post(_self, ctx)
    local is_ok, body = pcall(cjson.json_object_ordered_decode, ctx.req.body)

    if not is_ok or not (cjson.json_object_is_object(body)) then
        return http.reply_bad_request('UnrecognizedRequestBody')
    end

    local req = {
        user = ctx.user,
        params = ctx.req.params,
        path = ctx.req.path,
        header = ctx.req.header,
        body = body,
        mapper_session = ctx.mapper_session,
    }
    local query = require('http.query')

    req.query = query.parse_query(ctx.req.query)

    local rsp_body = {}
    local rsp_header = {}
    local rsp = {
        body = rsp_body,
        header = rsp_header,
    }

    return _self:process_post(req, rsp)
end

local function post(_self, ctx)
    local unrecognized_req = false
    local is_ok, body = pcall(cjson.json_object_ordered_decode, ctx.req.body)
    if not is_ok or not cjson.json_object_is_object(body) then
        unrecognized_req = true
    end

    local req = {
        user = ctx.user,
        params = ctx.req.params,
        path = ctx.req.path,
        header = ctx.req.header,
        body = body,
        unrecognized_req = unrecognized_req,
    }

    local query = require('http.query')
    req.query = query.parse_query(ctx.req.query)
    local rsp_body = {}
    local rsp_header = {}
    local rsp = { body = rsp_body, header = rsp_header }
    return _self:process_post(req, rsp)
end

local function replace_on_post(_self, req, rsp)
    if not req.user then
        req.user = {
            Interface = 'WEB',
        }
    else
        req.user.Interface = 'WEB'
    end

    local result = _self.route_mapper:execute(req.path, 'post', {
        is_auth = true,
        header = req.header,
        body = req.body,
        query = req.query,
        user = req.user,
        location_id = webrest_utils.get_location_id(),
        session = req.mapper_session,
    })
    rsp.body = result.body
    rsp.extra_header = result.headers
    rsp.status = result.status

    if #result.errors ~= 0 then
        return webrest_utils.parse_route_mapper_error(result.errors)
    end

    local task_info = result.effects and result.effects.Tasks and result.effects.Tasks[1]
    if task_info and tonumber(task_info.TaskId) and tonumber(task_info.TaskId) > 0 then
        rsp.task = true

        local task_id = task_mgnt.get_instance():create_new_task(
            _self.app.bus,
            task_info.Path,
            task_info.TaskId,
            task_info.Context,
            task_info.PostTaskProcess
        )

        if not task_id then
            return reply_bad_request('TaskLimitExceeded')
        end

        local taskinfo = task_mgnt.get_instance():get_task_info(task_id)

        if not taskinfo then
            return http.reply_not_found('ResourceMissingAtURI')
        end

        local body = {
            url = '/UI/Rest/Task/' .. task_id,
        }

        return http.reply(http.HTTP_OK, body)
    end
end

local function on_post(_self, req, rsp)
    local err, extra_info
    if not req.user then
        req.user = { Interface = 'WEB' }
    else
        req.user['Interface'] = 'WEB'
    end
    err, rsp.body, extra_info = _self.worker_ctrl:process(req.path, 'post', {
        header = req.header,
        body = req.body,
        query = req.query,
        user = req.user,
        is_auth = true,
        unrecognized_req = req.unrecognized_req,
        location_id = webrest_utils.get_location_id(),
    })
    rsp.extra_header = extra_info and extra_info.RspHeader
    if err and #err ~= 0 then
        return webrest_utils.parse_route_mapper_error(err)
    end
    if extra_info and extra_info.TaskInfo and extra_info.TaskInfo.TaskId > 0 then
        rsp.task = true
        local task_id = task_mgnt.get_instance():create_new_task(
            _self.app.bus,
            extra_info.TaskInfo.Path,
            extra_info.TaskInfo.TaskId,
            extra_info.TaskInfo.Context,
            extra_info.TaskInfo.PostTaskProcess
        )
        if not task_id then
            return reply_bad_request('TaskLimitExceeded')
        end

        local taskinfo = task_mgnt.get_instance():get_task_info(task_id)
        if not taskinfo then
            return http.reply_not_found('ResourceMissingAtURI')
        end
        local body = { url = '/UI/Rest/Task/' .. task_id }

        return http.reply(http.HTTP_OK, body)
    end
end

local function on_post_rsp_header(_self, req, rsp)
    merge_rsp_extra_header(rsp)
end

function load_controller.post(controller)
    if not controller then
        return
    end

    if replace_ok then
        controller.post = replace_post
        controller.on_post = replace_on_post
    else
        controller.post = post
        controller.on_post = on_post
    end
    controller.on_post_rsp_header = on_post_rsp_header
end

local function delete(_self, ctx)
    local reply = _self:auth(ctx)
    if not reply:isOk() then
        return reply
    end

    local is_ok, body = pcall(function()
        local req_body = (ctx.req.body == nil or ctx.req.body == '') and '{}' or ctx.req.body
        return cjson.json_object_ordered_decode(req_body)
    end)
    if not is_ok or not cjson.json_object_is_object(body) then
        return http.reply_bad_request('UnrecognizedRequestBody')
    end
    local req = {
        user = ctx.user,
        params = ctx.req.params,
        path = ctx.req.path,
        header = ctx.req.header,
        body = body,
    }
    if replace_ok then
        req.mapper_session = ctx.mapper_session
    end

    local rsp_body = {}
    local rsp_header = {}
    local rsp = { body = rsp_body, header = rsp_header }
    return _self:process_delete(req, rsp)
end

local function replace_on_delete(_self, req, rsp)
    local result = _self.route_mapper:execute(req.path, 'delete', {
        header = req.header,
        body = req.body,
        query = req.query,
        user = req.user,
        session = req.mapper_session,
    })
    rsp.body = result.body
    rsp.extra_header = result.headers
    rsp.status = result.status

    if #result.errors ~= 0 then
        return webrest_utils.parse_route_mapper_error(result.errors)
    end
end

local function on_delete(_self, req, rsp)
    local err, extra_info
    err, rsp.body, extra_info = _self.worker_ctrl:process(
        req.path,
        'delete',
        { header = req.header, body = req.body, query = req.query, user = req.user }
    )
    rsp.extra_header = extra_info and extra_info.RspHeader
    if err and #err ~= 0 then
        return webrest_utils.parse_route_mapper_error(err)
    end
end

local function on_delete_rsp_header(_self, req, rsp)
    merge_rsp_extra_header(rsp)
end

function load_controller.delete(controller)
    if not controller then
        return
    end

    if replace_ok then
        controller.on_delete = replace_on_delete
    else
        controller.on_delete = on_delete
    end
    controller.delete = delete
    controller.on_delete_rsp_header = on_delete_rsp_header
end

local function load_mapper_controller(server)
    if not (server and server.mapper) then
        error('in-process route mapper must be initialized before mapped controllers', 0)
    end

    local controller = Controller.new('/')
    controller.parent = routes_root.Route
    for _, method in ipairs({ 'get', 'post', 'patch', 'delete' }) do
        load_controller[method](controller)
    end
    controller.app = server.app
    controller.route_mapper = server.route_mapper
    controller.server = server
    server.mapper_controller = controller
end

local function load_json_controllers(server, resources)
    if not cjson.json_object_is_array(resources) then
        log:error('Resources type error(not array), load failed.')
        return
    end

    local interfaces, method_type
    for _, v in pairs(resources) do
        if not v or not v.Uri then
            goto continue_resources
        end

        local controller = Controller.new(v.Uri)
        controller.parent = routes_root.Route
        interfaces = v.Interfaces
        if not interfaces then
            log:error('The interfaces is incorrect(Uri=%s)', v.Uri)
            goto continue_resources
        end

        for _, value in ipairs(interfaces) do
            if not value['Type'] then
                log:error('The Type of Interfaces is empty(Uri=%s)', v.Uri)
                goto continue_interfaces
            end

            method_type = string.lower(value['Type'])
            local handle = load_controller[method_type]
            if handle then
                handle(controller)
            end
            ::continue_interfaces::
        end

        server:add_controller(controller)
        ::continue_resources::
    end
end

if replace_ok then
    load_controller.load_mapper_controller = load_mapper_controller
else
    load_controller.load_json_controllers = load_json_controllers
end

if replace_ok then
    function load_controller.dispatch_mapper_controller(server, ctx, session)
        local path = ctx.req.path or ctx.req.url
        local method = string.lower(ctx.req.method)
        if not (server.mapper:match(path, method, session)) then
            return false
        end

        local controller = assert(server.mapper_controller, 'mapped request controller is not initialized')
        ctx.mapper_session = session
        ctx.req.params = ctx.req.params or {}
        local handle = controller:get_handle(method)
        if not handle then
            return false
        end
        return true, handle(controller, ctx)
    end
else
    -- 递归解析映射器配置,加载到controller
    local function load_one_controller(server, mapper)
        if type(mapper) ~= 'table' then
            return
        end

        for uri, method_types in pairs(mapper) do
            if type(method_types) ~= 'table' then
                log:error('The resource is incorrect.')
                goto continue_resources
            end

            local controller = Controller.new(uri)
            controller.parent = routes_root.Route

            for method_type, flag in pairs(method_types) do
                if not flag then
                    log:error('The Type of Interfaces is empty.')
                    goto continue_interfaces
                end

                local handle = load_controller[method_type]
                if handle then
                    handle(controller)
                end
                ::continue_interfaces::
            end

            server:add_controller(controller)
            ::continue_resources::
        end
    end

    function load_controller.load_controllers(server, mapper)
        for key, value in pairs(mapper) do
            if string.match(key, '%.json$') ~= nil then
                load_one_controller(server, value)
            elseif type(value) == 'table' then
                load_controller.load_controllers(server, value)
            end
        end
    end
end

-- 加载手写controller
local function load_static_controller(server)
    if not server then
        log:error('Input param server is empty.')
        return
    end

    server:add_controller(require('firmware_inventory'))
    server:add_controller(require('download_static_file'))
    server:add_controller(require('task'))
    server:add_controller(require('auth_jar'))
    server:add_controller(require('ntp_importkeys'))
end

-- 加载路由controler,先加载手写路由,再根据映射器配置,加载映射器路由
-- 重复路由会被自动覆盖
function load_controller.load_route_controller(server, conf_path)
    if not server or not conf_path then
        log:error('Input param server or conf_path is empty.')
        return
    end
    load_static_controller(server)
    if replace_ok then
        load_controller.load_mapper_controller(server)
        return
    end
    -- 预编译文件不存在,直接加载映射器配置
    if not vos.get_file_accessible(conf_path .. '/config.lua') then
        mapper_intf.load_mapping(server.app.bus, conf_path, function(obj)
            load_json_controllers(server, obj.Resources)
        end)
    else
        local mapper = mapper_intf.load_intf_mapping(server.app.bus, conf_path)
        load_controller.load_controllers(server, mapper)
    end
end

return load_controller