package com.aps.macroplanner.objective;

import com.google.ortools.linearsolver.MPConstraint;
import com.google.ortools.linearsolver.MPObjective;
import com.google.ortools.linearsolver.MPSolver;
import com.aps.macroplanner.data.KPIWeights;
import com.aps.macroplanner.data.TestDataBuilder;
import com.aps.macroplanner.model.MacroPlannerModel;

/**
 * 目标函数构建器 — 加权惩罚最小化
 *
 * <p>将多个 KPI 汇总变量按权重加权求和, 通过最小化该加权和来优化。
 * 这是原始 Quintiq 模型中"非层级化目标"的等价实现。</p>
 *
 * <h3>数学公式</h3>
 * <pre>
 * Minimize  Σ (weight × KPI_TotalVariable)
 *
 * 展开:
 *   obj = 100.0 × TotalFulfillment          (需求满足 — 最高优先级)
 *       + 10.0 × TotalLotSize               (批次偏差)
 *       +  5.0 × TotalMaxInventoryLevel      (超库存)
 *       +  5.0 × TotalMinInventoryLevel      (欠库存)
 *       +  8.0 × TotalTargetInvLevel         (目标库存偏差)
 *       +  3.0 × TotalUnitCapacity           (产能超载)
 *       +  8.0 × TotalSupplyTarget           (供应目标偏差)
 *       +  5.0 × TotalMinSupply              (最小供应不足)
 *       +  5.0 × TotalMaxSupply              (最大供应超出)
 *       -  1.0 × TotalSalesDemandPriority    (销售需求优先级 — 最大化, 加负号)
 *       + 20.0 × TotalPostponementPenalty    (推迟惩罚 — 简化版=0)
 *       +  5.0 × TotalProcessMaxQuantity     (过程最大量 — 简化版=0)
 * </pre>
 *
 * <h3>方向说明</h3>
 * 所有 KPI 汇总变量都是非负的"惩罚量", 所以目标是最小化。
 * 唯一例外: TotalSalesDemandPriority 是"收益量" (越大越好),
 * 所以使用负系数 (-1.0), 在最小化目标中等价于最大化。
 *
 * <h3>权重调优原则</h3>
 * - 权重越大, 该项在目标函数中优先级越高
 * - 例如: Fulfillment=100 远大于 LotSize=10,
 *   优化器会优先满足需求, 即使这意味着批次偏差更大
 * - 权重为 0 表示该项不影响目标 (但约束仍然存在)
 */
public class ObjectiveBuilder {

    /**
     * 构建目标函数。
     * 必须在所有 KPI 汇总变量创建之后调用 (即 KpiAggregator.build() 之后)。
     *
     * @param model 模型容器 (提供所有 KPI 汇总变量)
     * @param data  测试数据 (提供 KPI 权重)
     */
    public static void build(MacroPlannerModel model, TestDataBuilder data) {
        MPObjective objective = model.getSolver().objective();
        KPIWeights w = data.getKpiWeights();

        // ===== 惩罚项 (最小化: 正系数) =====

        // Fulfillment: 需求松弛惩罚, 权重 100.0 (最高优先级)
        // 含义: 所有周期所有产品的需求松弛总和。值越小表示需求满足越好。
        objective.setCoefficient(model.getTotalFulfillment(), w.getFulfillmentWeight());

        // LotSize: 批次偏差惩罚, 权重 10.0
        // 含义: 所有操作所有周期的批次大小偏差总和。惩罚不按批次生产的量。
        objective.setCoefficient(model.getTotalLotSize(), w.getLotSizeWeight());

        // MaxInventoryLevel: 超库存惩罚, 权重 5.0
        // 含义: 所有超标库存的总和。惩罚库存超过最大水平。
        objective.setCoefficient(model.getTotalMaxInventoryLevel(), w.getMaxInventoryLevelWeight());

        // MinInventoryLevel: 欠库存(安全库存)惩罚, 权重 5.0
        // 含义: 所有低于最小库存的总和。惩罚库存低于安全水平。
        objective.setCoefficient(model.getTotalMinInventoryLevel(), w.getMinInventoryLevelWeight());

        // TargetInventoryLevel: 目标库存偏差惩罚, 权重 8.0
        // 含义: 所有低于目标库存的总和。惩罚库存偏离目标水平。
        objective.setCoefficient(model.getTotalTargetInvLevel(), w.getTargetInventoryLevelWeight());

        // UnitCapacity: 产能超载惩罚, 权重 3.0
        // 含义: 所有设备所有周期的产能超载总和。惩罚超负荷生产。
        objective.setCoefficient(model.getTotalUnitCapacity(), w.getUnitCapacityWeight());

        // SupplyTarget: 供应目标偏差惩罚, 权重 8.0
        // 含义: 供应低于目标的总缺口。惩罚供应不足。
        objective.setCoefficient(model.getTotalSupplyTarget(), w.getSupplyTargetWeight());

        // MinSupply: 最小供应不足惩罚, 权重 5.0
        // 含义: 供应低于最小值的总缺口。惩罚供应严重不足。
        objective.setCoefficient(model.getTotalMinSupply(), w.getMinSupplyWeight());

        // MaxSupply: 最大供应超出惩罚, 权重 5.0
        // 含义: 供应超出最大值的总量。惩罚过度供应。
        objective.setCoefficient(model.getTotalMaxSupply(), w.getMaxSupplyWeight());

        // ===== 收益项 (最大化: 负系数, 因为 OR-Tools 默认最小化) =====

        // SalesDemandPriority: 销售需求优先级, 权重 1.0, 负系数 = 最大化
        // 含义: 按优先级加权的销售需求满足量。值越大表示满足的需求越多越好。
        // 因目标函数是最小化, 此项使用负系数 (最大化 = 加负号后最小化)。
        objective.setCoefficient(model.getTotalSalesDemandPriority(), -w.getSalesDemandPriorityWeight());

        // PostponementPenalty: 推迟惩罚, 权重 20.0 (简化版固定为 0)
        // 含义: 对应原模型中的延迟销售需求惩罚。当前版本未实现推迟逻辑。
        objective.setCoefficient(model.getTotalPostponementPenalty(), w.getPostponementPenaltyWeight());

        // ProcessMaxQuantity: 过程最大量惩罚, 权重 5.0 (简化版固定为 0)
        // 含义: 对应原模型中超出最大过程量的惩罚。当前版本未实现。
        objective.setCoefficient(model.getTotalProcessMaxQuantity(), w.getProcessMaxQuantityWeight());

        // 设置为最小化目标
        objective.setMinimization();
    }

    // ==================== 分层优化方法 ====================

    /**
     * 清除目标函数中所有 KPI 变量的系数 (置零)。
     *
     * <p>在分层优化中, 每次切换层级时需要先清除上一层的目标,
     * 然后设置当前层的目标系数。OR-Tools 旧版 API 没有 clear() 方法,
     * 因此显式将所有 KPI 系数置零。</p>
     *
     * @param model 模型容器
     */
    public static void clearObjective(MacroPlannerModel model) {
        MPObjective objective = model.getSolver().objective();
        // 将所有 KPI 变量的系数置零
        objective.setCoefficient(model.getTotalFulfillment(), 0.0);
        objective.setCoefficient(model.getTotalLotSize(), 0.0);
        objective.setCoefficient(model.getTotalMaxInventoryLevel(), 0.0);
        objective.setCoefficient(model.getTotalMinInventoryLevel(), 0.0);
        objective.setCoefficient(model.getTotalTargetInvLevel(), 0.0);
        objective.setCoefficient(model.getTotalUnitCapacity(), 0.0);
        objective.setCoefficient(model.getTotalSupplyTarget(), 0.0);
        objective.setCoefficient(model.getTotalMinSupply(), 0.0);
        objective.setCoefficient(model.getTotalMaxSupply(), 0.0);
        objective.setCoefficient(model.getTotalSalesDemandPriority(), 0.0);
        objective.setCoefficient(model.getTotalPostponementPenalty(), 0.0);
        objective.setCoefficient(model.getTotalProcessMaxQuantity(), 0.0);
    }

    /**
     * 设置当前层级的目标函数 — 只包含该层级的 KPI。
     *
     * <p>调用前应先调用 {@link #clearObjective(MacroPlannerModel)} 清除上层目标。</p>
     *
     * @param model 模型容器
     * @param level 策略层级 (包含该层级的 KPI 列表)
     */
    public static void setLevelObjective(MacroPlannerModel model, StrategyLevel level) {
        MPObjective objective = model.getSolver().objective();
        for (StrategyLevel.KPIEntry kpi : level.getKpis()) {
            objective.setCoefficient(kpi.variable, kpi.effectiveCoefficient());
        }
        objective.setMinimization();
    }

    /**
     * 添加层级边界约束 — 限制上层目标值不超过最优值 × (1 + slack)。
     *
     * <p>该约束确保在求解下层 KPI 时, 上层 KPI 不会退化超过允许范围。
     * 对应 Quintiq 中 StrategyLevel 的 HierarchicalSolver 约束。</p>
     *
     * <h3>数学公式</h3>
     * <pre>
     *   Σ (effectiveCoeff × KPI_variable) ≤ optimalValue × (1 + relativeGoalSlack)
     * </pre>
     *
     * @param model        模型容器
     * @param level        策略层级 (提供 KPI 列表和松弛比例)
     * @param optimalValue 该层级在上一轮求解中的最优目标值
     */
    public static void addLevelBoundConstraint(MacroPlannerModel model,
                                               StrategyLevel level,
                                               double optimalValue) {
        if (level.getRelativeGoalSlack() < 0.0) return; // 负松弛表示不约束

        MPSolver solver = model.getSolver();
        double upperBound = optimalValue * (1.0 + level.getRelativeGoalSlack());

        MPConstraint bound = solver.makeConstraint(
                -MPSolver.infinity(), upperBound,
                "HierLevel" + level.getLevel() + "_Bound");

        for (StrategyLevel.KPIEntry kpi : level.getKpis()) {
            bound.setCoefficient(kpi.variable, kpi.effectiveCoefficient());
        }
    }
}