-- 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 skynet = require 'skynet'
local log = require 'mc.logging'
local frudata_tianchi = require 'frudata_object.frudata_tianchi'
local frudata_eepromv2 = require 'frudata_object.frudata_eepromv2'
local frudata_file = require 'frudata_object.frudata_file'
local frudata_mcu = require 'frudata_object.frudata_mcu'
local frudata_non_standard = require 'frudata_object.frudata_non_standard'
local frudata_custom_offset_eeprom = require 'frudata_object.frudata_custom_offset_eeprom'
local frudata_readonly_standard_eeprom = require 'frudata_object.frudata_readonly_standard_eeprom'
local signal = require 'mc.signal'
local class_mgnt = require 'mc.class_mgnt'
local fru_singleton = require 'fru_singleton'
local fru_func = require 'function_get_fru_info'
require 'frudata.json_types.BlockIO'

local frudata_service = { update_elabel_sig = signal.new() }
frudata_service.__index = frudata_service

function frudata_service:new(fruinfo_manage, non_standard, read_only_standard, intf_slot)
    return setmetatable(
        { fruinfo_manage = fruinfo_manage, non_standard = non_standard, read_only_standard = read_only_standard, intf_slot = intf_slot, m_fru_collection = {} },
        frudata_service)
end

local component_health = {}

function frudata_service:new_frudata(storage_type, fruinfo_manage, obj)
    local switch = {
        ['TianChi'] = function()
            return frudata_tianchi:new(fruinfo_manage, obj)
        end,
        ['EepromV2'] = function()
            return frudata_eepromv2:new(fruinfo_manage, obj)
        end,
        ['File'] = function()
            return frudata_file:new(fruinfo_manage, obj)
        end,
        ['MCU'] = function()
            self.intf_slot[obj['bmc.kepler.FrudataService.Frudata']] = obj
            fru_singleton.get_instance().is_mcu_fru[obj.FruId] = true
            return frudata_mcu:new(fruinfo_manage, obj)
        end,
        ['EepromStandard'] = function()
            return frudata_tianchi:new(fruinfo_manage, obj)
        end,
        ['CustomOffsetEeprom'] = function()
            return frudata_custom_offset_eeprom:new(fruinfo_manage, obj)
        end,
        ['ROStdEeprom'] = function()
            self.read_only_standard[obj.FruId] = true
            return frudata_readonly_standard_eeprom:new(fruinfo_manage, obj)
        end
    }
    if switch[storage_type] then
        return switch[storage_type]()
    end

    -- 内存初始化完成后再添加进表中，避免update方法失败后不抛出异常
    self.intf_slot[obj['bmc.kepler.FrudataService.Frudata']] = obj
    self.non_standard[obj.FruId] = true
    return frudata_non_standard:new(fruinfo_manage, obj)
end

local function read_eeprom_data(frudata, fruinfo_manage, obj)
    local ok, ret
    for i = 1, 10 do
        ok, ret = pcall(frudata.init, frudata, fruinfo_manage, obj)
        if not ok then
            log:error('read eeprom data failed(%d), sleep 10s. fru_id = %d, ret :%s', i, obj.FruId, ret)
            if not skynet.getenv('TEST_DATA_DIR') then
                skynet.sleep(1000)
            end
        else
            log:notice('read %s data successfully, break loop. fru_id = %d', obj.StorageType, obj.FruId)
            -- 读取成功，更新 Health 为 0（读取正常）
            obj['bmc.kepler.Systems.FruData.Overview'].Health = 0
            log:notice('update frudata Health to 0 (read success) for fru_id = %d', obj.FruId)
            if obj.FruId == 0 then
                fru_singleton.get_instance().is_fru_main_board_init = true
            end
            frudata_service.update_elabel_sig:emit(obj.FruId)
            return
        end
    end
    -- 读取失败（10次重试都失败），更新 Health 为 1（读取异常）
    obj['bmc.kepler.Systems.FruData.Overview'].Health = 1
    log:error('init fruid(%d) name(%s) failed', obj.FruId, obj:get_object_name() or '')
end

function frudata_service:add_object(fruinfo_manage, obj)
    -- 更新状态0默认更新完成，其他为正在更新
    fru_singleton.get_instance().update_status[obj.FruId] = 0

    fruinfo_manage[obj.FruId] = {
        IsSupportDft = 1, -- 是否支持ipmi读写电子标签
        FruType = 1,      -- 类型，是否用Eeprom存储电子标签
        EepFormat = 1,    -- 默认是天池规范的Eep规范
        FruOffset = 0,    -- fru域偏移
        SystemOffset = 0  -- system域偏移
    }

    local frudata = self:new_frudata(obj.StorageType, fruinfo_manage, obj)
    self.m_fru_collection[obj.FruId] = frudata

    skynet.fork(function()
        read_eeprom_data(frudata, fruinfo_manage, obj)
    end)
end

function frudata_service:update_fru_data_by_id(fru_id)
    local obj = self.m_fru_collection[fru_id]
    if obj then
        return obj:update_fru_data_to_dbus(fru_id)
    end
    return nil
end

function frudata_service:update_sys_data_by_id(fru_id)
    local obj = self.m_fru_collection[fru_id]
    if obj then
        return obj:update_sys_data_to_dbus(fru_id)
    end
    return nil
end

-- 根据FruID写Fru域信息到EEP
function frudata_service:set_fru_info_by_id(fru_id, data)
    local obj = self.m_fru_collection[fru_id]
    if obj then
        fru_singleton.get_instance().update_status[fru_id] = fru_singleton.get_instance().update_status[fru_id] + 1
        local ok, ret = pcall(function ()
            obj:update_fru_data_to_dbus(fru_id)
            obj:write_fru_area(fru_id, data)
        end)
        fru_singleton.get_instance().update_status[fru_id] = fru_singleton.get_instance().update_status[fru_id] - 1
        if not ok then
            log:error('write_fru_area failed. fru_id = %s, ret=%s', fru_id, ret)
        end
    end
    return nil
end

function frudata_service:set_system_info_by_id(fru_id)
    local obj = self.m_fru_collection[fru_id]
    if obj then
        fru_singleton.get_instance().update_status[fru_id] = fru_singleton.get_instance().update_status[fru_id] + 1
        local ok = pcall(function ()
            obj:update_sys_data_to_dbus(fru_id)
            obj:write_system_area_to_eep(fru_id)
        end)
        fru_singleton.get_instance().update_status[fru_id] = fru_singleton.get_instance().update_status[fru_id] - 1
        if not ok then
            log:error('write_system_area_to_eep failed. fru_id = %s', fru_id)
        end
    end
    return nil
end

function frudata_service:update_fru_health(fru_id)
    local fru_obj = fru_func.get_fru_obj_by_fruid(fru_id)
    if not fru_obj or not component_health[fru_id] then
        return
    end
    local health = 0  -- 0表示OK状态
    for _, value in pairs(component_health[fru_id]) do
        if value > health then
            health = value
        end
    end
    fru_obj.Health = health
end

function frudata_service:update_component_health(fru_id, path, value)
    if fru_id == 255 then  -- 255为无效FruId
        return
    end
    if not component_health[fru_id] then
        component_health[fru_id] = {}
    end

    component_health[fru_id][path] = value
    self:update_fru_health(fru_id)
end

function frudata_service:clear_component_health(fru_id, path)
    if not component_health[fru_id] then
        return
    end

    component_health[fru_id][path] = nil
    self:update_fru_health(fru_id)
end

local function format_frudata_info(fru_id, frudata)
    return {
        Items = {
            FruId = tostring(fru_id),
            FruName = frudata.FruName,
            ChassisType = frudata.ChassisType,
            ChassisPartNumber = frudata.ChassisPartNumber,
            ChassisSerialNumber = frudata.ChassisSerialNumber,
            ChassisCustomInfo = frudata.ChassisCustomInfo,
            MfgDate = frudata.MfgDate,
            BoardManufacturer = frudata.BoardManufacturer,
            BoardProductName = frudata.BoardProductName,
            BoardSerialNumber = frudata.BoardSerialNumber,
            BoardPartNumber = frudata.BoardPartNumber,
            BoardFRUFileID = frudata.BoardFRUFileID,
            BoardCustomInfo = frudata.BoardCustomInfo,
            ManufacturerName = frudata.ManufacturerName,
            ProductName = frudata.ProductName,
            ProductPartNumber = frudata.ProductPartNumber,
            ProductVersion = frudata.ProductVersion,
            ProductSerialNumber = frudata.ProductSerialNumber,
            AssetTag = frudata.AssetTag,
            ProductFRUFileID = frudata.ProductFRUFileID,
            ProductCustomInfo = frudata.ProductCustomInfo
        }
    }
end

function frudata_service:get_frudata_list()
    local res = {}
    local fruid_seen = {}  -- 去重，确保每个 FruId 只返回一次
    local objs = class_mgnt('FruData'):get_all()
    local fru_id
    for _, obj in pairs(objs) do
        fru_id = obj['FruId']
        if not fruid_seen[fru_id] then
            fruid_seen[fru_id] = true
            table.insert(res, format_frudata_info(fru_id, obj))
        end
    end
    return res
end

return frudata_service
