-- 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 c_object = require 'mc.orm.object'
local c_valve_object = require 'valve_object'
local log = require 'mc.logging'
local skynet = require 'skynet'
local valve_enums = require 'valve_enums'
local client = require 'thermal_mgmt.client'
local mdb = require 'mc.mdb'

local valves_manager = c_object('Valves')


local MANAGERS_PATH <const> = '/bmc/kepler/Managers/'
local MANAGER_ID<const> = 1

local function get_location_id()
    local path = MANAGERS_PATH .. tostring(MANAGER_ID) .. "/NodeLocation"
    local obj = client:GetNodeLocationObjects()[path]
    if not obj then
        log:error('[thermal_mgmt]get LocationId failed')
        return nil
    end
    return obj.LocationId
end

function valves_manager:get_patrol_state()
    return self.PatrolState
end

function valves_manager:set_patrol_state(state)
    self.PatrolState = state
end

function valves_manager:get_last_patrol_timestamp()
    return self.LastPatrolTimestamp
end

function valves_manager:update_last_patrol_timestamp()
    self.LastPatrolTimestamp = os.time()
end

function valves_manager:get_patrol_type()
    return self.PatrolType
end

function valves_manager:get_invalid_location_id()
    return self.InvalidLocationId
end

local function is_time_to_patrol(datetime_obj, location_id)
    local current_day = datetime_obj['day']
    local current_hour = datetime_obj['hour']

    -- 每月1日根据槽位偏移设定巡检时间，槽位1=1点，槽位2=2点...
    local patrol_hour = location_id % 24 -- 用槽位号对24小时取余
    local patrol_day = math.modf(location_id / 24) -- 用槽位号对24小时取整

    if current_hour == patrol_hour and current_day == patrol_day + 1 then
        return true
    end

    return false
end

-- 检查是否是装备包
local function is_manufacture_mode()
    local ok, _ = pcall(require, 'thermal_mgmt_manufacture')
    if ok then
        return true
    end

    return false
end

function valves_manager:regular_patrol()
    local last_patrol_timestamp, last_patrol_datetime_obj
    local current_datetime_obj, current_timestamp
    local location_id
    local patrol_result

    log:notice('[thermal_mgmt] valves regular patrol started')

    while true do
        -- 巡检任务尚未结束需要继续等待
        if self:get_patrol_state() ~= valve_enums.Patrol_State.Patrol_State_Idle then
            goto continue
        end

        -- 获取当前系统时间
        current_timestamp = os.time()
        current_datetime_obj = os.date("*t", current_timestamp)

        if not current_datetime_obj then
            log:error('[thermal_mgmt] can not parse current time object')
            goto continue
        end

        -- 获取最近一次巡检时间
        last_patrol_timestamp = self:get_last_patrol_timestamp()

        -- 当可以获取最近一次巡检时间时判断是否处于最近一次巡检当月
        if last_patrol_timestamp then
            -- 当前系统时间小于最近一次巡检时间可能发生时间回退（如启动阶段1970-01-01），此时跳过巡检
            if current_timestamp < last_patrol_timestamp then
                goto continue
            end

            last_patrol_datetime_obj = os.date("*t", last_patrol_timestamp)
            -- 处于最近巡检当月
            if current_datetime_obj['year'] == last_patrol_datetime_obj['year'] and current_datetime_obj['month'] == last_patrol_datetime_obj['month'] then
                goto continue
            end
        end

        -- 读取槽位信息
        location_id = get_location_id()
        if location_id == nil then
            goto continue
        elseif location_id >= self:get_invalid_location_id() then
            patrol_result = valve_enums.Patrol_Result.Failed
        else
            patrol_result = valve_enums.Patrol_Result.Success
        end

        -- 根据槽位获取情况决定是否需要告警
        c_valve_object.collection:fold(function (_, valve_obj)
            if valve_obj:is_present() ~= true then
                -- 不在位会有相应告警，此处无需处置
                return
            end
            valve_obj:set_patrol_result(patrol_result)
        end)

        -- 无效槽位直接跳过巡检
        if patrol_result ~= valve_enums.Patrol_Result.Success then
            goto continue
        end

        if is_time_to_patrol(current_datetime_obj, location_id) then
            --随机时间延时后巡检开始: 0~30min
            self:start_patrol(true, math.random(0, 1800))
        end

        ::continue::
        self:sleep_ms(60000)     -- 按分钟间隔检查巡检时间
    end
end

function valves_manager:start_patrol(regular_flag, delay_seconds)
    log:notice("[thermal_mgmt] regular_flag = %s, delay_seconds = %s", regular_flag, delay_seconds)
    local patrol_action = false
    skynet.fork_once(function()
        -- 巡检延时，进入Ready状态
        self:set_patrol_state(valve_enums.Patrol_State.Patrol_State_Ready)
        self:sleep_ms(delay_seconds * 1000)

        -- 开始巡检
        self:set_patrol_state(valve_enums.Patrol_State.Patrol_State_Running)

        c_valve_object.collection:fold(function (_, valve_obj)
            if valve_obj:is_present() ~= true then
                -- 不在位会有相应告警，此处无需处置
                log:notice("valve(slot-%s) is not present", valve_obj:get_slot())
                return
            end
            if valve_obj:check_opening_degree(valve_obj:get_opening_degree(), valve_enums.OPENING_DEGREE_MIN, 30) then
                -- 判断关断阀是否关闭，如果关闭则不巡检
                log:notice("valve(slot-%s) is closed", valve_obj:get_slot())
                return
            end
            patrol_action = true
            valve_obj:set_patrol_result(valve_obj:do_patrol())  -- 执行巡检动作, 并更新巡检结果
        end)

        -- 只有定期巡检且存在阀门实际执行巡检动作时才会更新最近一次巡检时间
        if regular_flag and patrol_action then
            self:update_last_patrol_timestamp()
        end

        -- 巡检结束
        self:set_patrol_state(valve_enums.Patrol_State.Patrol_State_Idle)
    end)
end

function valves_manager:init()
    self:connect_signal(self.on_add_object, function()
    end)
    self:connect_signal(self.on_add_object_complete, function()
        log:notice('[thermal_mgmt] valves on_add_object_complete PatrolType: %d, InvalidLocationId: %s',
            self.PatrolType, self.InvalidLocationId)
        -- 不支持例行巡检时直接返回
        if self:get_patrol_type() == 0 then
            return
        end

        self:set_patrol_state(valve_enums.Patrol_State.Patrol_State_Idle)
        -- 非装备模式启动自动巡检
        if not is_manufacture_mode()then
            self:next_tick(self.regular_patrol, self)
        end
    end)
    valves_manager.super.init(self)
end

return valves_manager
