-- 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.

-- Description: the database procedure for sensor application.
local log = require 'mc.logging'
local utils = require 'sensor_utils'
local queue = require 'skynet.queue'
local or_ = require('database.statement').or_

local sensor_database = {}
sensor_database.__index = sensor_database

function sensor_database.new(db, local_db, reset_db)
    return setmetatable({
        db = db,
        local_db = local_db,
        reset_db = reset_db,
        data_queue = queue()
    }, sensor_database)
end

function sensor_database:query_sel_info()
    local obj = self.db:select(self.db.SensorSelInfo):first()
    if not obj then
        return nil
    end

    return obj.__datas
end

function sensor_database:query_sel_count(table_name)
    local count = self.local_db[table_name]:get_count()
    if not count then
        count = 0
    end
    return count
end


function sensor_database:query_sel_max_record_seq()
    local t = self.local_db.IpmiSelList
    return self.local_db:select(t):order_by(t.RecordSeq, true):first()
end

function sensor_database:query_sel_max_timestamp()
    local t = self.local_db.IpmiSelList
    local record = self.local_db:select(t):order_by(t.Timestamp, true):first()
    if not record then
        return nil
    end
    -- 时间戳单位1s可能存在重复，需要确定最新记录
    local records = self.local_db:select(t):where({Timestamp = record.Timestamp}):all()
    table.sort(records, function(a, b)
        return a.RecordId < b.RecordId
    end)

    for i, _ in ipairs(records) do
        -- 最后一条或者下一条SEL的RecordId不连续时即代表最新
        if i == #records or records[i + 1].RecordId - records[i].RecordId ~= 1 then
            return records[i]
        end
    end
end

function sensor_database:delete_sel(start_id, end_id, table_name)
    local sel_list_db = self.local_db[table_name]
    local conds = {}
    conds[#conds+1] = sel_list_db.RecordId:ge(start_id)
    conds[#conds+1] = sel_list_db.RecordId:le(end_id)
    self.local_db:delete(sel_list_db):where(table.unpack(conds)):exec()
end

local query_handler = {
    [utils.GET_SEL_RULE] = function (self, t, start_id, count)
        start_id = start_id == 0 and 1 or start_id
        -- 查询符合查询条件的全部事件，直接基于当前的start_id查询
        return self.local_db:select(t):order_by(t.RecordId):limit(count):offset(start_id - 1):all()
    end,
    [utils.REPORT_SEL_RULE] = function (self, t, start_id, count)
        local conds = {}
        conds[#conds + 1] = t.RecordSeq:ge(start_id)
        return self.local_db:select(t):where(table.unpack(conds)):order_by(t.RecordSeq):limit(count):all()
    end
}
function sensor_database:query_sel(rule, start_id, count, table_name)
    if not query_handler[rule] then
        log:error('Invalid query rule[%s]', rule)
        return {}
    end

    local t = self.local_db[table_name]
    local records = query_handler[rule](self, t, start_id, count)
    if not records or #records == 0 then
        return {}
    end
    local events = {}
    for i = 1, count do
        if not records[i] then
            break
        end
        table.insert(events, 1, records[i].__datas)
    end
    return events
end

function sensor_database:query_sel_list(rid)
    local record = self.local_db.IpmiSelList({RecordId = rid})
    if not next(record.__datas) then
        return nil
    end
    return record.__datas
end

function sensor_database:insert_sel_list(sel, table_name)
    local ok, ret = pcall(function ()
        local record = self.local_db[table_name](sel)
        if not record then
            log:error('Insert sel(id:%s) is failed', sel.RecordId)
            return false
        end
        record:save()
        return true
    end)
    if not ok then
        log:error('Insert sel(id:%s) is failed:%s', sel.RecordId, ret)
        return false
    end
    return ret
end

function sensor_database:delete_sel_list(rid, table_name)
    local record = self.local_db[table_name]({RecordId = rid})
    if not next(record.__datas) then
        return
    end
    record:delete()
end

function sensor_database:insert_pef_config(cfg)
    local record = self.db.IpmiPefConfig({
        Enabled = cfg.Enabled,
        ActionEnabled = cfg.ActionEnabled,
        StartupDelayDisabled = cfg.StartupDelayDisabled,
        AlertStartupDelayDisabled = cfg.AlertStartupDelayDisabled,
        AlertEnabled = cfg.AlertEnabled,
        PowerOffEnabled = cfg.PowerOffEnabled,
        ResetEnabled = cfg.ResetEnabled,
        PowerCycleEnabled = cfg.PowerCycleEnabled,
        OEMEnabled = cfg.OEMEnabled,
        DiagInterruptEnabled = cfg.DiagInterruptEnabled,
        SystemGUIDEnabled = cfg.SystemGUIDEnabled,
        StartupDelay = cfg.StartupDelay,
        AlertStartupDelay = cfg.AlertStartupDelay,
        SystemGUID = cfg.SystemGUID,
        Id = cfg.Id
    })
    if not record then
        log:error('insert pef configuration is invalid')
        return
    end
    record:save()
end

function sensor_database:query_pef_control()
    local record = self.db:select(self.db.IpmiPefControl):first()
    if not record then
        return nil
    end
    return record.__datas
end

function sensor_database:insert_pef_control(ctrl)
    local record = self.db.IpmiPefControl({
        InProgress = ctrl.InProgress,
        PostponeTimeout = ctrl.PostponeTimeout,
        StartupDelayTID = ctrl.StartupDelayTID,
        Action = ctrl.InProgress,
        UnqueuedEvent = ctrl.UnqueuedEvent,
        LastEventBMC = ctrl.LastEventBMC,
        LastEventSMS = ctrl.LastEventSMS,
        NextEvent = ctrl.NextEvent,
        Id = ctrl.Id
    })
    if not record then
        log:error('insert pef control configuration is invalid')
        return
    end
    record:save()
end

function sensor_database:insert_persist_property(id, prop, value)
    local property_table = self.local_db.PersistProperty
    local record = property_table({PerId = id, Key = prop})
    if next(record.__datas) then
        record.Value = value
    else
        record = property_table({PerId = id, Key = prop, Value = value})
    end
    record:save()
end

function sensor_database:query_persist_property(id, prop)
    local property_table = self.local_db.PersistProperty
    local record = property_table({PerId = id, Key = prop})

    if not next(record.__datas) then
        return nil
    end
    return record.__datas
end

function sensor_database:insert_sel_msg(msg)
    self.data_queue(function ()
        self.reset_db.SelMsgList(msg):save()
    end)
    return true
end

function sensor_database:delete_sel_msg(record)
    if not record then
        log:error('delete msg is invalid')
    end
    record:delete()
end

function sensor_database:get_sel_msg(index)
    local records = self.reset_db:select(self.reset_db.SelMsgList):all()
    if not records or #records == 0 then
        return nil
    end
    return records[index]
end

function sensor_database:query_all_sel_msgs()
    local records = self.reset_db:select(self.reset_db.SelMsgList):all()
    if not records or #records == 0 then
        return nil
    end
    return records
end

function sensor_database:query_sel_msg(mode, msg)
    local sel_msg_db = self.reset_db.SelMsgList

    local conditions = {
        [utils.SENSOR_TYPE_MODE] = function ()
            return {sel_msg_db.SensorType:eq(msg.SensorType)}
        end,
        [utils.SENSOR_ID_MODE] = function ()
            return {sel_msg_db.SensorId:eq(msg.SensorId)}
        end,
        [utils.SEL_MSG_MODE] = function ()
            local conds = {}
            conds[#conds+1] = sel_msg_db.SensorType:eq(msg.SensorType)
            conds[#conds+1] = sel_msg_db.SensorId:eq(msg.SensorId)
            conds[#conds+1] = sel_msg_db.EventData1:eq(msg.EventData1)
            if msg.EventData2 ~= 0xFF then
                conds[#conds+1] = sel_msg_db.EventData2:eq(msg.EventData2)
            end
            if msg.EventData3 ~= 0xFF then
                conds[#conds+1] = sel_msg_db.EventData3:eq(msg.EventData3)
            end
            return conds
        end,
        [utils.SENSOR_EVENT_MODE] = function ()
            return {sel_msg_db.SensorType:eq(msg.SensorType),
                    sel_msg_db.EventType:eq(msg.EventType)}
        end
    }

    if not conditions[mode] then
        log:warn('sel no matching mode [%d]', mode)
        return nil
    end

    local conds = conditions[mode]()
    local records = self.reset_db:select(sel_msg_db):where(table.unpack(conds)):all()
    if not records or #records == 0 then
        log:info('query sel msg by mode [%d] failed', mode)
        return nil
    end

    return records
end

function sensor_database:query_sel_filter(sensor_type, reading_type, data)
    local t = self.db.IpmiSelFilter
    local conds = {
        or_(t.SensorType:eq(sensor_type), t.SensorType:eq(0x00)),
        t.ReadingType:eq(reading_type)
    }
    local records = self.db:select(t):where(table.unpack(conds)):all()
    if not records or not next(records)then
        return nil
    end

    local record = nil
    for _, v in ipairs(records) do
        if v.__datas.FilterMask1 & (1 << (data & 0x0F)) ~= 0 then
            record = v.__datas
            break
        end
    end
    return record
end

function sensor_database:query_sel_desc(sensor_type, reading_type, data)
    local t = self.db.IpmiSelDesc
    local conds = (reading_type == 0x6F) and
        { t.SensorType:eq(sensor_type), t.SelData1:eq(data) } or
        { t.ReadingType:eq(reading_type), t.SelData1:eq(data) }

    local records = self.db:select(t):where(table.unpack(conds)):all()
    if not records or not next(records) then
        return nil
    end

    local rs = {}
    for _, v in ipairs(records) do
        table.insert(rs, v.__datas)
    end
    return rs
end

return sensor_database