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

---@module unit_manager.drive_start_slot_helper
---DriveStartSlotConfig（硬盘起始槽位配置）匹配与更新逻辑的通用辅助模块。
---供以下三个时机复用：
---  1. DriveStartSlotConfig 对象添加
---  2. HddBackplane/M2TransferCard 背板对象添加
---  3. PSR add_object_complete 触发
---
---槽位连续性校验规则：
---  ChildStartSlot[i] + 背板槽位个数 == ChildStartSlot[i+1]
---背板槽位个数从 HddBackplane/M2TransferCard 的 SlotCount 属性获取。

local cmn = require 'common'
local log = require 'mc.logging'

local drive_start_slot_helper = {}

-- MDS 接口名，与 model.json 一致，用于 get_prop 第二参数
local IF_BOARD <const> = 'bmc.kepler.Systems.Board'
local IF_BOARD_UNIT <const> = 'bmc.kepler.Systems.Board.Unit'
local IF_HDD_BACKPLANE <const> = 'bmc.kepler.Systems.HddBackplane'

-- 历史兼容说明：
--  - 0xFF：部分背板（如部分 M2 X 场景）用于“不走 Drive.update_drive_slot_info，只按 CSR 固定 Name/Id/NodeId” 的语义，
--          PSR 匹配更新不应改写 0xFF
--  - 0xFE：作为“待 PSR 更新”的标志位，仅当 StartSlot=0xFE 时才参与 DriveStartSlotConfig 匹配/兜底
local START_SLOT_WAIT_PSR <const> = 0xFE
local LOG_PREFIX <const> = '[drive_start_slot_helper]'

-- DriveStartSlotConfigItem 下标（与 types.json 定义顺序一致：UID, Slot, ChildSlotType, ChildSlotIndex, ChildStartSlot）
local DSCI_UID <const> = 1
local DSCI_SLOT <const> = 2
local DSCI_CHILD_SLOT_TYPE <const> = 3
local DSCI_CHILD_SLOT_INDEX <const> = 4
local DSCI_CHILD_START_SLOT <const> = 5

---检查背板与单个 DriveStartSlotConfig.Configurations 条目是否匹配，返回匹配到的 ChildStartSlot 下标。
---@param container_uid string 容器 UID，用于与配置项 UID 匹配
---@param parent_slot any 父单板的 Slot，用于与配置项 Slot 匹配
---@param backplane_type any 背板类型（已由调用方获取）
---@param backplane_slot any 背板在父单板下的槽位号（已由调用方获取）
---@param configuration table 单个配置项（按下标：1=UID,2=Slot,3=ChildSlotType,4=ChildSlotIndex,5=ChildStartSlot）
---@return integer|nil matched_index 若匹配成功返回下标，否则返回 nil
function drive_start_slot_helper.find_matching_configuration_index(container_uid, parent_slot, backplane_type, backplane_slot, configuration)
    if container_uid ~= configuration[DSCI_UID] or configuration[DSCI_SLOT] ~= parent_slot or
        backplane_type ~= configuration[DSCI_CHILD_SLOT_TYPE] then
        return nil
    end

    local child_slot_index = configuration[DSCI_CHILD_SLOT_INDEX]
    local child_start_slot = configuration[DSCI_CHILD_START_SLOT]
    if not child_slot_index or not child_start_slot or #child_slot_index ~= #child_start_slot then
        return nil
    end
    return cmn.find(child_slot_index, backplane_slot)
end

---执行配置连续性校验：ChildStartSlot[i] + 背板槽位个数 == ChildStartSlot[i+1]。
---相邻背板起始槽位差值需等于对应背板的槽位数量。
---@param configuration table 单个配置项（按下标：5=ChildStartSlot）
---@param matched_index integer 当前背板在 ChildStartSlot/ChildSlotIndex 中匹配到的下标
---@param backplane_slot_count integer|nil 背板槽位个数
---@param backplane table 背板对象，仅用于日志
function drive_start_slot_helper.check_configuration_continuity(configuration, matched_index, backplane_slot_count, backplane)
    local child_start_slot = configuration[DSCI_CHILD_START_SLOT]
    if not child_start_slot or matched_index >= #child_start_slot then
        return true
    end

    local expected_next = (child_start_slot[matched_index] or 0) + backplane_slot_count
    local actual_next = child_start_slot[matched_index + 1]
    if expected_next ~= actual_next then
        log:info('%s continuity fail, expected_next=%s actual_next=%s slot_count=%s idx=%s pos=%s',
            LOG_PREFIX, expected_next, actual_next, backplane_slot_count, matched_index, backplane and backplane.position or '?')
        return false
    end
    return true
end

---根据 DriveStartSlotConfig 配置计算指定背板应设置的 StartSlot。
---父单板对象由调用方解析并传入，通过其 UID + 背板 Slot 进行匹配。
---@param backplane table 背板对象
---@param drive_start_slot_configs table DriveStartSlotConfig 对象列表
---@param parent_unit table|nil 上一级单板对象（实现 bmc.kepler.Systems.Board.Unit 接口）
---@return integer resolved_start_slot 计算得到的 StartSlot，匹配失败或异常场景返回 0
function drive_start_slot_helper.calculate_start_slot_from_config(backplane, drive_start_slot_configs, parent_unit)
    if not drive_start_slot_configs or not next(drive_start_slot_configs) then
        log:debug('%s resolve no config, pos=%s', LOG_PREFIX, backplane and backplane.position or '?')
        return 0
    end
    local container = parent_unit
    if not container then
        log:info('%s resolve fail, no parent unit object, pos=%s', LOG_PREFIX,
            backplane and backplane.position or '?')
        return 0
    end

    local container_uid = container:get_prop('UID', IF_BOARD_UNIT)
    local parent_slot = container:get_prop('Slot', IF_BOARD)
    if not container_uid or parent_slot == nil then
        log:info('%s resolve fail, parent unit missing UID or Slot, pos=%s', LOG_PREFIX,
            backplane and backplane.position or '?')
        return 0
    end
    local slot_count = backplane:get_prop('SlotCount', IF_HDD_BACKPLANE)
    if slot_count == nil or slot_count == 0 or slot_count == 0xff then
        log:debug('%s resolve fail, slot_count=%s pos=%s', LOG_PREFIX, slot_count, backplane.position)
        return 0
    end
    local backplane_slot = backplane:get_prop('Slot', IF_BOARD)
    local backplane_type = backplane:get_prop('Type', IF_BOARD_UNIT)

    for _, config in pairs(drive_start_slot_configs) do
        local configurations = config.mds_obj and config.mds_obj.Configurations
        if not configurations then
            goto continue
        end
        for _, config_item in pairs(configurations) do
            local matched_index = drive_start_slot_helper.find_matching_configuration_index(
                container_uid, parent_slot, backplane_type, backplane_slot, config_item)
            if matched_index then
                if drive_start_slot_helper.check_configuration_continuity(config_item, matched_index, slot_count, backplane) then
                    local start_slot = config_item[DSCI_CHILD_START_SLOT] and config_item[DSCI_CHILD_START_SLOT][matched_index]
                    if start_slot ~= nil then
                        log:debug('%s match ok, uid=%s type=%s slot=%s -> start_slot=%s pos=%s',
                            LOG_PREFIX, container_uid, backplane_type, backplane_slot, start_slot, backplane.position)
                        return start_slot
                    end
                end
                log:info('%s match ok but continuity fail, uid=%s type=%s slot=%s pos=%s',
                    LOG_PREFIX, container_uid, backplane_type, backplane_slot, backplane.position)
                return 0
            end
        end
        ::continue::
    end
    log:debug('%s no match, uid=%s type=%s slot=%s pos=%s', LOG_PREFIX, container_uid, backplane_type, backplane_slot, backplane.position)
    return 0
end

---更新背板的 StartSlot（仅当当前为 0xFE，即“待 PSR 更新”状态时）。
---@param backplane table 背板对象
---@param drive_start_slot_configs table DriveStartSlotConfig 对象列表
---@param psr_add_object_complete_fired boolean PSR add_object_complete 是否已触发
---@return boolean updated 是否发生了更新
function drive_start_slot_helper.update_backplane_start_slot_if_pending(backplane, drive_start_slot_configs,
    psr_add_object_complete_fired, parent_unit)
    local current = backplane:get_prop('StartSlot', IF_HDD_BACKPLANE)
    if current ~= START_SLOT_WAIT_PSR then
        log:debug('%s skip, StartSlot:%s not configurable pos=%s', LOG_PREFIX, current, backplane.position)
        return false
    end
    local new_slot
    if drive_start_slot_configs and next(drive_start_slot_configs) then
        new_slot = drive_start_slot_helper.calculate_start_slot_from_config(backplane, drive_start_slot_configs,
            parent_unit)
    elseif psr_add_object_complete_fired then
        new_slot = 0
        log:debug('%s fallback to 0, no config and complete fired, pos=%s', LOG_PREFIX, backplane.position)
    else
        log:debug('%s skip, no config and complete not fired, waiting, pos=%s', LOG_PREFIX, backplane.position)
        return false
    end
    backplane:set_prop('StartSlot', new_slot, IF_HDD_BACKPLANE)
    log:notice('%s StartSlot updated: %s, position: %s', LOG_PREFIX, new_slot, backplane.position)
    return true
end

-- 注：不在本模块中直接处理“遍历所有背板并解析父单板”的逻辑，
-- 由 unit_manager 等上层组件负责根据 position 解析 parent_unit，并调用
-- update_backplane_start_slot_if_pending(backplane, configs, fired, parent_unit)。

drive_start_slot_helper.START_SLOT_WAIT_PSR = START_SLOT_WAIT_PSR

return drive_start_slot_helper
