package com.aps.macroplanner.util;

import com.aps.macroplanner.data.Period;

import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.logging.Level;
import java.util.logging.Logger;

/**
 * 安全库存天数折算计算器 — 将 "N 天安全库存" 折算为对未来周期 DemandFulfillment 的加权求和。
 *
 * <h2>核心公式</h2>
 * <pre>
 * 安全库存天数 = N 天
 * 折算结果: Σ(ratio_i × DemandFulfillment_i)
 *
 * 其中 ratio_i = min(remainingDays, p_i.duration) / p_i.duration
 * </pre>
 *
 * <h2>使用场景</h2>
 * <ul>
 *   <li>库存规格约束 (InventorySpec) — 最小/最大/目标库存以天为单位</li>
 *   <li>KPI 汇总 — 安全库存天数偏差计算</li>
 *   <li>结果分析 — 排查库存异常时追溯需求预测的来源</li>
 * </ul>
 *
 * <h2>日志输出</h2>
 * 当启用 FINE 级别日志时, 会输出每个周期的折算详情, 便于排查:
 * <pre>
 * [SafetyStock] PISPIP=PA_SP1, 当前周期=P0, 安全库存天数=3.00
 *   P1: 剩余  3.0天 → 消耗  1.0天/全覆盖周期(1.00) → 剩余  2.0天
 *   P2: 剩余  2.0天 → 消耗  1.0天/全覆盖周期(1.00) → 剩余  1.0天
 *   P3: 剩余  1.0天 → 消耗  1.0天/全覆盖周期(1.00) → 剩余  0.0天
 *   → 总覆盖天数: 3.0天, 涉及 3 个未来周期
 * [SafetyStock] 折算结果: DFulfill_PA_SP1_1×1.00 + DFulfill_PA_SP1_2×1.00 + DFulfill_PA_SP1_3×1.00
 * </pre>
 */
public class SafetyStockCalculator {

    private static final Logger LOG = Logger.getLogger(SafetyStockCalculator.class.getName());

    /**
     * 计算安全库存天数对应的 DemandFulfillment 折算项。
     *
     * <p>从当前周期的下一个周期开始向后遍历, 直到覆盖完指定的安全库存天数,
     * 或遍历完所有可用周期。每个未来周期按比例贡献其 DemandFulfillment。</p>
     *
     * @param currentPeriodIdx 当前周期在 periods 列表中的索引
     * @param safetyStockDays  安全库存天数 (如 3.0 表示 3 天)
     * @param periods          全部周期列表 (按时间顺序)
     * @param invKey           当前库存的 key (productId_spId_periodIndex),
     *                         用于生成 DemandFulfillment 变量 key
     * @return 折算项列表, 按周期顺序排列。如果所有未来周期都用完了仍不够覆盖,
     *         返回已覆盖的项 (末尾周期可能 ratio < 1.0)
     */
    public static List<SafetyStockTerm> calculate(
            int currentPeriodIdx,
            double safetyStockDays,
            List<Period> periods,
            String invKey) {

        if (safetyStockDays <= 0) {
            LOG.fine("[SafetyStock] 安全库存天数 <= 0, 跳过计算");
            return Collections.emptyList();
        }

        List<SafetyStockTerm> terms = new ArrayList<>();
        double remainingDays = safetyStockDays;
        double totalCoveredDays = 0;

        if (LOG.isLoggable(Level.FINE)) {
            LOG.fine(String.format(
                    "[SafetyStock] PISPIP=%s, 当前周期=%s(%d), 安全库存天数=%.2f天, 开始折算...",
                    stripPeriodIndex(invKey), getPeriodName(periods, currentPeriodIdx),
                    currentPeriodIdx, safetyStockDays));
        }

        // 从下一个周期开始遍历
        for (int i = currentPeriodIdx + 1; i < periods.size() && remainingDays > 0; i++) {
            Period nextPeriod = periods.get(i);
            double periodDuration = nextPeriod.getDurationInDays();

            // ratio = 该周期对安全库存天数的贡献比例
            // 如果剩余天数 >= 周期长度, 整个周期都算进去 (ratio = 1.0)
            // 如果剩余天数 < 周期长度, 只算部分 (ratio = remainingDays / periodDuration)
            double ratio = Math.min(remainingDays, periodDuration) / periodDuration;
            double contributedDays = ratio * periodDuration;
            double remainingBefore = remainingDays;
            remainingDays -= periodDuration;

            String dfKey = replacePeriodIndex(invKey, nextPeriod.getIndex());

            SafetyStockTerm term = new SafetyStockTerm(
                    nextPeriod, ratio, remainingBefore, Math.max(0, remainingDays), dfKey);
            terms.add(term);
            totalCoveredDays += contributedDays;

            LOG.fine(term.toString());
        }

        // 汇总日志
        if (remainingDays > 0) {
            LOG.warning(String.format(
                    "[SafetyStock] PISPIP=%s, 安全库存天数=%.2f天, 但未来周期不足! "
                    + "已覆盖=%.2f天, 缺口=%.2f天 (未来只有 %d 个周期可用)",
                    stripPeriodIndex(invKey), safetyStockDays, totalCoveredDays,
                    remainingDays, periods.size() - currentPeriodIdx - 1));
        } else {
            if (LOG.isLoggable(Level.FINE)) {
                LOG.fine(String.format(
                        "[SafetyStock] PISPIP=%s → 总覆盖天数: %.2f天, 涉及 %d 个未来周期",
                        stripPeriodIndex(invKey), totalCoveredDays, terms.size()));
            }
        }

        // 折算公式汇总
        if (LOG.isLoggable(Level.FINE)) {
            StringBuilder sb = new StringBuilder();
            sb.append(String.format("[SafetyStock] 折算公式: InvQty ≥ "));
            for (int i = 0; i < terms.size(); i++) {
                if (i > 0) sb.append(" + ");
                SafetyStockTerm t = terms.get(i);
                sb.append(String.format("DFulfill_%s×%.2f",
                        t.getDemandFulfillmentKey(), t.getRatio()));
            }
            LOG.fine(sb.toString());
        }

        return terms;
    }

    /**
     * 替换 key 中的周期索引。
     * 例如: "PA_SP1_0" + 新索引 2 → "PA_SP1_2"
     */
    static String replacePeriodIndex(String key, int newIndex) {
        int lastUnderscore = key.lastIndexOf('_');
        if (lastUnderscore < 0) return key;
        return key.substring(0, lastUnderscore + 1) + newIndex;
    }

    /**
     * 去掉 key 中的周期索引, 只保留 productId_spId。
     * 例如: "PA_SP1_0" → "PA_SP1"
     */
    private static String stripPeriodIndex(String key) {
        int lastUnderscore = key.lastIndexOf('_');
        if (lastUnderscore < 0) return key;
        return key.substring(0, lastUnderscore);
    }

    /**
     * 根据周期索引获取周期名称。
     */
    private static String getPeriodName(List<Period> periods, int index) {
        if (index >= 0 && index < periods.size()) {
            return periods.get(index).getName();
        }
        return "?";
    }
}