-- 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 log = require 'mc.logging'
local signal = require 'mc.signal'
local utils = require 'mc.utils'
local file_sec = require 'utils.file'
local utils_core = require 'utils.core'
local log_collector = require 'device.class.log_collector'
local card_defs = require 'device.class.network_card_adapter.card_defs'

local card_resource_smbus_update = {}

local function update_health(resource_obj)
    local s = resource_obj.smbus_config_obj:Health()
    s.on_data_change:on(function(data)
        if data ~= 0 then
            log:error('card:%s, is not in normal state, health_status: %s', resource_obj.NodeId, data)
        end
    end)
    table.insert(resource_obj.smbus_schedulers, s)
    s:start()
end

local function update_fault_code(resource_obj)
    local s = resource_obj.smbus_config_obj:FaultCode()
    s.on_data_change:on(function(data)
        if resource_obj.health ~= 0 and not utils.table_compare(resource_obj.last_fault_code, data) then
            resource_obj.last_fault_code = data
            -- 故障告警只用第一个故障码
            if data and #data > 0 then
                resource_obj.FaultState = data[1]
            end
            if resource_obj.smbus_collect_status == card_defs.LOG_DUMP_IDLE then
                log:info('netcard fault occur')
                resource_obj.smbus_collect_status = card_defs.LOG_DUMP_BUSY
                card_resource_smbus_update.collect_log_by_smbus_task(resource_obj)
                resource_obj.smbus_collect_status = card_defs.LOG_DUMP_IDLE
                resource_obj.smbus_has_collected = true
            end
        end
    end)
    table.insert(resource_obj.smbus_schedulers, s)
    s:start()
end

local function update_chip_temp(resource_obj)
    local s = resource_obj.smbus_config_obj:ChipTemp()
    s.on_data_change:on(function(data)
        resource_obj.TemperatureCelsius = data
        resource_obj.TemperatureStatus = (resource_obj.smbios_status == 3 and data >= 32768) and 1 or 0
    end)
    table.insert(resource_obj.smbus_schedulers, s)
    s:start()
end

local function update_ports_property(s, ports, cb)
    local sig = signal.new()
    s.on_data_change:on(function(data)
        for i, val in ipairs(data) do
            sig:emit(i, val)
        end
    end)

    for port_id, port in ipairs(ports) do
        port:connect_signal(sig, function(i, ...)
            if port_id == i then
                cb(port, ...)
            end
        end)
    end
end

local function update_optical_temp(resource_obj, ports)
    local s = resource_obj.smbus_config_obj:OpticalTemp()
    update_ports_property(s, ports, function(port, temp)
        port:set_op_temp(temp)
    end)
    table.insert(resource_obj.smbus_schedulers, s)
    s:start()
end

local function update_smbus_link_status(resource_obj, ports)
    local s = resource_obj.smbus_config_obj:LinkStatus()
    update_ports_property(s, ports, function(port, status)
        port:set_link_status(status)
        port:set_link_status_numeric(status)
    end)
    table.insert(resource_obj.smbus_schedulers, s)
    s:start()
end

local function update_smbus_mac_address(resource_obj, ports)
    local s = resource_obj.smbus_config_obj:MacAddress()
    update_ports_property(s, ports, function(port, addr)
        port:set_mac_addr(addr)
        port:set_permanent_mac_addr(addr)
    end)
    table.insert(resource_obj.smbus_schedulers, s)
    s:start()
end

local function update_fw_ver_on_data_change(resource_obj, data, ports)
    if not data then return end
    if resource_obj.update_fw_ver_abnormal then
        log:notice('[network_adapter] update FirmwareVersion successfully, name:%s, FirmwareVersion:%s',
            resource_obj.NodeId, data)
            resource_obj.update_fw_ver_abnormal = false
    end
    resource_obj.FirmwareVersion = data
    for _, port in pairs(ports) do
        port:set_firmware_version(data)
    end
end

local function update_fw_ver_on_error(resource_obj)
    if not resource_obj.update_fw_ver_abnormal then
        log:error('[network_adapter] update FirmwareVersion on_error, name:%s', resource_obj.NodeId)
        resource_obj.update_fw_ver_abnormal = true
    end
end

local function update_fw_ver(resource_obj, ports)
    local s = resource_obj.smbus_config_obj:FirmwareVersion()
    s.on_data_change:on(function(data)
        update_fw_ver_on_data_change(resource_obj, data, ports)
    end)
    -- 消息发不通的时候
    s.on_error:on(function()
        update_fw_ver_on_error(resource_obj)
    end)

    table.insert(resource_obj.smbus_schedulers, s)
    s:start()
end

local function fetch_log(obj, log_prop_name, log_file)
    if not obj[log_prop_name] then
        log:info('the log prop %s doesn\'t exist, ignore it', log_prop_name)
        return
    end

    log:info('start to collect %s', log_prop_name)
    local fp_w, err = file_sec.open_s(log_file, 'wb+')
    if not fp_w then
        log:error('open file failed for %s, err: %s', log_prop_name, err)
        return
    end
    utils.safe_close_file(fp_w, function()
        utils_core.chmod_s(log_file, utils.S_IRUSR | utils.S_IWUSR | utils.S_IRGRP) -- 文件权限设置为0640

        local log_data = obj[log_prop_name](obj):value()
        fp_w:write(log_data)
    end)
    log:info('finish to collect %s', log_prop_name)
end

function card_resource_smbus_update.collect_log_by_smbus_task(resource_obj)
    local log_dir = log_collector.get_smbus_log_dir(resource_obj)
    if not log_dir then
        return
    end
    if not log_collector.create_dir(log_dir) then
        return
    end

    log:notice('start to dump smbus log for netcard, NodeId:%s', resource_obj.NodeId)
    local curr_log_files = {}

    -- 获取错误日志
    curr_log_files[#curr_log_files + 1] = 'error_log_' .. log_collector.get_time() .. '.bin'
    local ok = pcall(fetch_log, resource_obj.smbus_config_obj, 'ErrorLog',
        log_dir .. curr_log_files[#curr_log_files])
    if not ok then
        log:notice('cannot fetch error log')
    end

    -- 获取临终遗言日志
    curr_log_files[#curr_log_files + 1] = 'last_word_' .. log_collector.get_time() .. '.bin'
    ok = pcall(fetch_log, resource_obj.smbus_config_obj, 'LastWord',
        log_dir .. curr_log_files[#curr_log_files])
    if not ok then
        log:notice('cannot fetch last word')
    end

    -- 获取运行日志
    curr_log_files[#curr_log_files + 1] = 'running_log_' .. log_collector.get_time() .. '.bin'
    ok = pcall(fetch_log, resource_obj.smbus_config_obj, 'RunningLog',
        log_dir .. curr_log_files[#curr_log_files])
    if not ok then
        log:notice('cannot fetch running log')
    end

    -- 获取操作日志
    curr_log_files[#curr_log_files + 1] = 'operate_log_' .. log_collector.get_time() .. '.bin'
    ok = pcall(fetch_log, resource_obj.smbus_config_obj, 'OperateLog',
        log_dir .. curr_log_files[#curr_log_files])
    if not ok then
        log:notice('cannot fetch operate log')
    end

    -- 获取维护日志
    curr_log_files[#curr_log_files + 1] = 'maintenance_log_' .. log_collector.get_time() .. '.bin'
    ok = pcall(fetch_log, resource_obj.smbus_config_obj, 'MaintenanceLog',
        log_dir .. curr_log_files[#curr_log_files])
    if not ok then
        log:notice('cannot fetch maintenance log')
    end

    -- 删除老的日志
    log_collector.delete_old_log_file(log_dir, curr_log_files)
    log:notice('finish to dump log for netcard, NodeId:%s', resource_obj.NodeId)
end

function card_resource_smbus_update.update_smbus_properties(resource_obj, ports)
    resource_obj:next_tick(update_health, resource_obj)
    resource_obj:next_tick(update_fault_code, resource_obj)
    resource_obj:next_tick(update_chip_temp, resource_obj)
    resource_obj:next_tick(update_smbus_link_status, resource_obj, ports)
    resource_obj:next_tick(update_smbus_mac_address, resource_obj, ports)
    resource_obj:next_tick(update_optical_temp, resource_obj, ports)
    resource_obj:next_tick(update_fw_ver, resource_obj, ports)
end

return card_resource_smbus_update