-- Copyright (c) 2025 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 service = require 'observability.service'
local c_initiator = require 'mc.initiator'
local context = require 'mc.context'
local network_core = require 'network.core'
local vos = require 'utils.vos'
local custom_messages = require 'messages.custom'
local m_control = require 'public.control'
local error_code = require 'public.error'

local module = {}

module.initiator = c_initiator.new('N/A', 'N/A', 'N/A')

local receiver_service_mdb_intf = 'bmc.kepler.ObservabilityService.Receiver'
local module_name = 'observability'
local DOMAIN_NAME_MAX_LEN = 255
local ADDR_LIMIT_CHARACTER = "`~!@#$%^&*()_=+[{]}\\|;' ,<>/?\""

local function check_addr_valid(content)
    if content == '' then
        return error_code.E_ERR
    end

    -- IP地址长度不能长于255
    if string.len(content) > DOMAIN_NAME_MAX_LEN then
        log:error("The IP address of the observability server is too long")
        return error_code.E_ERR
    end

    -- IP地址和域名不能包含特殊字符
    if vos.vos_check_incorrect_char(content, -1, ADDR_LIMIT_CHARACTER) ~= 0 then
        log:error('The IP address of the observability server contains special characters')
        return error_code.E_ERR
    end

    -- 校验地址的合法性
    if network_core.verify_host_addr(content) ~= 0 then
        log:error("The IP address of the observability server is invalid")
        return error_code.E_ERR
    end

    return error_code.E_OK
end

local function set_config(db, index, name, value)
    local ok, err = pcall(function()
        db:update(db.Receivers):where({ReceiverId = index}):value({[name] = value}):exec()
    end)
    if not ok then
        log:error('save property %s to db failed, reason: %s', name, err)
        return ok
    end
    log:notice('set receiver(Id=%s) property(name=%s) successfully', tostring(index), tostring(name))
    return ok
end

local function after_change_enabled(obj, db, name, value, sender)
    local ok = set_config(db, obj.ReceiverId, name, value)
    local index = obj.ReceiverId + 1
    local enabled_log = value and 'Enable' or 'Disable'
    local result = ok and 'successfully' or 'failed'
    local op_log = enabled_log .. ' observability receiver' .. index  .. ' ' .. result
    local initiator = (context.get_context() or context.new('N/A', 'N/A', '127.0.0.1')):get_initiator()
    log:operation(initiator, module_name, op_log)
end

local function after_change_address(obj, db, name, value, sender)
    local ok = set_config(db, db.ReceiverId, name, value)
    local index = obj.ReceiverId + 1
    local result = ok and 'successfully' or 'failed'
    local op_log = 'Set observability receiver' .. index .. ' address to ' .. value .. ' ' .. result
    local initiator = (context.get_context() or context.new('N/A', 'N/A', '127.0.0.1')):get_initiator()
    log:operation(initiator, module_name, op_log)
end

local function after_change_port(obj, db, name, value, sender)
    local ok = set_config(db, db.ReceiverId, name, value)
    local index = obj.ReceiverId + 1
    local result = ok and 'successfully' or 'failed'
    local op_log = 'Set observability receiver' .. index .. ' port to ' .. value .. ' ' .. result
    local initiator = (context.get_context() or context.new('N/A', 'N/A', '127.0.0.1')):get_initiator()
    log:operation(initiator, module_name, op_log)
end

local function after_change_protocol(obj, db, name, value, sender)
    local ok = set_config(db, db.ReceiverId, name, value)
    local index = obj.ReceiverId + 1
    local result = ok and 'successfully' or 'failed'
    local op_log = 'Set observability receiver' .. index .. ' protocol to ' .. value .. ' ' .. result
    local initiator = (context.get_context() or context.new('N/A', 'N/A', '127.0.0.1')):get_initiator()
    log:operation(initiator, module_name, op_log)
end

local after_change = {
    ['Enabled'] = function (...)
        after_change_enabled(...)
    end,
    ['Address'] = function (...)
        after_change_address(...)
    end,
    ['Port'] = function (...)
        after_change_port(...)
    end,
    ['Protocol'] = function (...)
        after_change_protocol(...)
    end
}

local function receiver_config_changed_handle(obj, db, name, value, sender)
    if not after_change[name] then
        return
    end
    return after_change[name](obj, db, name, value, sender)
end

function module:register_receiver_service(db, bus)
    self.db = db
    self.bus = bus

    local ok, receiver_objs = pcall(function() return self.db:select(self.db.Receivers):all() end)
    if not ok then
        log:error('get observability database failed, reason: %s', receiver_objs)
        return
    end

    for _, receiver_obj in pairs(receiver_objs) do
        local receiver_service = service:CreateReceivers(receiver_obj.ReceiverId)
        local receiver_service_obj = receiver_service:get_mdb_object(receiver_service_mdb_intf)
        receiver_service_obj.ReceiverId = receiver_obj.ReceiverId
        receiver_service_obj.Enabled = receiver_obj.Enabled
        receiver_service_obj.Address = receiver_obj.Address
        receiver_service_obj.Port = receiver_obj.Port
        receiver_service_obj.property_before_change:on(function(name, value, sender)
            if sender == nil then
                return false
            end

            local ctx = context.get_context()
            local initiator = ctx and ctx:get_initiator() or module.initiator
            self:check_receiver_service_property(receiver_service_obj, initiator, name, value)
            return true
        end)
        receiver_service_obj.property_changed:on(function(name, value, sender)
            self.control:set_state_flag()
            return receiver_config_changed_handle(receiver_service_obj, db, name, value, sender)
        end
        )
    end
end

function module:check_receiver_service_property(obj, initiator, name, content)
    if name == "Address" then
        if check_addr_valid(content) == error_code.E_ERR then
            log:operation(initiator, module_name, 'set observability receiver(Id=%s) Address to %s failed',
                obj.ReceiverId, content)
            error(custom_messages.InvalidServerAddress('%' .. name .. ':' .. content, '%' .. name))
        end
    end
end

function module:init(db, bus)
    self.control = m_control.get_instance()
    self:register_receiver_service(db, bus)
end

return module
