package com.aps.macroplanner.data;

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

/**
 * 工艺路线 — 对应 Quintiq 中的 Routing。
 *
 * <p>工艺路线定义了一组串行工序步骤, 用于生产某个产品。
 * 用户只需定义工艺路线关联的产品和最终库存点, 以及各道工序,
 * 工序间的 WIP 缓冲区和 BOM 依赖关系由 {@link #expand()} 自动生成。</p>
 *
 * <h3>使用示例</h3>
 * <pre>{@code
 *   // 定义工艺路线: 产品 P 通过 3 道工序生产, 最终入库 SP_FG
 *   Routing routing = new Routing("R001", "P生产工艺", prodP, spFG);
 *   routing.addOperation(new Operation("OP_Cut",   "下料",   "Unit_Cut",   1.0, ...));
 *   routing.addOperation(new Operation("OP_Rough", "粗加工", "Unit_Rough", 1.5, ...));
 *   routing.addOperation(new Operation("OP_Finish","精加工", "Unit_Finish",2.0, ...));
 *
 *   // 展开: 自动生成 WIP 库存点 + BOM 输入 + 工序产出配置
 *   // 只有最后一道工序 (Finish) 产出成品, 中间工序 WIP 自动流转
 *   RoutingExpansion expansion = routing.expand();
 * }</pre>
 *
 * <h3>与 Quintiq 模型的对应关系</h3>
 * <pre>
 *   Quintiq Routing:
 *     - RoutingSteps (有序)     → steps
 *     - PISPNodeInRouting       → expand() 自动生成的 WIP 库存点
 *     - OperationLink (BOM)     → expand() 自动生成的 OperationInput
 *     - AllowWIPInventory       → 中间 WIP 库存点无容量限制
 *     - Product/StockingPoint   → routing 关联的最终产品和库存点
 * </pre>
 *
 * @see RoutingStep
 */
public class Routing {

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

    /** 工艺路线 ID */
    private final String id;

    /** 工艺路线名称 */
    private final String name;

    /** 该工艺路线生产的最终产品 */
    private final Product product;

    /** 最终产出库存点 (成品库) */
    private final StockingPoint finalSp;

    /** 工序步骤列表 (按 sequenceNumber 排序) */
    private final List<RoutingStep> steps = new ArrayList<>();

    /**
     * 创建工艺路线。
     *
     * @param id      工艺路线 ID (唯一标识)
     * @param name    工艺路线名称 (描述)
     * @param product 该工艺路线生产的最终产品
     * @param finalSp 最终产出库存点 (成品库)
     */
    public Routing(String id, String name, Product product, StockingPoint finalSp) {
        this.id = id;
        this.name = name;
        this.product = product;
        this.finalSp = finalSp;
    }

    /**
     * 添加工序 (便捷方法)。自动按添加顺序分配 sequenceNumber。
     * 对应 Quintiq 中向 Routing 添加 Unit 创建 Operation。
     *
     * @param op 工序
     * @return this (流式 API)
     */
    public Routing addOperation(Operation op) {
        int seq = steps.size() + 1;
        return addStep(new RoutingStep(seq, op));
    }

    /**
     * 添加工序步骤。步骤按 sequenceNumber 自动排序。
     * 对应 Quintiq 中的 RoutingStep。
     *
     * @param step 工艺路线步骤
     * @return this (流式 API)
     */
    public Routing addStep(RoutingStep step) {
        steps.add(step);
        steps.sort((a, b) -> Integer.compare(a.getSequenceNumber(), b.getSequenceNumber()));
        return this;
    }

    /**
     * 展开工艺路线 — 自动生成中间 WIP 库存点、BOM 输入和操作输出配置。
     *
     * <h3>展开逻辑 (对应 Quintiq PISPNodeInRouting 的自动生成)</h3>
     * <pre>
     *   对于 N 步路由 (S1, S2, ..., SN):
     *     S1:     产出 → WIP_{routingId}_1 (中间缓冲区)
     *     S2:     产出 → WIP_{routingId}_2, 消耗 → S1 的 WIP 产出
     *     ...
     *     SN-1:   产出 → WIP_{routingId}_{N-1}, 消耗 → SN-2 的 WIP 产出
     *     SN(最后): 产出 → finalSp (成品库), 消耗 → SN-1 的 WIP 产出
     * </pre>
     *
     * <p>只有最后一步的产出计入最终产品库存, 中间步骤的产出经 BOM 约束
     * 被下一道工序消耗, 不会重复计算产出。</p>
     *
     * @return 展开结果, 包含所有自动生成的库存点、映射和 BOM 输入
     */
    public RoutingExpansion expand() {
        if (steps.isEmpty()) {
            throw new IllegalStateException("工艺路线 '" + id + "' 没有工序步骤");
        }

        LOG.info(String.format("[路由展开] 开始: %s '%s' → 产品=%s 最终库存点=%s 工序数=%d",
                id, name, product.getId(), finalSp.getId(), steps.size()));

        // 记录各工序当前已有的产出 (联产品/副产品)
        for (int i = 0; i < steps.size(); i++) {
            Operation op = steps.get(i).getOperation();
            List<OperationOutput> existingOutputs = op.getOutputs();
            if (!existingOutputs.isEmpty()) {
                List<String> descs = new ArrayList<>();
                for (OperationOutput oo : existingOutputs) {
                    descs.add(oo.getProductId() + "@" + oo.getSpId());
                }
                LOG.fine(String.format("[路由展开] 工序%d '%s' 已有产出: %s",
                        i + 1, op.getId(), String.join(", ", descs)));
            }
        }

        RoutingExpansion expansion = new RoutingExpansion(this);

        // 单步路由: 直接产出到最终库存点, 无需 WIP
        // 使用 addOutput() 保留工序上预配置的联产品/副产品产出
        if (steps.size() == 1) {
            RoutingStep step = steps.get(0);
            Operation op = step.getOperation();
            int beforeCount = op.getOutputs().size();
            op.addOutput(new OperationOutput(product, finalSp));
            int afterCount = op.getOutputs().size();
            LOG.info(String.format("[路由展开] 单步路由: '%s' 产出 → %s@%s (产出数: %d→%d)%s",
                    op.getId(), product.getId(), finalSp.getId(),
                    beforeCount, afterCount,
                    afterCount > 1 ? " [多产出: 联产品/副产品]" : ""));
            return expansion;
        }

        // 多步路由: 生成中间 WIP 库存点 (对应 Quintiq PISPNodeInRouting)
        StockingPoint previousWipSp = null;
        int wipCount = 0;
        int bomLinkCount = 0;

        for (int i = 0; i < steps.size(); i++) {
            RoutingStep step = steps.get(i);
            Operation op = step.getOperation();
            boolean isLast = (i == steps.size() - 1);

            if (isLast) {
                // === 最后一步: 产出到最终库存点 ===
                // 使用 addOutput() 保留工序上预配置的联产品/副产品产出
                int beforeCount = op.getOutputs().size();
                op.addOutput(new OperationOutput(product, finalSp));
                int afterCount = op.getOutputs().size();
                LOG.info(String.format("[路由展开] 最后工序%d '%s' → %s@%s (产出数: %d→%d)%s",
                        i + 1, op.getId(), product.getId(), finalSp.getId(),
                        beforeCount, afterCount,
                        afterCount > 1 ? " [多产出: 联产品/副产品]" : ""));

                // 最后一步消耗前一步的 WIP 产出
                OperationInput input = new OperationInput(
                        step.getOperation(), product, previousWipSp, 1.0);
                expansion.addOperationInput(input);
                bomLinkCount++;
                LOG.fine(String.format("[路由展开] BOM链接: '%s' 消耗 %s@%s ×1.0 (来自前序WIP)",
                        op.getId(), product.getId(), previousWipSp.getId()));
            } else {
                // === 中间步骤: 产出到自动生成的 WIP 库存点 ===
                // 对应 Quintiq 中 PISPNodeInRouting 的自动创建
                String wipSpId = "WIP_" + id + "_" + (i + 1);
                String wipSpName = name + "-工序" + (i + 1) + "→工序" + (i + 2) + "缓冲区";
                StockingPoint wipSp = new StockingPoint(wipSpId, wipSpName);
                wipCount++;
                LOG.fine(String.format("[路由展开] 创建WIP库存点: %s '%s'", wipSpId, wipSpName));

                // 第一步: 只设置产出, 无需消耗
                // 后续步骤: 消耗前一步的 WIP 产出
                if (previousWipSp != null) {
                    OperationInput input = new OperationInput(
                            step.getOperation(), product, previousWipSp, 1.0);
                    expansion.addOperationInput(input);
                    bomLinkCount++;
                    LOG.fine(String.format("[路由展开] BOM链接: '%s' 消耗 %s@%s ×1.0 (前序WIP)",
                            op.getId(), product.getId(), previousWipSp.getId()));
                } else {
                    LOG.fine(String.format("[路由展开] 首工序%d '%s' 无前序WIP消耗 (仅产出)",
                            i + 1, op.getId()));
                }

                // 设置当前步骤的产出到 WIP 缓冲区
                // 使用 addOutput() 保留工序上预配置的联产品/副产品产出
                int beforeCount = op.getOutputs().size();
                op.addOutput(new OperationOutput(product, wipSp));
                int afterCount = op.getOutputs().size();
                LOG.fine(String.format("[路由展开] 中间工序%d '%s' → %s@%s (产出数: %d→%d)%s",
                        i + 1, op.getId(), product.getId(), wipSp.getId(),
                        beforeCount, afterCount,
                        afterCount > 1 ? " [多产出: 联产品/副产品]" : ""));

                // 注册 WIP 库存点、产品映射和初始库存
                expansion.addStockingPoint(wipSp);
                expansion.addProductSpMapping(new ProductSpMapping(product, wipSp));
                expansion.addInitialInventory(new InitialInventory(product, wipSp, 0.0));

                previousWipSp = wipSp;
            }
        }

        LOG.info(String.format("[路由展开] 完成: '%s' → 生成 WIP库存点=%d, BOM链接=%d, 展开后库存点=%d, 映射=%d, 初始库存=%d",
                id, wipCount, bomLinkCount,
                expansion.getStockingPoints().size(),
                expansion.getProductSpMappings().size(),
                expansion.getInitialInventories().size()));

        return expansion;
    }

    // ==================== Getters ====================

    public String getId() { return id; }
    public String getName() { return name; }
    public Product getProduct() { return product; }
    public StockingPoint getFinalSp() { return finalSp; }
    public List<RoutingStep> getSteps() { return Collections.unmodifiableList(steps); }

    /**
     * 获取路由中所有工序 (按顺序)。
     */
    public List<Operation> getOperations() {
        List<Operation> ops = new ArrayList<>();
        for (RoutingStep step : steps) {
            ops.add(step.getOperation());
        }
        return ops;
    }
}