package com.zioinfo.mes.bom;

import com.zioinfo.mes.admin.AuditService;
import com.zioinfo.mes.bom.mapper.BomMapper;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Service;

import java.util.ArrayList;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;

/**
 * BOM 서비스 — 구성 CRUD + 트리 전개 + 소요량(MRP) 계산.
 */
@Service
@RequiredArgsConstructor
public class BomService {

    private static final int MAX_DEPTH = 20; // 순환 방지
    private final BomMapper mapper;
    private final AuditService audit;

    public List<MesBom> listByParent(String parentItemCode, String bomVersion) {
        return mapper.findByParent(parentItemCode, bomVersion);
    }

    public List<MesBom> listAll(String status) {
        return mapper.findAll(status);
    }

    public MesBom create(MesBom bom, String actor) {
        if (bom.getParentItemCode() == null || bom.getChildItemCode() == null)
            throw new IllegalArgumentException("ERR-BOM-400: parent/child 품목코드 필수");
        if (bom.getParentItemCode().equals(bom.getChildItemCode()))
            throw new IllegalArgumentException("ERR-BOM-400: 자기 자신 구성 불가");
        if (bom.getQtyPer() == null || bom.getQtyPer() <= 0) bom.setQtyPer(1.0);
        if (bom.getStatus() == null) bom.setStatus("ACTIVE");
        bom.setCreatedBy(actor);
        mapper.insert(bom);
        audit.log("BOM_CREATE", bom.getParentItemCode(), "child=" + bom.getChildItemCode());
        return mapper.findById(bom.getId());
    }

    public MesBom update(Long id, MesBom bom, String actor) {
        MesBom cur = require(id);
        bom.setId(id);
        mapper.update(bom);
        audit.log("BOM_UPDATE", cur.getParentItemCode(), "id=" + id);
        return mapper.findById(id);
    }

    public void delete(Long id) {
        MesBom cur = require(id);
        mapper.delete(id);
        audit.log("BOM_DELETE", cur.getParentItemCode(), "id=" + id);
    }

    /** 트리 전개 — 부모 품목에서 시작하는 다단 BOM 구조. */
    public Map<String, Object> explode(String parentItemCode, double qty, String bomVersion) {
        return node(parentItemCode, qty, bomVersion, 0, new ArrayList<>());
    }

    private Map<String, Object> node(String itemCode, double qty, String version, int depth, List<String> path) {
        Map<String, Object> m = new LinkedHashMap<>();
        m.put("itemCode", itemCode);
        m.put("requiredQty", round3(qty));
        m.put("depth", depth);
        if (depth >= MAX_DEPTH || path.contains(itemCode)) {
            m.put("children", List.of());
            if (path.contains(itemCode)) m.put("warning", "순환 BOM 감지");
            return m;
        }
        List<String> newPath = new ArrayList<>(path);
        newPath.add(itemCode);
        List<MesBom> comps = mapper.findByParent(itemCode, version);
        List<Map<String, Object>> children = new ArrayList<>();
        for (MesBom c : comps) {
            double loss = c.getLossRate() == null ? 0 : c.getLossRate();
            double childQty = qty * c.getQtyPer() * (1 + loss);
            children.add(node(c.getChildItemCode(), childQty, version, depth + 1, newPath));
        }
        m.put("children", children);
        return m;
    }

    /** 소요량 집계(MRP) — 리프 자재별 총 소요량 평탄화. */
    public Map<String, Double> materialRequirement(String parentItemCode, double qty, String version) {
        Map<String, Double> agg = new LinkedHashMap<>();
        accumulate(parentItemCode, qty, version, 0, new ArrayList<>(), agg);
        return agg;
    }

    private void accumulate(String itemCode, double qty, String version, int depth,
                            List<String> path, Map<String, Double> agg) {
        if (depth >= MAX_DEPTH || path.contains(itemCode)) return;
        List<MesBom> comps = mapper.findByParent(itemCode, version);
        if (comps.isEmpty()) {
            if (depth > 0) agg.merge(itemCode, round3(qty), Double::sum); // 리프 자재
            return;
        }
        List<String> newPath = new ArrayList<>(path);
        newPath.add(itemCode);
        for (MesBom c : comps) {
            double loss = c.getLossRate() == null ? 0 : c.getLossRate();
            double childQty = qty * c.getQtyPer() * (1 + loss);
            accumulate(c.getChildItemCode(), childQty, version, depth + 1, newPath, agg);
        }
    }

    private MesBom require(Long id) {
        MesBom b = mapper.findById(id);
        if (b == null) throw new RuntimeException("ERR-BOM-404: BOM 없음");
        return b;
    }

    private double round3(double d) {
        return Math.round(d * 1000.0) / 1000.0;
    }
}
