package com.aps.macroplanner.constraint;

import com.google.ortools.linearsolver.MPConstraint;
import com.google.ortools.linearsolver.MPSolver;
import com.google.ortools.linearsolver.MPVariable;
import com.aps.macroplanner.data.*;
import com.aps.macroplanner.model.MacroPlannerModel;
import com.aps.macroplanner.util.SafetyStockCalculator;
import com.aps.macroplanner.util.SafetyStockTerm;

import java.util.List;
import java.util.Map;
import java.util.logging.Logger;

/**
 * 库存规格约束构建器 (InventorySpecConstraint)
 *
 * <p>为每个产品/库存点/周期定义三个库存水平约束,
 * 支持两种模式: 绝对数量模式 和 安全库存天数模式。</p>
 *
 * <h3>绝对数量模式</h3>
 * <pre>
 * 最小库存: InvQty + MinInvQtyUnder ≥ MinLevel         (固定数量)
 * 最大库存: InvQty - MaxInvQtyOver ≤ MaxLevel
 * 目标库存: InvQty + InvQtyUnderTarget = TargetLevel
 * </pre>
 *
 * <h3>安全库存天数模式</h3>
 * <pre>
 * 委托给 {@link SafetyStockCalculator} 进行折算。
 * 最小库存: InvQty + MinInvQtyUnder ≥ Σ(ratio_i × DemandFulfillment_i)
 * 最大库存: InvQty - MaxInvQtyOver ≤ Σ(ratio_i × DemandFulfillment_i)
 * 目标库存: InvQty + InvQtyUnderTarget = Σ(ratio_i × DemandFulfillment_i)
 * </pre>
 */
public class InventorySpecConstraint {

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

    /**
     * 构建库存规格约束 (自动判断绝对数量模式 vs 天数模式)。
     *
     * <p>在安全库存天数模式下, 会输出详细日志, 便于排查:
     * <ul>
     *   <li>每个 PISPIP 的安全库存天数折算过程</li>
     *   <li>约束添加的 DemandFulfillment 项及其系数</li>
     *   <li>绝对数量模式下跳过的详细信息</li>
     * </ul></p>
     */
    public static void build(MacroPlannerModel model, TestDataBuilder data) {
        Map<String, MPVariable> invVars = model.getInvQtyVars();
        Map<String, MPVariable> minUnderVars = model.getMinInvQtyUnderVars();
        Map<String, MPVariable> maxOverVars = model.getMaxInvQtyOverVars();
        Map<String, MPVariable> targetUnderVars = model.getInvQtyUnderTargetVars();
        Map<String, MPVariable> dfVars = model.getDemandFulfillmentVars();
        List<Period> periods = data.getPeriods();

        int inDaysCount = 0;
        int absoluteCount = 0;

        for (InventorySpec spec : data.getInventorySpecs()) {
            String invKey = spec.getKey();
            MPVariable invVar = invVars.get(invKey);
            if (invVar == null) continue;

            int periodIdx = spec.getPeriod().getIndex();
            boolean usedInDays = false;

            // === 最小库存 ===
            if (spec.hasMinLevel()) {
                if (spec.hasMinLevelInDays()) {
                    usedInDays = true;
                    // 安全库存天数模式: RHS=0, 由 DemandFulfillment 项提供 RHS
                    MPConstraint c = model.getSolver().makeConstraint(
                            0.0, MPSolver.infinity(), "MinInv_" + invKey);
                    c.setCoefficient(invVar, 1.0);
                    MPVariable u = minUnderVars.get(invKey);
                    if (u != null) c.setCoefficient(u, 1.0);

                    LOG.info(String.format("[MinInv_天] %s: 最小库存=%d天, 开始折算...",
                            invKey, (int) spec.getMinLevelInDays()));
                    addDemandFulfillmentTerms(c, invKey, dfVars, periods, periodIdx,
                            spec.getMinLevelInDays(), "MinInv");
                } else {
                    // 绝对数量模式
                    double rhs = spec.getMinLevel();
                    MPConstraint c = model.getSolver().makeConstraint(
                            rhs, MPSolver.infinity(), "MinInv_" + invKey);
                    c.setCoefficient(invVar, 1.0);
                    MPVariable u = minUnderVars.get(invKey);
                    if (u != null) c.setCoefficient(u, 1.0);
                    absoluteCount++;
                }
            }

            // === 最大库存 ===
            if (spec.hasMaxLevel()) {
                if (spec.hasMaxLevelInDays()) {
                    usedInDays = true;
                    MPConstraint c = model.getSolver().makeConstraint(
                            -MPSolver.infinity(), 0.0, "MaxInv_" + invKey);
                    c.setCoefficient(invVar, 1.0);
                    MPVariable o = maxOverVars.get(invKey);
                    if (o != null) c.setCoefficient(o, -1.0);

                    LOG.info(String.format("[MaxInv_天] %s: 最大库存=%d天",
                            invKey, (int) spec.getMaxLevelInDays()));
                    addDemandFulfillmentTerms(c, invKey, dfVars, periods, periodIdx,
                            spec.getMaxLevelInDays(), "MaxInv");
                } else {
                    double rhs = spec.getMaxLevel();
                    MPConstraint c = model.getSolver().makeConstraint(
                            -MPSolver.infinity(), rhs, "MaxInv_" + invKey);
                    c.setCoefficient(invVar, 1.0);
                    MPVariable o = maxOverVars.get(invKey);
                    if (o != null) c.setCoefficient(o, -1.0);
                    absoluteCount++;
                }
            }

            // === 目标库存 ===
            if (spec.hasTarget()) {
                if (spec.hasTargetInDays()) {
                    usedInDays = true;
                    MPConstraint c = model.getSolver().makeConstraint(
                            0.0, 0.0, "TargetInv_" + invKey);
                    c.setCoefficient(invVar, 1.0);
                    MPVariable u = targetUnderVars.get(invKey);
                    if (u != null) c.setCoefficient(u, 1.0);

                    LOG.info(String.format("[TargetInv_天] %s: 目标库存=%d天",
                            invKey, (int) spec.getTargetInDays()));
                    addDemandFulfillmentTerms(c, invKey, dfVars, periods, periodIdx,
                            spec.getTargetInDays(), "TargetInv");
                } else {
                    double rhs = spec.getTargetLevel();
                    MPConstraint c = model.getSolver().makeConstraint(
                            rhs, rhs, "TargetInv_" + invKey);
                    c.setCoefficient(invVar, 1.0);
                    MPVariable u = targetUnderVars.get(invKey);
                    if (u != null) c.setCoefficient(u, 1.0);
                    absoluteCount++;
                }
            }

            if (usedInDays) inDaysCount++;
        }

        LOG.info(String.format("[库存规格] 构建完成: %d 个使用安全库存天数, %d 个使用绝对数量",
                inDaysCount, absoluteCount));
    }

    /**
     * 向约束添加 DemandFulfillment 项 (委托给 {@link SafetyStockCalculator})。
     *
     * <p>添加负系数项, 效果是:
     * InvQty + Slack ≥ Σ(ratio × DemandFulfillment)</p>
     *
     * @param constraint      库存约束
     * @param invKey          库存 key (productId_spId_periodIndex)
     * @param dfVars          DemandFulfillment 变量映射
     * @param periods         周期列表
     * @param currentPeriodIdx 当前周期索引
     * @param safetyStockDays 安全库存天数
     * @param constraintType  约束类型 (MinInv / MaxInv / TargetInv), 仅用于日志
     */
    private static void addDemandFulfillmentTerms(
            MPConstraint constraint,
            String invKey,
            Map<String, MPVariable> dfVars,
            List<Period> periods,
            int currentPeriodIdx,
            double safetyStockDays,
            String constraintType) {

        // 委托给工具类计算折算项
        List<SafetyStockTerm> terms = SafetyStockCalculator.calculate(
                currentPeriodIdx, safetyStockDays, periods, invKey);

        if (terms.isEmpty()) {
            LOG.warning(String.format("[%s] %s: 安全库存天数=%.2f天, 但没有找到未来周期!",
                    constraintType, invKey, safetyStockDays));
            return;
        }

        // 将折算项添加到约束
        int termsAdded = 0;
        for (SafetyStockTerm term : terms) {
            MPVariable dfVar = dfVars.get(term.getDemandFulfillmentKey());
            if (dfVar != null) {
                // 负系数: -ratio × DemandFulfillment
                // 约束: InvQty + Slack ≥ 0
                // 移项: InvQty + Slack ≥ Σ(ratio × DemandFulfillment)
                constraint.setCoefficient(dfVar, -term.getRatio());
                termsAdded++;
            } else {
                LOG.warning(String.format("[%s] %s: DemandFulfillment 变量 '%s' 不存在! "
                        + "周期 %s 的需求预测未计入安全库存约束",
                        constraintType, invKey, term.getDemandFulfillmentKey(),
                        term.getPeriodName()));
            }
        }

        LOG.fine(String.format("[%s] %s: 已添加 %d/%d 个 DemandFulfillment 项到约束",
                constraintType, invKey, termsAdded, terms.size()));
    }
}