-- 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 canbus_base = require 'device_tree.adapters.power_mgmt.protocol.monitor.canbus'
local log = require 'mc.logging'
local utils_core = require 'utils.core'

local E_OK = nil -- 函数执行成功返回nil
local E_FAILED = '' -- 空错误信息
local QUERY_INFO_OK<const> = 0
local QUERY_INFO_FAIL<const> = 1
local BLACK_BOX_REFRESH_INTERVAL <const> = 24 * 60 * 60 --黑匣子刷新间隔周期24小时
local ONE_BLACK_BOX_RECORD_LEN <const> = 120
local MAX_BLACK_BOX_RECORD_INDEX <const> = 29
local MAX_BLACK_BOX_RECORD_NUM <const> = 30

local function get_pbit(val)
    return 1 << val
end

return function (obj)
    local psu_slot = canbus_base(obj)

    function psu_slot:health_check(query_info)
        self:update_health_event(query_info)
        self.scan_status = self.scan_status | get_pbit(0)
        self:dal_refresh_property('scan_status', self.scan_status)
    end

    function psu_slot:refresh_batch_query_info()
        -- 0x40 批量查询信息
        local batch_query_info = self.protocol['refresh_batch_query_info']
        local batch_query_info_ok, batch_query_info_resp = pcall(batch_query_info, self.protocol)
        if batch_query_info_ok then
            log:info('[power_mgmt]ps%d refresh_batch_query_info ok', self.ps_id)
            for prop, value in pairs(batch_query_info_resp) do
                self:dal_refresh_property(prop, value)
            end
            self:health_check(batch_query_info_resp)
            self.protocol:canbus_ms_channel_switch(QUERY_INFO_OK)
        else
            self.protocol:canbus_ms_channel_switch(QUERY_INFO_FAIL)
            log:info('[power_mgmt]ps%s refresh_batch_query_info failed, response = %s', self.ps_id,
                        batch_query_info_resp)
        end
    end

    function psu_slot:update_health_event(query_info)
        if not query_info then
            return
        end
        local alarm_status = query_info.AlarmStatus
        local pre_Alarm_Status = query_info.PreAlarmStatus
        local event = 0
        local output_voltage_fault = 0
        local input_voltage_fault = 0
        -- 输出欠压
        if (alarm_status & get_pbit(10)) > 0 then
            --canbus协议AlarmStatus属性bit10为1，代表输出欠压。但是根据电源告警，若输出欠压，OutputVoltageFault bit4需要置为1
            output_voltage_fault = output_voltage_fault | get_pbit(4)
        end
        -- 输出欠压预告警
        if (pre_Alarm_Status & get_pbit(5)) > 0 then
            --canbus协议pre_Alarm_Status属性bit5为1，代表输出欠压预告警。但是根据电源告警，若输出欠压预告警，OutputVoltageFault bit5需要置为1
            output_voltage_fault = output_voltage_fault | get_pbit(5)
        end
        -- 输出过压预告警
        if (pre_Alarm_Status & get_pbit(4)) > 0 then
            --canbus协议AlarmStatus属性bit4为1，代表输出过压预告警。但是根据电源告警，若输出过压预告警，OutputVoltageFault bit6需要置为1
            output_voltage_fault = output_voltage_fault | get_pbit(6)
        end
        -- 输出过压
        if (alarm_status & get_pbit(8)) > 0 then
            --canbus协议AlarmStatus属性bit8为1，代表输出过压。但是根据电源告警，若输出过压，OutputVoltageFault bit7需要置为1
            output_voltage_fault = output_voltage_fault | get_pbit(7)
        end

        -- 输入欠压
        if (alarm_status & get_pbit(1)) > 0 then
            -- canbus协议AlarmStatus属性bit1为1，代表输入欠压。但是根据电源告警，若输入欠压，InputVoltageFault bit4需要置为1
            input_voltage_fault = input_voltage_fault | get_pbit(4)
            event = event | get_pbit(4)
        end
        -- 输入欠压预告警
        if (pre_Alarm_Status & get_pbit(1)) > 0 then
            --canbus协议pre_Alarm_Status属性bit1为1，代表输入欠压预告警。但是根据电源告警，欠压预告警，InputVoltageFault bit5需要置为1
            input_voltage_fault = input_voltage_fault | get_pbit(5)
        end
        -- 输入过压预告警
        if (pre_Alarm_Status & get_pbit(0)) > 0 then
            --canbus协议AlarmStatus属性bit0为1，代表输入过压预告警。但是根据电源告警，过压预告警，InputVoltageFault bit6需要置为1
            input_voltage_fault = input_voltage_fault | get_pbit(6)
        end
        -- 输入过压
        if (alarm_status & get_pbit(0)) > 0 then
            -- 根据canbus协议AlarmStatus属性bit0为1，代表输入过压。但是根据电源告警，若输入过压，InputVoltageFault bit7需要置为1
            input_voltage_fault = input_voltage_fault | get_pbit(7)
            event = event | get_pbit(4)
        end

        -- 过温告警
        local over_temper_fault = 0
        if (alarm_status & get_pbit(16)) > 0 then
            -- 根据canbus协议AlarmStatus属性bit16为1，代表过温。但是根据电源告警，若过温，OverTemperature bit7需要置为1
            over_temper_fault = over_temper_fault | get_pbit(7)
            event = event | get_pbit(3)
        end
        -- 过温预告警
        if (pre_Alarm_Status & get_pbit(16)) > 0 then
            -- 根据canbus协议pre_Alarm_Status属性bit16为1，代表过温。但是根据电源告警，若过温，OverTemperature bit6需要置为1
            over_temper_fault = over_temper_fault | get_pbit(6)
        end

        -- 输出过流
        local output_over_current = 0
        if (alarm_status & get_pbit(9)) > 0 then
            -- 根据canbus协议AlarmStatus属性bit16为1，代表输出过流。但是根据电源告警，若输出过流，OutputCurrentFault bit7需要置为1
            output_over_current = output_over_current | get_pbit(7)
        end
        if (pre_Alarm_Status & get_pbit(8)) > 0 then
            -- 根据canbus协议alarm_status属性bit8为1，代表输出过流。但是根据电源告警，若输出过流，OutputCurrentFault bit5需要置为1
            output_over_current = output_over_current | get_pbit(5)
        end

        local canbus_fan_fault = 0
        if (alarm_status & get_pbit(17)) > 0 then
            -- 根据canbus协议AlarmStatus属性bit17为1，代表风扇错误。但是根据电源告警，若风扇错误，FanFault bit7需要置为1
            canbus_fan_fault = get_pbit(7)
        end
        if self.protocol:get_power_state() == 'Off' then
            alarm_status = alarm_status & 0xffff7fff7dffffff --turbo关机后屏蔽bit47 bit31 bit25告警
        end
        self:dal_refresh_property('health_event', {
            event = event,
            output_voltage_fault = output_voltage_fault,
            output_current_fault = output_over_current,
            temper_fault = over_temper_fault,
            input_voltage_fault = input_voltage_fault,
            fan_fault = canbus_fan_fault,
            turbo_status_high = (alarm_status & 0xffffffff00000000) >> 32,
            turbo_status_low = alarm_status & 0xffffffff
        })
        self:dal_refresh_property('health', event)
        return E_OK
    end

    function psu_slot:check_psu_lock_status()
        -- 电源锁死时，电源上报输出过压或者过流告警
        if self.prop_value and self.prop_value.health_event and (
            (self.prop_value.health_event.output_voltage_fault & get_pbit(7) ~= 0) or
            (self.prop_value.health_event.output_current_fault & get_pbit(7) ~= 0)) then
            return true
        end
        return false
    end

    function psu_slot:update_communication_status()
        local ok, _
        if self.refresh_info_nums < 10 then
            return
        end
        self.refresh_info_nums = 0
        ok, _ = pcall(function()
            return self.protocol:can_get_canbus_basic_info()
        end)
        if not ok then
            if self.read_fail_count < 4 then
                self.read_fail_count = self.read_fail_count + 1
            else
                self:dal_refresh_property('communication_status', 1)
            end
        else
            self.read_fail_count = 0
            self:dal_refresh_property('communication_status', 0)
        end
    end

    function psu_slot:psm_task_power_mode()
        local ret, rsp, _
        local read_fail_count = 0
        while true do
            rsp, _ = self:get_dynamic_data('sleep_mode')
            ret = ret or rsp
            if ret ~= E_OK then
                if read_fail_count < 3 then
                    read_fail_count = read_fail_count + 1
                else
                    self.scan_status = self.scan_status & ~get_pbit(3)
                end
            else
                read_fail_count = 0
                self.scan_status = self.scan_status | get_pbit(3)
            end
            self:dal_refresh_property('scan_status', self.scan_status)
            ret = nil
            self:sleep_ms(2000)
        end
    end

    function psu_slot:psm_task_send_heart_beat()
        local ok
        while true do
            if not self.protocol or not self.protocol.send_heart_beat then
                goto continue
            end
            for _ = 1, 3 do
                ok = pcall(self.protocol.send_heart_beat, self.protocol)
                if ok then
                    goto continue
                end
                self:sleep_ms(100)
            end
            ::continue::
            self:sleep_ms(10000)
        end
    end

    function psu_slot:psm_task_refresh_capacitor_info()
        while true do
            self:get_dynamic_data('hardware_version', 1)
            self:get_dynamic_data('power_state', 1)
            self:get_dynamic_data('capacity_microfarads', 1)
            self:get_dynamic_data('vout_debounce_milliseconds', 1)
            self:get_dynamic_data('depth_of_discharge_volts', 1)
            self:get_dynamic_data('relay_switched_count', 1)
            self:get_dynamic_data('output_power_limit_watts', 1)
            self:sleep_ms(60000) -- 1分钟刷新一次电容相关信息
        end
    end

    function psu_slot:psm_task_circuit_status()
        while true do
            self:get_dynamic_data('main_circuit_vin_status', 1)
            self:get_dynamic_data('backup_circuit_vin_status', 1)
            self:sleep_time(1000, 1000)
       end
    end

    function psu_slot:get_black_box_event_time(one_black_box_record)
        local event_time_str = one_black_box_record:sub(1, 4)
        -- 黑匣子数据为空时，事件时间为全FF
        if event_time_str == "\xFF\xFF\xFF\xFF" then
            return nil
        end
        return string.unpack('<I4', event_time_str)
    end

    function psu_slot:get_black_box_event(one_black_box_record)
        local event_str = one_black_box_record:sub(5, 12)
        return string.unpack('<I8', event_str)
    end

    function psu_slot:get_black_box_old_idx_by_event_info(event_time, event)
        for index, one_black_box_record in ipairs(self.black_box_data) do
            if one_black_box_record.event_time == event_time and one_black_box_record.event == event then
                return index
            end
        end
        -- 0表示当前内存中找不到对应黑匣子数据
        return 0
    end

    function psu_slot:get_black_box_data()
        local ok, black_box_len, black_box_data = pcall(self.protocol.get_event_log, self.protocol)
        if not ok then
            log:error('Get event log failed, psid %s, err info: %s', self.ps_id, black_box_len)
            return
        end
        local one_black_box_record, event_time, event
        local new_black_box_data = {}
        for i = 1, #black_box_data // ONE_BLACK_BOX_RECORD_LEN do
            one_black_box_record = black_box_data:sub(
                (i - 1) * ONE_BLACK_BOX_RECORD_LEN + 1, i * ONE_BLACK_BOX_RECORD_LEN)
            event_time = self:get_black_box_event_time(one_black_box_record)
            if not event_time then
                break
            end
            event = self:get_black_box_event(one_black_box_record)
            new_black_box_data[#new_black_box_data + 1] = {
                event_time = event_time,
                event = event,
                black_box_data = one_black_box_record
            }
        end
        self.black_box_data = new_black_box_data
        log:notice('Get black box data successfully, ps_id: %s', self.ps_id)
    end

    function psu_slot:refresh_blask_box_data()
        local ok, one_black_box_record, event_time, event, new_black_box_data, remain_valid_num
        -- 计算新增黑匣子数据条数
        local new_record_num = 0
        for i = MAX_BLACK_BOX_RECORD_INDEX, 0, -1 do
            ok, one_black_box_record = pcall(self.protocol.get_black_box_data, self.protocol, i)
            if not ok then
                log:error('Get black box data failed, psid %s, err info: %s', self.ps_id, one_black_box_record)
                return
            end
            event_time = self:get_black_box_event_time(one_black_box_record)
            if event_time then
                event = self:get_black_box_event(one_black_box_record)
                new_record_num = i + 1 - self:get_black_box_old_idx_by_event_info(event_time, event)
                break
            end
        end
        if new_record_num < 1 then
            goto continue
        end
        -- 获取新增的黑匣子数据，并维护在内存
        new_black_box_data = {}
        for i = 1, new_record_num, 1 do
            ok, one_black_box_record = pcall(self.protocol.get_black_box_data, self.protocol, i - 1)
            if not ok then
                log:error('Get black box data failed, psid %s, err info: %s', self.ps_id, one_black_box_record)
                return
            end
            event_time = self:get_black_box_event_time(one_black_box_record)
            if not event_time then
                log:error('Get black box event time failed, psid %s', self.ps_id)
                return
            end
            event = self:get_black_box_event(one_black_box_record)
            new_black_box_data[#new_black_box_data + 1] = {
                event_time = event_time,
                event = event,
                black_box_data = one_black_box_record
            }
        end
        -- 计算上次内存中黑匣子数据剩余的有效数量
        remain_valid_num = new_record_num + #self.black_box_data > MAX_BLACK_BOX_RECORD_NUM and
            MAX_BLACK_BOX_RECORD_NUM * 2 - new_record_num - #self.black_box_data or #self.black_box_data
        if remain_valid_num < 1 then
            goto continue
        end
        -- 将剩余有效黑匣子数据合并维护在内存中
        for i = 1, remain_valid_num, 1 do
            new_black_box_data[#new_black_box_data + 1] = self.black_box_data[i]
        end
        self.black_box_data = new_black_box_data
        ::continue::
        log:notice('Refresh black box data successfully, new record num: %s, ps_id: %s', new_record_num, self.ps_id)
    end

    function psu_slot:get_event_log()
        if self.refresh_black_box_task then
            self.refresh_black_box_task:stop()
            self.refresh_black_box_task = nil
        end
        if not next(self.black_box_data) then
            self:get_black_box_data()
        else
            self:refresh_blask_box_data()
        end
        local ret, uptime = pcall(utils_core.get_bmc_time)
        if ret then
            self.last_fresh_time = uptime
        end
        local black_box_data_table = {}
        for _, item in ipairs(self.black_box_data) do
            table.insert(black_box_data_table, item.black_box_data)
        end
        -- 对于黑匣子数据未记录满的场景补充默认数据
        if #self.black_box_data < MAX_BLACK_BOX_RECORD_NUM then
            for _ = 1, MAX_BLACK_BOX_RECORD_NUM - #self.black_box_data, 1 do
                table.insert(black_box_data_table, string.rep("\xFF", ONE_BLACK_BOX_RECORD_LEN))
            end
        end
        return self.protocol.protocol.CANBUS_BLACK_BOX_MAX_LEN, table.concat(black_box_data_table)
    end

    function psu_slot:task_refresh_blask_box_data()
        -- 内存维护的黑匣子数据
        self.black_box_data = self.black_box_data or {}
        -- 上一次刷新黑匣子数据的时间
        self.last_fresh_time = self.last_fresh_time or 0
        -- 黑匣子数据获取或刷新任务
        self.refresh_black_box_task = nil
        local ret, uptime
        while true do
            ret, uptime = pcall(utils_core.get_bmc_uptime)
            if not ret then
                goto continue
            end
            if self.refresh_black_box_task then
                goto continue
            end
            if not next(self.black_box_data) then
                self.refresh_black_box_task = self:next_tick(function ()
                    self:get_black_box_data()
                    self.refresh_black_box_task = nil
                    self.last_fresh_time = uptime
                end)
                goto continue
            end
            if uptime - self.last_fresh_time > BLACK_BOX_REFRESH_INTERVAL then
                self.refresh_black_box_task = self:next_tick(function ()
                    self:refresh_blask_box_data()
                    self.refresh_black_box_task = nil
                    self.last_fresh_time = uptime
                end)
            end
           ::continue::
           self:sleep_time(600000, 600000)
       end
    end

    local PS_TRY_CNT = 30 -- 重试次数
    function psu_slot:power_monitor_entry()
        self.refresh_info_nums = 0
        self.read_fail_count = 0
        self:add_monitor_tasks(function()
            while true do
                self:refresh_batch_query_info()
                self.scan_status = self.scan_status | get_pbit(2) | get_pbit(1)
                self:dal_refresh_property('scan_status', self.scan_status)
                self.refresh_info_nums = self.refresh_info_nums + 1
                self:update_communication_status()
                self:sleep_time(1000, 1000)
            end
        end)
        self:add_monitor_tasks(
            function()
                self:time_service_task()
            end
        )
        self:add_monitor_tasks(
            function()
                self:psm_task_circuit_status()
            end
        )
        self:add_monitor_tasks(
            function()
                while true do
                    self.protocol:canbus_recover_to_main_channel()
                    self:sleep_time(30000, 30000)
                end
            end
        )
        self:add_monitor_tasks(
            function ()
                self:psm_task_power_mode()
            end
        )
        self:add_monitor_tasks(
            function ()
                self:psm_task_send_heart_beat()
            end
        )
        self:add_monitor_tasks(
            function ()
                self:psm_task_refresh_capacitor_info()
            end
        )
        self:add_monitor_tasks(
            function ()
                self:task_refresh_blask_box_data()
            end
        )
        self.scan_status = self.scan_status | get_pbit(3)
        self:dal_refresh_property('scan_status', self.scan_status)
        self:get_dynamic_data('rate', PS_TRY_CNT)
    end
    return psu_slot
end