package com.aps.macroplanner.constraint;

import com.google.ortools.linearsolver.MPConstraint;
import com.google.ortools.linearsolver.MPVariable;
import com.aps.macroplanner.data.*;
import com.aps.macroplanner.model.MacroPlannerModel;

import java.util.Map;

/**
 * 物料平衡约束构建器 (Balance)
 *
 * <p>确保每个 PISPIP (Product In StockingPoint In Period) 的物料流守恒:
 * 流入 = 流出</p>
 *
 * <h3>数学公式</h3>
 * <pre>
 * Σ(PTQty×relDur) + DemandSlack + InvQty(t-1) + 在途到货
 *   - ΣSalesDemandQty - DependentDemand - InvQty(t) = 初始库存(t=0)
 * </pre>
 */
public class BalanceConstraint {

    /**
     * 构建物料平衡约束。
     */
    public static void build(MacroPlannerModel model, TestDataBuilder data) {
        Map<String, MPVariable> ptQtyVars = model.getPtQtyVars();
        Map<String, MPVariable> invQtyVars = model.getInvQtyVars();
        Map<String, MPVariable> salesDemandQtyVars = model.getSalesDemandQtyVars();
        Map<String, MPVariable> demandSlackVars = model.getDemandSlackVars();
        Map<String, MPVariable> dependentDemandVars = model.getDependentDemandVars();

        for (Product prod : data.getProducts()) {
            for (StockingPoint sp : data.getStockingPointsForProduct(prod.getId())) {
                for (Period p : data.getPeriods()) {
                    String invKey = prod.getId() + "_" + sp.getId() + "_" + p.getIndex();

                    MPConstraint balance = model.getSolver().makeConstraint(0.0, 0.0,
                            "Balance_" + prod.getId() + "_" + sp.getId() + "_P" + p.getIndex());

                    // === 流入 ===
                    // PTQty 产出 (考虑提前期: productionDate 生产, productionDate+leadTimeDays 到货)
                    // 关键: 只统计产出到当前库存点(sp)的操作, 避免多工序路由中产出到错误的库存点
                    for (Operation op : data.getOperations()) {
                        if (!op.producesProductAtSp(prod.getId(), sp.getId())) continue;
                        int leadTimeDays = op.getLeadTimeDays();
                        Period productionPeriod = data.getPeriodOffsetByDays(p, leadTimeDays);
                        if (productionPeriod == null) continue;
                        for (UnitOperation uo : op.getUnitOperations()) {
                            MPVariable ptVar = ptQtyVars.get(op.ptQtyKey(uo, productionPeriod.getIndex()));
                            if (ptVar != null) balance.setCoefficient(ptVar, op.getRelativeDuration());
                        }
                    }
                    // DemandSlack
                    MPVariable slackVar = demandSlackVars.get(invKey);
                    if (slackVar != null) balance.setCoefficient(slackVar, 1.0);
                    // 上一周期库存 (t>0)
                    if (p.getIndex() > 0) {
                        String prevKey = prod.getId() + "_" + sp.getId() + "_" + (p.getIndex() - 1);
                        MPVariable prevVar = invQtyVars.get(prevKey);
                        if (prevVar != null) balance.setCoefficient(prevVar, 1.0);
                    }

                    // === 流出 ===
                    // SalesDemandQty
                    for (SalesDemand sd : data.getSalesDemandsFor(prod, sp, p)) {
                        MPVariable sdVar = salesDemandQtyVars.get(sd.getKey());
                        if (sdVar != null) balance.setCoefficient(sdVar, -1.0);
                    }
                    // DependentDemandInPISPIP
                    MPVariable ddVar = dependentDemandVars.get(invKey);
                    if (ddVar != null) balance.setCoefficient(ddVar, -1.0);
                    // InvQty
                    MPVariable invVar = invQtyVars.get(invKey);
                    if (invVar != null) balance.setCoefficient(invVar, -1.0);

                    // === RHS ===
                    double rhs = 0.0;

                    // 初始库存 (t=0)
                    if (p.getIndex() == 0) {
                        rhs -= data.getInitialInventory(prod.getId(), sp.getId());
                    }

                    // 在途供应 (供应商已发货, 固定到货量, 按日期匹配周期)
                    for (InTransitSupply its : data.getInTransitSupplies()) {
                        if (its.getProduct().getId().equals(prod.getId())
                                && its.getStockingPoint().getId().equals(sp.getId())
                                && p.equals(data.getPeriodByDate(its.getArrivalDate()))) {
                            rhs -= its.getQuantity();
                        }
                    }

                    balance.setBounds(rhs, rhs);
                }
            }
        }
    }
}