package com.aps.macroplanner.constraint;

import com.aps.macroplanner.data.TestDataBuilder;
import com.aps.macroplanner.model.MacroPlannerModel;

import java.util.logging.Level;
import java.util.logging.Logger;

/**
 * 约束工厂 — 统一调度所有约束的构建。
 *
 * <p>设计原则: 与 {@link com.aps.macroplanner.model.VariableFactory} 对应,
 * 提供单一入口 {@link #buildAll(MacroPlannerModel, TestDataBuilder)},
 * 按逻辑顺序逐个委托给各约束构建器。</p>
 *
 * <h3>构建顺序 (必须严格遵循)</h3>
 * <pre>
 * 1. 物料平衡约束         — 核心约束, 确保流入=流出
 * 2. BOM 依赖需求         — 依赖 PTQty 变量, 定义物料消耗关系
 * 3. 需求满足量汇总       — 依赖 SalesDemandQty + OperationDemandQty
 * 4. 需求缺口联动         — 强制 SalesDemandQty + DemandSlack >= DemandQuantity
 * 5. 库存规格约束         — 依赖 InvQty + DemandFulfillment (in-days 模式)
 * 6. 产能约束             — 依赖 PTQty 变量, 限制设备使用
 * 7. 供应规格约束         — 依赖 PTQty 变量, 管理总供应量
 * 8. 批次大小约束         — 依赖 PTQty 变量, 控制批量生产
 * 9. KPI 汇总变量         — 依赖所有松弛变量, 汇总到目标函数
 * </pre>
 *
 * <p>注意: 各约束构建器之间通过 {@link MacroPlannerModel} 共享变量,
 * 因此构建顺序相对独立, 但建议按上述顺序以确保可读性。</p>
 */
public class ConstraintFactory {

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

    /**
     * 构建所有约束和 KPI 汇总变量。
     *
     * <p>每个步骤前后记录约束数量的变化, 便于验证每个构建器是否生效。</p>
     *
     * @param model 模型容器 (提供求解器和所有变量)
     * @param data  测试数据 (提供索引维度和规格参数)
     */
    public static void buildAll(MacroPlannerModel model, TestDataBuilder data) {
        LOG.info("========== 开始构建约束 ==========");

        int beforeConstraints = model.getSolver().numConstraints();
        int beforeVariables = model.getSolver().numVariables();

        // 1. 物料平衡约束 (核心约束, 必须最先创建)
        BalanceConstraint.build(model, data);
        logStep("1. 物料平衡", "确保每个 PISPIP 流入 = 流出",
                model, beforeConstraints, beforeVariables);
        beforeConstraints = model.getSolver().numConstraints();
        beforeVariables = model.getSolver().numVariables();

        // 2. BOM 依赖需求约束 (多级物料消耗)
        BomConstraint.build(model, data);
        logStep("2. BOM依赖需求", "OperationDemandQty = PTQty × BOM因子",
                model, beforeConstraints, beforeVariables);
        beforeConstraints = model.getSolver().numConstraints();
        beforeVariables = model.getSolver().numVariables();

        // 3. 需求满足量汇总 (安全库存天数计算的基础)
        DemandFulfillmentConstraint.build(model, data);
        logStep("3. 需求满足量汇总", "DemandFulfillment = SalesDemandQty + OperationDemandQty",
                model, beforeConstraints, beforeVariables);
        beforeConstraints = model.getSolver().numConstraints();
        beforeVariables = model.getSolver().numVariables();

        // 4. 需求缺口联动 (强制 SalesDemandQty + DemandSlack >= DemandQuantity)
        DemandSlackLinkageConstraint.build(model, data);
        logStep("4. 需求缺口联动", "SalesDemandQty + DemandSlack >= DemandQuantity",
                model, beforeConstraints, beforeVariables);
        beforeConstraints = model.getSolver().numConstraints();
        beforeVariables = model.getSolver().numVariables();

        // 4.5 工序产量一致性 (强制 Routing 内相邻工序 PTQty 相等, 防止 WIP 堆积)
        RoutingConstraint.build(model, data);
        logStep("4.5 工序产量一致", "Routing 内相邻工序 PTQty 相等, 防止 WIP 堆积",
                model, beforeConstraints, beforeVariables);
        beforeConstraints = model.getSolver().numConstraints();
        beforeVariables = model.getSolver().numVariables();

        // 5. 库存规格约束 (最小/最大/目标库存)
        InventorySpecConstraint.build(model, data);
        logStep("5. 库存规格", "InvQty + Slack ≥ Min/Max/Target (支持天数模式)",
                model, beforeConstraints, beforeVariables);
        beforeConstraints = model.getSolver().numConstraints();
        beforeVariables = model.getSolver().numVariables();

        // 6. 产能约束 (设备产能上下限)
        CapacityConstraint.build(model, data);
        logStep("6. 产能约束", "Σ(PTQty×coeff) ∈ [MinCapacity, MaxCapacity]",
                model, beforeConstraints, beforeVariables);
        beforeConstraints = model.getSolver().numConstraints();
        beforeVariables = model.getSolver().numVariables();

        // 7. 供应规格约束 (跨周期总量供应)
        SupplySpecConstraint.build(model, data);
        logStep("7. 供应规格", "ΣPTQty + Slack ≥ Target/Min/Max",
                model, beforeConstraints, beforeVariables);
        beforeConstraints = model.getSolver().numConstraints();
        beforeVariables = model.getSolver().numVariables();

        // 8. 批次大小约束 (批量生产)
        LotSizeConstraint.build(model, data);
        logStep("8. 批次大小", "PTQty×QTPFactor + Slack ∈ [LotSize]",
                model, beforeConstraints, beforeVariables);
        beforeConstraints = model.getSolver().numConstraints();
        beforeVariables = model.getSolver().numVariables();

        // 9. KPI 汇总变量及定义约束 (必须在目标函数之前)
        KpiAggregator.build(model, data);
        logStep("9. KPI汇总", "TotalKPI = Σ 松弛变量 (12个KPI)",
                model, beforeConstraints, beforeVariables);

        logSummary(model);
    }

    /**
     * 记录单个约束构建步骤的结果。
     * 输出新增约束数和新增变量数, 如果为 0 则输出 WARNING。
     */
    private static void logStep(String stepName, String description,
                                MacroPlannerModel model,
                                int beforeConstraints, int beforeVariables) {
        int addedConstraints = model.getSolver().numConstraints() - beforeConstraints;
        int addedVariables = model.getSolver().numVariables() - beforeVariables;

        if (addedConstraints == 0 && addedVariables == 0) {
            LOG.warning(String.format("[%s] %s → 未创建任何约束或变量! (可能数据为空)",
                    stepName, description));
        } else {
            StringBuilder sb = new StringBuilder();
            sb.append(String.format("[%s] %s", stepName, description));
            if (addedConstraints > 0) {
                sb.append(String.format(" | +%d约束", addedConstraints));
            }
            if (addedVariables > 0) {
                sb.append(String.format(" | +%d变量", addedVariables));
            }
            LOG.info(sb.toString());
        }
    }

    /**
     * 输出构建完成汇总。
     */
    private static void logSummary(MacroPlannerModel model) {
        int totalConstraints = model.getSolver().numConstraints();
        int totalVariables = model.getSolver().numVariables();
        LOG.info(String.format("========== 约束构建完成: 总计 %d 约束, %d 变量 ==========",
                totalConstraints, totalVariables));
    }
}