-- 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.
--         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 log = require 'mc.logging'
local skynet = require 'skynet'
local singleton = require 'mc.singleton'
local client = require 'general_hardware.client'

-- 常量集中定义
local DEFAULT_SYSTEM_ID = 1
local FRU0ID = 0
local MAX_RETRY_TIMES = 10
local RETRY_INTERVAL_MS = 6000

-- 属性名
local IP_PROPERTY = "IpAddr"
local MAC_PROPERTY = "Mac"

-- 接口路径模板
local PATH_TEMPLATES = {
    ipv4 = '/bmc/kepler/Managers/(%d)/EthernetInterfaces/Ipv4',
    ipv6 = '/bmc/kepler/Managers/(%d)/EthernetInterfaces/Ipv6',
    mac  = '/bmc/kepler/Managers/(%d)/EthernetInterfaces'
}

local INTERFACE_TYPES = {
    ipv4 = 'bmc.kepler.Managers.EthernetInterfaces.Ipv4',
    ipv6 = 'bmc.kepler.Managers.EthernetInterfaces.Ipv6',
    mac  = 'bmc.kepler.Managers.EthernetInterfaces'
}

local common_interface = class()

-- 辅助：安全 sleep（skynet 单位是 10ms）
function common_interface:sleep_ms(ms)
    skynet.sleep(math.ceil(ms / 10))
end

-- 通用注册回调（修复字段名）
function common_interface:register_callback(cb, obj, property, flag)
    if flag and self.flags[flag] then
        return
    end
    if flag then
        self.flags[flag] = true
    end
    self.callbacks[property] = self.callbacks[property] or {}
    table.insert(self.callbacks[property], {
        callback = cb,
        obj = obj
    })
end

-- 通知所有注册的回调
function common_interface:_notify_callbacks(property, id, value)
    local list = self.callbacks[property]
    if not list then
        return
    end
    for _, item in pairs(list) do
        item.callback(item.obj, id, value)
    end
end

-- 通用监听函数
local function setup_eth_listener(self, prop_type)
    local path_pattern = PATH_TEMPLATES[prop_type]
    local interface_name = INTERFACE_TYPES[prop_type]
    local property_key = (prop_type == 'mac') and MAC_PROPERTY or IP_PROPERTY
    local store = self[prop_type == 'ipv4' and 'ipv4_addr' or
    prop_type == 'ipv6' and 'ipv6_addr' or 'mac']

    -- OnPropertiesChanged
    local on_prop_changed = function(value, path, interface)
        if interface ~= interface_name then
            log:info('monitor %s: interface(%s) mismatch', prop_type,
                interface)
            return
        end
        local manager_id = tonumber(string.match(path or '', path_pattern))
        if manager_id and value and value[property_key] then
            local new_val = value[property_key]:value()
            log:notice('the %s %s of manager %s is %s', prop_type, property_key, manager_id, new_val)
            store[manager_id] = new_val
            self:_notify_callbacks(prop_type, manager_id, new_val)
        end
    end

    -- OnInterfacesAdded
    local on_added = function(_, path, obj)
        local manager_id = tonumber(string.match(path or '', path_pattern))
        if manager_id and obj and obj[property_key] then
            local new_val = obj[property_key]:value()
            log:notice('the %s %s of manager %s is %s', prop_type, property_key, manager_id, new_val)
            store[manager_id] = new_val
            self:_notify_callbacks(prop_type, manager_id, new_val)
        end
    end

    -- Foreach initial objects
    local foreach_func = function(obj)
        local manager_id = tonumber(string.match(obj.path or '', path_pattern))
        if manager_id and obj and obj[property_key] then
            local new_val = obj[property_key]
            log:notice('the %s %s of manager %s is %s', prop_type, property_key, manager_id, new_val)
            store[manager_id] = new_val
            self:_notify_callbacks(prop_type, manager_id, new_val)
        end
    end

    -- 注册监听器
    if prop_type == 'ipv4' then
        client:OnIpv4PropertiesChanged(on_prop_changed)
        client:OnIpv4InterfacesAdded(on_added)
        client:ForeachIpv4Objects(foreach_func)
    elseif prop_type == 'ipv6' then
        client:OnIpv6PropertiesChanged(on_prop_changed)
        client:OnIpv6InterfacesAdded(on_added)
        client:ForeachIpv6Objects(foreach_func)
    else -- mac
        client:OnEthernetInterfacesPropertiesChanged(on_prop_changed)
        client:OnEthernetInterfacesInterfacesAdded(on_added)
        client:ForeachEthernetInterfacesObjects(foreach_func)
    end
end

function common_interface:listen_and_update_sn()
    local fru_path_params = { SystemId = DEFAULT_SYSTEM_ID }
    local retry_count = 0

    -- 尝试获取 FRU ID 并读取 SN
    while retry_count < MAX_RETRY_TIMES do
        local fru_id = nil
        client:ForeachOverviewObjects(function(obj)
            if obj and obj.FruId == FRU0ID then
                fru_id = obj.extra_params and obj.extra_params.ObjectName
            end
        end)

        if not fru_id then
            log:info(" get fru_id failed (retry %s/%s)", retry_count + 1,
                MAX_RETRY_TIMES)
            self:sleep_ms(RETRY_INTERVAL_MS)
            retry_count = retry_count + 1
            goto continue
        end

        fru_path_params.Id = fru_id
        local ok, rsp = pcall(client.GetFruDataProductObject, client, fru_path_params)

        if ok and rsp and rsp.ProductSerialNumber and rsp.ProductSerialNumber ~= "" then
            local sn = rsp.ProductSerialNumber
            log:notice(" the SN of host bmc is %s", sn)
            self.sn[DEFAULT_SYSTEM_ID] = sn
            self:_notify_callbacks('sn', DEFAULT_SYSTEM_ID, sn)
            break
        end

        retry_count = retry_count + 1
        log:info("get fru sn failed (retry %s/%s)", retry_count, MAX_RETRY_TIMES)
        if retry_count >= MAX_RETRY_TIMES then
            log:error("get fru sn failed after max retries")
        else
            self:sleep_ms(RETRY_INTERVAL_MS)
        end
        ::continue::
    end

    -- 监听后续变更
    client:OnProductPropertiesChanged(function(values)
        if values.ProductSerialNumber and values.ProductSerialNumber ~= "" then
            local sn = values.ProductSerialNumber:value()
            log:notice('[general_hardware] the SN of host bmc is %s', sn)
            self.sn[DEFAULT_SYSTEM_ID] = sn
            self:_notify_callbacks('sn', DEFAULT_SYSTEM_ID, sn)
        end
    end, fru_path_params)
end

function common_interface:register_bmc_listening()
    if not self.registered_bmc_properties_listening then
        self.registered_bmc_properties_listening = true
        local ok, err = pcall(function()
            setup_eth_listener(self, 'ipv4')
            setup_eth_listener(self, 'ipv6')
            setup_eth_listener(self, 'mac')
            self:listen_and_update_sn()
        end)
        if not ok then
            log:error('start listening bmc properties failed: %s', tostring(err))
        else
            log:notice('start listening bmc properties')
        end
        self.registered_bmc_properties_listening = ok
    end
    return self.registered_bmc_properties_listening
end

function common_interface:get_ipv4_addr(manager_id)
    return self.ipv4_addr[manager_id]
end

function common_interface:get_ipv6_addr(manager_id)
    return self.ipv6_addr[manager_id]
end

function common_interface:get_mac(manager_id)
    return self.mac[manager_id]
end

function common_interface:get_sn()
    return self.sn[DEFAULT_SYSTEM_ID]
end

function common_interface:ctor(bus)
    self.bus = bus
    self.callbacks = {}
    self.flags = {}
    self.ipv4_addr = {}
    self.ipv6_addr = {}
    self.mac = {}
    self.sn = {}
    self.registered_bmc_properties_listening = false
end

common_interface.setup_eth_listener = setup_eth_listener

return singleton(common_interface)
