-- 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 class = require 'mc.class'
local singleton = require 'mc.singleton'
local enum = require 'class.types.types'
local web_session = require 'session.domain.session_collection_web'
local cli_session = require 'session.domain.session_collection_cli'
local vnc_session = require 'session.domain.session_collection_vnc'
local kvm_session = require 'session.domain.session_collection_kvm'
local redfish_session = require 'session.domain.session_collection_redfish'
local session_service_mdb = require 'session.interface.mdb.session_service_mdb'
local singal = require 'mc.signal'
local context = require 'mc.context'
local client = require 'iam.client'
local log = require 'mc.logging'
local base_msg = require 'messages.base'
local sutils = require 'session.infrastructure.session_utils'



local SessionService = class()

local session_type_map = {
    [enum.SessionType.GUI:value()] = web_session,
    [enum.SessionType.Redfish:value()] = redfish_session,
    [enum.SessionType.CLI:value()] = cli_session,
    [enum.SessionType.KVM:value()] = kvm_session,
    [enum.SessionType.VNC:value()] = vnc_session,
}


function SessionService:ctor()
    self.collections = {}
    local session_config
    for k, v in pairs(session_type_map) do
        session_config = v.new()
        table.insert(self.collections, session_config)
    end
end

function SessionService:init()
    self:singles_init()
end

function SessionService:session_timeout_monitor_start()
    local skynet = require 'skynet'
    skynet.fork_loop({ count = 0 }, function()
        log:info("session monitor seccessfully startup")
        local ok, err
        while true do
            for _, session_collection in pairs(session_type_map) do
                ok, err = pcall(session_collection.new().remove_timeout_sessions, session_collection.new())
                if not ok then
                    log:error("delete timeout sessions error %s", err.name)
                end
            end
            skynet.sleep(500)
        end
    end)
end

function SessionService:handle_heart_beat(session_id, remain_alive_seconds)
    local session_obj = self:get_session_by_session_id(session_id)
    if session_obj == nil then
        log:warning("try to heartbeat to a nil session, session id: %s", session_id)
        return
    end
    if remain_alive_seconds == 0 then
        session_obj:renew_session_timeout()
    end
end

function SessionService:create_service_to_mdb()
    for _, v in pairs(self.collections) do
        v.m_session_config_create:emit(v)
    end
end

function SessionService:singles_init()
    self.m_session_added = singal.new()
    self.m_session_removed = singal.new()
end

function SessionService:delete_session(session_id)
    -- 完成认证
    local session_type = self.m_session_removed:emit(session_id)
end

local function new_session_authenticate(user_name, password, session_type, ip)
    local ext_config = {
        -- ['RecordOnly'] = true,
        -- ['RecodeLoginInfo'] = true,
        -- ['UpdateActiveTime'] = true,
        ['IsAuthPassword'] = true
    }
    local ctx = context.new()
    local ok, account_info = client:PLocalAccountAuthNLocalAccountAuthNLocalAuthenticate(ctx, user_name, password,
        ext_config)
    if not ok then
        log:notice('call local authenticate failed')
        error(base_msg.InternalError())
    end
    return account_info
end

function SessionService:new_session(ctx, user_name, password, session_type, domain, ip, extradata)
    -- 完成认证
    local account_info = new_session_authenticate(user_name, password, session_type, ip)
    local session_obj = session_type_map[session_type].new():create(account_info, password, ip)
    self.m_session_added:emit(session_obj)
    return session_obj.token, session_obj.csrf_token, session_obj.session_id
end

function SessionService:validate_session(ctx, session_type, token, csrf_token)
    local session = session_type_map[session_type].new():get_session_by_token(token, csrf_token)
    if session == nil then
        log:error('validate session (%s) failed', tostring(session_type) .. token)
        error(base_msg.InternalError())
    end
    if session_type == enum.SessionType.Redfish:value() then
        session:renew_session_timeout()
    end
    return session.session_id
end

function SessionService:set_session_max_count(ctx, session_type, value)
    session_type_map[session_type].new():set_max_count(value)
end

function SessionService:set_session_mode(ctx, session_type, value)
    session_type_map[session_type].new():set_mode(value)
end

function SessionService:set_session_timeout(ctx, session_type, value)
    session_type_map[session_type].new():set_timeout(value)
end

function SessionService:authenticate(ctx, user_name, password, domain)
    local ext_config = {
        ['IsAuthPassword'] = true
    }
    local ctx = context.new()
    local ok, account_info = client:PLocalAccountAuthNLocalAccountAuthNLocalAuthenticate(ctx, user_name, password,
        ext_config)
    if not ok then
        log:notice('call local authenticate failed')
        error(base_msg.InternalError())
    end
    return account_info.Id, account_info.currnet_privileges, account_info.RoleId, {}
end

function SessionService:delete_all_sessions(ctx, session_type, logout_type)
    local delete_session_ids = session_type_map[session_type].new():all_delete(logout_type)
    for _, id in delete_session_ids do
        self.m_session_removed:emit(delete_session_ids)
    end
end

function SessionService:get_session_by_session_id(session_id)
    local session_obj = nil
    for _, v in pairs(self.collections) do
        session_obj = v:get_session_by_session_id(session_id)
        if session_obj ~= nil then
            return session_obj
        end
    end
    return session_obj
end

function SessionService:new_console_session(ctx, token, session_type, session_mode)
    local session_id = sutils.generate_session_id(token)
    local session = self:get_session_by_session_id(session_id)
    if not session then
        log:error('invalid token')
        error(base_msg.InternalError())
    end
    --todo :实现session_id的全局查找
    if session_type ~= enum.SessionType.KVM:value() and session_type ~= enum.SessionType.VIDEO:value() then
        log:error('session type can not create console session')
        error(base_msg.InternalError())
    end

    local required_privilege = (session_type == enum.SessionType.KVM:value() and "KVMMgmt" or "DiagnoseMgmt")
    if not session:check_privileges(required_privilege) then
        log:error('privilege_check failed, can not create console session')
        error(base_msg.InternalError())
    end

    if session.session_type == tostring(enum.SessionType.GUI) or session_type == tostring(enum.SessionType.Redfish) then
        return self:create_console_session_by_session(session,session_type,session_mode)
    end
    if session_type == enum.SessionType.VIDEO:value() then
        log:error('video session is not supported to create by this kind of session')
        error(base_msg.InternalError())
    end
    local kvm_key = session_type_map[session_type].new():get_kvm_key()
    if kvm_key and kvm_key.key:lower() == token:lower() then
        return self:create_console_session_by_session(session,session_type,session_mode)
    end
    log:error('kvm_key error')
    error(base_msg.InternalError())
end

function SessionService:create_console_session_by_session(session, session_type, session_mode)
    local account_info = session.account_info
    local ip = session.client_origin_ip_address
    local session_obj = session_type_map[session_type].new():create(account_info, "null", ip)
    self.m_session_added:emit(session_obj)
    return session_obj.token, session_obj.session_id
end

function SessionService:create_console_session_by_key(session, session_type, session_mode)

end

function SessionService:set_kvm_key(ctx, kvm_key, mode)
    --todo :检验ctx.Username
    session_type_map[enum.SessionType.KVM:value()].new():set_kvm_key(ctx, kvm_key, mode)
end

return singleton(SessionService)
