package com.erp.service;

import com.erp.common.BaseService;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import java.util.*;
import java.util.stream.Collectors;

@Service
@RequiredArgsConstructor
@Slf4j
public class BomService extends BaseService {

    private static final String NS = "bom.";

    private final CommonService commonService;
    private final JdbcTemplate jdbcTemplate;

    // ─── 기본 CRUD ───

    public Map<String, Object> getBomList(Map<String, Object> params) {
        commonService.applyCompanyCodeFilter(params);
        commonService.applyPagination(params);
        int totalCount = sqlSession.selectOne(NS + "getBomListCnt", params);
        List<Map<String, Object>> list = sqlSession.selectList(NS + "getBomList", params);
        return commonService.buildListResponse(list, totalCount, params);
    }

    public Map<String, Object> getBomInfo(Map<String, Object> params) {
        commonService.applyCompanyCodeFilter(params);
        return sqlSession.selectOne(NS + "getBomInfo", params);
    }

    @Transactional
    public Map<String, Object> insertBom(Map<String, Object> params) {
        commonService.applyCompanyCodeFilter(params);
        sqlSession.insert(NS + "insertBom", params);
        return params;
    }

    @Transactional
    public Map<String, Object> updateBom(Map<String, Object> params) {
        commonService.applyCompanyCodeFilter(params);
        sqlSession.update(NS + "updateBom", params);
        return params;
    }

    @Transactional
    public Map<String, Object> deleteBom(Map<String, Object> params) {
        commonService.applyCompanyCodeFilter(params);
        sqlSession.delete(NS + "deleteBom", params);
        return params;
    }

    // ─── BOM 헤더 조회 (entity join 포함) ───

    public Map<String, Object> getBomHeader(String bomId) {
        List<Map<String, Object>> rows = jdbcTemplate.queryForList(
            "SELECT b.*, ii.item_name AS ii_item_name, ii.item_number, ii.division AS item_type," +
            "  COALESCE(b.unit, ii.unit) AS unit, ii.unit AS item_unit, ii.division, ii.size, ii.material" +
            " FROM bom b LEFT JOIN item_info ii ON b.item_id = ii.id" +
            " WHERE b.id = ?",
            bomId
        );
        return rows.isEmpty() ? null : rows.get(0);
    }

    // ─── 이력 ───

    public List<Map<String, Object>> getBomHistory(String bomId, String companyCode) {
        if ("*".equals(companyCode)) {
            return jdbcTemplate.queryForList(
                "SELECT * FROM bom_history WHERE bom_id = ? ORDER BY changed_date DESC",
                bomId
            );
        }
        return jdbcTemplate.queryForList(
            "SELECT * FROM bom_history WHERE bom_id = ? AND company_code = ? ORDER BY changed_date DESC",
            bomId, companyCode
        );
    }

    @Transactional
    public Map<String, Object> addBomHistory(String bomId, String companyCode, Map<String, Object> data) {
        List<Map<String, Object>> rows = jdbcTemplate.queryForList(
            "INSERT INTO bom_history (bom_id, revision, version, change_type, change_description, changed_by, company_code)" +
            " VALUES (?, ?, ?, ?, ?, ?, ?) RETURNING *",
            bomId,
            data.get("revision"),
            data.get("version"),
            data.get("change_type"),
            data.get("change_description"),
            data.get("changed_by"),
            companyCode
        );
        return rows.isEmpty() ? new HashMap<>() : rows.get(0);
    }

    // ─── 버전 관리 ───

    public Map<String, Object> getBomVersions(String bomId, String companyCode) {
        List<Map<String, Object>> versions;
        if ("*".equals(companyCode)) {
            versions = jdbcTemplate.queryForList(
                "SELECT v.*, (SELECT COUNT(*) FROM bom_detail d WHERE d.version_id = v.id) AS detail_count" +
                " FROM bom_version v WHERE v.bom_id = ? ORDER BY v.created_date DESC",
                bomId
            );
        } else {
            versions = jdbcTemplate.queryForList(
                "SELECT v.*, (SELECT COUNT(*) FROM bom_detail d WHERE d.version_id = v.id) AS detail_count" +
                " FROM bom_version v WHERE v.bom_id = ? AND v.company_code = ? ORDER BY v.created_date DESC",
                bomId, companyCode
            );
        }

        List<Map<String, Object>> bomRows = jdbcTemplate.queryForList(
            "SELECT current_version_id FROM bom WHERE id = ?", bomId
        );
        Object currentVersionId = bomRows.isEmpty() ? null : bomRows.get(0).get("current_version_id");

        Map<String, Object> result = new LinkedHashMap<>();
        result.put("versions", versions);
        result.put("current_version_id", currentVersionId);
        return result;
    }

    @Transactional
    public Map<String, Object> createBomVersion(String bomId, String companyCode, String createdBy, String versionName) {
        List<Map<String, Object>> bomRows = jdbcTemplate.queryForList(
            "SELECT * FROM bom WHERE id = ?", bomId
        );
        if (bomRows.isEmpty()) throw new IllegalArgumentException("BOM을 찾을 수 없습니다");
        Map<String, Object> bomData = bomRows.get(0);

        String finalVersionName = (versionName != null && !versionName.trim().isEmpty()) ? versionName.trim() : null;
        if (finalVersionName == null) {
            List<Map<String, Object>> countRows = jdbcTemplate.queryForList(
                "SELECT COUNT(*)::int AS cnt FROM bom_version WHERE bom_id = ?", bomId
            );
            int cnt = countRows.isEmpty() ? 0 : ((Number) countRows.get(0).get("cnt")).intValue();
            finalVersionName = (cnt + 1) + ".0";
        }

        List<Map<String, Object>> dupCheck = jdbcTemplate.queryForList(
            "SELECT id FROM bom_version WHERE bom_id = ? AND version_name = ?", bomId, finalVersionName
        );
        if (!dupCheck.isEmpty()) throw new IllegalArgumentException("이미 존재하는 버전명입니다: " + finalVersionName);

        Object revision = bomData.get("revision");
        int revInt = revision == null ? 0 : (revision instanceof Number ? ((Number) revision).intValue() : 0);

        List<Map<String, Object>> newVersionRows = jdbcTemplate.queryForList(
            "INSERT INTO bom_version (bom_id, version_name, revision, status, created_by, company_code)" +
            " VALUES (?, ?, ?, 'developing', ?, ?) RETURNING *",
            bomId, finalVersionName, revInt, createdBy, companyCode
        );
        if (newVersionRows.isEmpty()) throw new IllegalArgumentException("버전 생성 실패");
        Map<String, Object> newVersion = newVersionRows.get(0);
        Object newVersionId = newVersion.get("id");

        Object sourceVersionId = bomData.get("current_version_id");
        if (sourceVersionId != null) {
            List<Map<String, Object>> sourceDetails = jdbcTemplate.queryForList(
                "SELECT * FROM bom_detail WHERE bom_id = ? AND version_id = ? ORDER BY parent_detail_id NULLS FIRST, id",
                bomId, sourceVersionId
            );

            Map<Object, Object> oldToNew = new HashMap<>();
            for (Map<String, Object> d : sourceDetails) {
                Object parentDetailId = d.get("parent_detail_id");
                Object mappedParent = parentDetailId != null ? oldToNew.get(parentDetailId) : null;

                List<Map<String, Object>> insertedRows = jdbcTemplate.queryForList(
                    "INSERT INTO bom_detail (bom_id, version_id, parent_detail_id, child_item_id, quantity, unit," +
                    "  process_type, loss_rate, remark, level, base_qty, revision, seq_no, writer, company_code)" +
                    " VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?) RETURNING id",
                    bomId, newVersionId, mappedParent, d.get("child_item_id"), d.get("quantity"), d.get("unit"),
                    d.get("process_type"), d.get("loss_rate"), d.get("remark"), d.get("level"),
                    d.get("base_qty"), d.get("revision"), d.get("seq_no"), d.get("writer"), companyCode
                );
                if (!insertedRows.isEmpty()) {
                    oldToNew.put(d.get("id"), insertedRows.get(0).get("id"));
                }
            }
        }

        jdbcTemplate.update(
            "UPDATE bom SET version = ?, current_version_id = ? WHERE id = ?",
            finalVersionName, newVersionId, bomId
        );

        log.info("BOM 버전 생성 완료: bomId={}, versionName={}", bomId, finalVersionName);
        return newVersion;
    }

    @Transactional
    public Map<String, Object> loadBomVersion(String bomId, String versionId, String companyCode) {
        List<Map<String, Object>> verRows = jdbcTemplate.queryForList(
            "SELECT * FROM bom_version WHERE id = ? AND bom_id = ?", versionId, bomId
        );
        if (verRows.isEmpty()) throw new IllegalArgumentException("버전을 찾을 수 없습니다");
        String versionName = (String) verRows.get(0).get("version_name");

        jdbcTemplate.update(
            "UPDATE bom SET version = ?, current_version_id = ? WHERE id = ?",
            versionName, versionId, bomId
        );

        Map<String, Object> result = new HashMap<>();
        result.put("restored", true);
        result.put("version_name", versionName);
        return result;
    }

    @Transactional
    public Map<String, Object> activateBomVersion(String bomId, String versionId) {
        List<Map<String, Object>> verRows = jdbcTemplate.queryForList(
            "SELECT version_name FROM bom_version WHERE id = ? AND bom_id = ?", versionId, bomId
        );
        if (verRows.isEmpty()) throw new IllegalArgumentException("버전을 찾을 수 없습니다");
        String versionName = (String) verRows.get(0).get("version_name");

        jdbcTemplate.update(
            "UPDATE bom_version SET status = 'inactive' WHERE bom_id = ? AND status = 'active'", bomId
        );
        jdbcTemplate.update("UPDATE bom_version SET status = 'active' WHERE id = ?", versionId);
        jdbcTemplate.update(
            "UPDATE bom SET version = ?, current_version_id = ? WHERE id = ?",
            versionName, versionId, bomId
        );

        Map<String, Object> result = new HashMap<>();
        result.put("activated", true);
        result.put("version_name", versionName);
        return result;
    }

    @Transactional
    public Map<String, Object> initializeBomVersion(String bomId, String companyCode, String createdBy) {
        List<Map<String, Object>> bomRows = jdbcTemplate.queryForList("SELECT * FROM bom WHERE id = ?", bomId);
        if (bomRows.isEmpty()) throw new IllegalArgumentException("BOM을 찾을 수 없습니다");
        Map<String, Object> bomData = bomRows.get(0);

        Object currentVersionId = bomData.get("current_version_id");
        if (currentVersionId != null) {
            jdbcTemplate.update(
                "UPDATE bom_detail SET version_id = ? WHERE bom_id = ? AND version_id IS NULL",
                currentVersionId, bomId
            );
            Map<String, Object> result = new HashMap<>();
            result.put("version_id", currentVersionId);
            result.put("created", false);
            return result;
        }

        List<Map<String, Object>> existingVersions = jdbcTemplate.queryForList(
            "SELECT id, version_name FROM bom_version WHERE bom_id = ? ORDER BY created_date ASC LIMIT 1", bomId
        );
        if (!existingVersions.isEmpty()) {
            Object existId = existingVersions.get(0).get("id");
            jdbcTemplate.update(
                "UPDATE bom_detail SET version_id = ? WHERE bom_id = ? AND version_id IS NULL", existId, bomId
            );
            jdbcTemplate.update(
                "UPDATE bom SET current_version_id = ? WHERE id = ? AND current_version_id IS NULL", existId, bomId
            );
            Map<String, Object> result = new HashMap<>();
            result.put("version_id", existId);
            result.put("created", false);
            return result;
        }

        String versionName = bomData.get("version") != null ? String.valueOf(bomData.get("version")) : "1.0";
        List<Map<String, Object>> versionRows = jdbcTemplate.queryForList(
            "INSERT INTO bom_version (bom_id, version_name, revision, status, created_by, company_code)" +
            " VALUES (?, ?, 0, 'active', ?, ?) RETURNING id",
            bomId, versionName, createdBy, companyCode
        );
        Object versionId = versionRows.isEmpty() ? null : versionRows.get(0).get("id");

        jdbcTemplate.update(
            "UPDATE bom_detail SET version_id = ? WHERE bom_id = ? AND version_id IS NULL", versionId, bomId
        );
        jdbcTemplate.update("UPDATE bom SET current_version_id = ? WHERE id = ?", versionId, bomId);

        Map<String, Object> result = new HashMap<>();
        result.put("version_id", versionId);
        result.put("version_name", versionName);
        result.put("created", true);
        return result;
    }

    @Transactional
    public boolean deleteBomVersion(String bomId, String versionId) {
        List<Map<String, Object>> checkRows = jdbcTemplate.queryForList(
            "SELECT status FROM bom_version WHERE id = ? AND bom_id = ?", versionId, bomId
        );
        if (checkRows.isEmpty()) throw new IllegalArgumentException("버전을 찾을 수 없습니다");
        if ("active".equals(checkRows.get(0).get("status"))) {
            throw new IllegalArgumentException("사용중인 버전은 삭제할 수 없습니다");
        }

        jdbcTemplate.update("DELETE FROM bom_detail WHERE bom_id = ? AND version_id = ?", bomId, versionId);
        int deleted = jdbcTemplate.update("DELETE FROM bom_version WHERE id = ? AND bom_id = ?", versionId, bomId);
        return deleted > 0;
    }

    // ─── 엑셀 업로드/다운로드 ───

    @Transactional
    public Map<String, Object> createBomFromExcel(String companyCode, String userId, List<Map<String, Object>> rows) {
        Map<String, Object> result = new LinkedHashMap<>();
        result.put("success", false);
        result.put("inserted_count", 0);
        result.put("skipped_count", 0);
        result.put("errors", new ArrayList<String>());
        result.put("unmatched_items", new ArrayList<String>());

        @SuppressWarnings("unchecked")
        List<String> errors = (List<String>) result.get("errors");
        @SuppressWarnings("unchecked")
        List<String> unmatchedItems = (List<String>) result.get("unmatched_items");

        if (rows == null || rows.isEmpty()) {
            errors.add("업로드할 데이터가 없습니다");
            return result;
        }

        Map<String, Object> headerRow = rows.stream().filter(r -> toInt(r.get("level")) == 0).findFirst().orElse(null);
        List<Map<String, Object>> detailRows = rows.stream().filter(r -> toInt(r.get("level")) > 0).collect(Collectors.toList());

        if (headerRow == null) { errors.add("레벨 0(BOM 마스터) 행이 필요합니다"); return result; }
        String headerItemNumber = headerRow.get("item_number") != null ? String.valueOf(headerRow.get("item_number")).trim() : "";
        if (headerItemNumber.isEmpty()) { errors.add("레벨 0(BOM 마스터)의 품번은 필수입니다"); return result; }
        if (detailRows.isEmpty()) { errors.add("하위품목이 없습니다"); return result; }

        // 레벨 유효성 검사
        for (int i = 0; i < rows.size(); i++) {
            int level = toInt(rows.get(i).get("level"));
            if (level < 0) errors.add((i + 1) + "행: 레벨은 0 이상이어야 합니다");
            if (i > 0 && level > toInt(rows.get(i - 1).get("level")) + 1)
                errors.add((i + 1) + "행: 레벨이 이전 행보다 2 이상 깊어질 수 없습니다");
            if (level > 0 && (rows.get(i).get("item_number") == null || String.valueOf(rows.get(i).get("item_number")).trim().isEmpty()))
                errors.add((i + 1) + "행: 품번은 필수입니다");
        }
        if (!errors.isEmpty()) return result;

        // 모든 품번 일괄 조회
        Set<String> allItemNumbers = new LinkedHashSet<>();
        rows.stream().filter(r -> r.get("item_number") != null && !String.valueOf(r.get("item_number")).trim().isEmpty())
            .forEach(r -> allItemNumbers.add(String.valueOf(r.get("item_number")).trim()));

        String placeholders = allItemNumbers.stream().map(i -> "?").collect(Collectors.joining(", "));
        List<Object> lookupArgs = new ArrayList<>();
        lookupArgs.add(companyCode);
        lookupArgs.addAll(allItemNumbers);

        List<Map<String, Object>> itemLookup = jdbcTemplate.queryForList(
            "SELECT id, item_number, item_name, unit FROM item_info WHERE company_code = ? AND item_number IN (" + placeholders + ")",
            lookupArgs.toArray()
        );
        Map<String, Map<String, Object>> itemMap = new LinkedHashMap<>();
        for (Map<String, Object> item : itemLookup) {
            itemMap.put(String.valueOf(item.get("item_number")), item);
        }

        for (String num : allItemNumbers) {
            if (!itemMap.containsKey(num)) unmatchedItems.add(num);
        }
        if (!unmatchedItems.isEmpty()) {
            errors.add("매칭되지 않는 품번이 있습니다: " + String.join(", ", unmatchedItems));
            return result;
        }

        // BOM 마스터 생성
        Map<String, Object> headerItemInfo = itemMap.get(headerItemNumber);

        List<Map<String, Object>> dupCheck = jdbcTemplate.queryForList(
            "SELECT id FROM bom WHERE item_id = ? AND company_code = ? AND status = 'active'",
            headerItemInfo.get("id"), companyCode
        );
        if (!dupCheck.isEmpty()) {
            errors.add("해당 품목(" + headerItemNumber + ")으로 등록된 BOM이 이미 존재합니다");
            return result;
        }

        double headerQty = toDouble(headerRow.getOrDefault("quantity", 1));
        Object headerUnit = headerRow.get("unit") != null ? headerRow.get("unit") : headerItemInfo.get("unit");

        List<Map<String, Object>> bomInsert = jdbcTemplate.queryForList(
            "INSERT INTO bom (item_id, item_code, item_name, base_qty, unit, version, status, remark, writer, company_code)" +
            " VALUES (?, ?, ?, ?, ?, '1.0', 'active', ?, ?, ?) RETURNING id",
            headerItemInfo.get("id"), headerItemNumber,
            headerItemInfo.get("item_name"), String.valueOf(headerQty),
            headerUnit, headerRow.get("remark"), userId, companyCode
        );
        Object newBomId = bomInsert.isEmpty() ? null : bomInsert.get(0).get("id");

        List<Map<String, Object>> versionInsert = jdbcTemplate.queryForList(
            "INSERT INTO bom_version (bom_id, version_name, revision, status, created_by, company_code)" +
            " VALUES (?, '1.0', 0, 'active', ?, ?) RETURNING id",
            newBomId, userId, companyCode
        );
        Object versionId = versionInsert.isEmpty() ? null : versionInsert.get(0).get("id");
        jdbcTemplate.update("UPDATE bom SET current_version_id = ? WHERE id = ?", versionId, newBomId);

        // bom_detail INSERT
        List<Object> levelStack = new ArrayList<>();
        Map<Object, Integer> seqByParent = new LinkedHashMap<>();
        int insertedCount = 0;

        for (Map<String, Object> row : detailRows) {
            int dbLevel = toInt(row.get("level")) - 1;
            while (levelStack.size() > dbLevel) levelStack.remove(levelStack.size() - 1);
            Object parentDetailId = levelStack.isEmpty() ? null : levelStack.get(levelStack.size() - 1);
            Object parentKey = parentDetailId != null ? parentDetailId : "__root__";
            int currentSeq = seqByParent.getOrDefault(parentKey, 0) + 1;
            seqByParent.put(parentKey, currentSeq);

            String rowItemNumber = String.valueOf(row.get("item_number")).trim();
            Map<String, Object> itemInfo = itemMap.get(rowItemNumber);
            Object rowUnit = row.get("unit") != null ? row.get("unit") : itemInfo.get("unit");
            double rowQty = toDouble(row.getOrDefault("quantity", 1));

            List<Map<String, Object>> detailInsert = jdbcTemplate.queryForList(
                "INSERT INTO bom_detail (bom_id, version_id, parent_detail_id, child_item_id, level, seq_no, quantity, unit, loss_rate, process_type, remark, writer, company_code)" +
                " VALUES (?, ?, ?, ?, ?, ?, ?, ?, '0', ?, ?, ?, ?) RETURNING id",
                newBomId, versionId, parentDetailId, itemInfo.get("id"),
                String.valueOf(dbLevel), String.valueOf(currentSeq), String.valueOf(rowQty),
                rowUnit, row.get("process_type"), row.get("remark"), userId, companyCode
            );
            if (!detailInsert.isEmpty()) levelStack.add(detailInsert.get(0).get("id"));
            insertedCount++;
        }

        jdbcTemplate.update(
            "INSERT INTO bom_history (bom_id, change_type, change_description, changed_by, company_code)" +
            " VALUES (?, 'excel_upload', ?, ?, ?)",
            newBomId, "엑셀 업로드로 BOM 생성 (하위품목 " + insertedCount + "건)", userId, companyCode
        );

        result.put("success", true);
        result.put("inserted_count", insertedCount);
        result.put("created_bom_id", newBomId);
        return result;
    }

    @Transactional
    public Map<String, Object> createBomVersionFromExcel(String bomId, String companyCode, String userId, List<Map<String, Object>> rows, String versionName) {
        Map<String, Object> result = new LinkedHashMap<>();
        result.put("success", false);
        result.put("inserted_count", 0);
        result.put("skipped_count", 0);
        result.put("errors", new ArrayList<String>());
        result.put("unmatched_items", new ArrayList<String>());

        @SuppressWarnings("unchecked")
        List<String> errors = (List<String>) result.get("errors");
        @SuppressWarnings("unchecked")
        List<String> unmatchedItems = (List<String>) result.get("unmatched_items");

        if (rows == null || rows.isEmpty()) { errors.add("업로드할 데이터가 없습니다"); return result; }

        List<Map<String, Object>> detailRows = rows.stream().filter(r -> toInt(r.get("level")) > 0).collect(Collectors.toList());
        result.put("skipped_count", rows.size() - detailRows.size());
        if (detailRows.isEmpty()) { errors.add("하위품목이 없습니다"); return result; }

        List<Map<String, Object>> bomCheck = jdbcTemplate.queryForList(
            "SELECT id FROM bom WHERE id = ? AND company_code = ?", bomId, companyCode
        );
        if (bomCheck.isEmpty()) { errors.add("BOM을 찾을 수 없습니다"); return result; }

        Set<String> uniqueItemNumbers = detailRows.stream()
            .filter(r -> r.get("item_number") != null)
            .map(r -> String.valueOf(r.get("item_number")).trim())
            .collect(Collectors.toCollection(LinkedHashSet::new));

        String placeholders = uniqueItemNumbers.stream().map(i -> "?").collect(Collectors.joining(", "));
        List<Object> lookupArgs = new ArrayList<>();
        lookupArgs.add(companyCode);
        lookupArgs.addAll(uniqueItemNumbers);

        List<Map<String, Object>> itemLookup = jdbcTemplate.queryForList(
            "SELECT id, item_number, item_name, unit FROM item_info WHERE company_code = ? AND item_number IN (" + placeholders + ")",
            lookupArgs.toArray()
        );
        Map<String, Map<String, Object>> itemMap = new LinkedHashMap<>();
        for (Map<String, Object> item : itemLookup) itemMap.put(String.valueOf(item.get("item_number")), item);
        for (String num : uniqueItemNumbers) if (!itemMap.containsKey(num)) unmatchedItems.add(num);
        if (!unmatchedItems.isEmpty()) { errors.add("매칭되지 않는 품번이 있습니다: " + String.join(", ", unmatchedItems)); return result; }

        String finalVersionName = (versionName != null && !versionName.trim().isEmpty()) ? versionName.trim() : null;
        if (finalVersionName == null) {
            List<Map<String, Object>> countRows = jdbcTemplate.queryForList(
                "SELECT COUNT(*)::int AS cnt FROM bom_version WHERE bom_id = ?", bomId
            );
            int cnt = countRows.isEmpty() ? 0 : ((Number) countRows.get(0).get("cnt")).intValue();
            finalVersionName = (cnt + 1) + ".0";
        }

        List<Map<String, Object>> dupCheck = jdbcTemplate.queryForList(
            "SELECT id FROM bom_version WHERE bom_id = ? AND version_name = ?", bomId, finalVersionName
        );
        if (!dupCheck.isEmpty()) { errors.add("이미 존재하는 버전명입니다: " + finalVersionName); return result; }

        List<Map<String, Object>> versionInsert = jdbcTemplate.queryForList(
            "INSERT INTO bom_version (bom_id, version_name, revision, status, created_by, company_code)" +
            " VALUES (?, ?, 0, 'developing', ?, ?) RETURNING id",
            bomId, finalVersionName, userId, companyCode
        );
        Object newVersionId = versionInsert.isEmpty() ? null : versionInsert.get(0).get("id");

        List<Object> levelStack = new ArrayList<>();
        Map<Object, Integer> seqByParent = new LinkedHashMap<>();
        int insertedCount = 0;

        for (Map<String, Object> row : detailRows) {
            int dbLevel = toInt(row.get("level")) - 1;
            while (levelStack.size() > dbLevel) levelStack.remove(levelStack.size() - 1);
            Object parentDetailId = levelStack.isEmpty() ? null : levelStack.get(levelStack.size() - 1);
            Object parentKey = parentDetailId != null ? parentDetailId : "__root__";
            int currentSeq = seqByParent.getOrDefault(parentKey, 0) + 1;
            seqByParent.put(parentKey, currentSeq);

            String rowItemNumber = String.valueOf(row.get("item_number")).trim();
            Map<String, Object> itemInfo = itemMap.get(rowItemNumber);
            Object rowUnit = row.get("unit") != null ? row.get("unit") : itemInfo.get("unit");
            double rowQty = toDouble(row.getOrDefault("quantity", 1));

            List<Map<String, Object>> detailInsert = jdbcTemplate.queryForList(
                "INSERT INTO bom_detail (bom_id, version_id, parent_detail_id, child_item_id, level, seq_no, quantity, unit, loss_rate, process_type, remark, writer, company_code)" +
                " VALUES (?, ?, ?, ?, ?, ?, ?, ?, '0', ?, ?, ?, ?) RETURNING id",
                bomId, newVersionId, parentDetailId, itemInfo.get("id"),
                String.valueOf(dbLevel), String.valueOf(currentSeq), String.valueOf(rowQty),
                rowUnit, row.get("process_type"), row.get("remark"), userId, companyCode
            );
            if (!detailInsert.isEmpty()) levelStack.add(detailInsert.get(0).get("id"));
            insertedCount++;
        }

        jdbcTemplate.update(
            "UPDATE bom SET version = ?, current_version_id = ? WHERE id = ?",
            finalVersionName, newVersionId, bomId
        );
        jdbcTemplate.update(
            "INSERT INTO bom_history (bom_id, change_type, change_description, changed_by, company_code)" +
            " VALUES (?, 'excel_upload', ?, ?, ?)",
            bomId, "엑셀 업로드로 새 버전 " + finalVersionName + " 생성 (하위품목 " + insertedCount + "건)", userId, companyCode
        );

        result.put("success", true);
        result.put("inserted_count", insertedCount);
        result.put("created_bom_id", bomId);
        return result;
    }

    public List<Map<String, Object>> downloadBomExcelData(String bomId, String companyCode) {
        List<Map<String, Object>> bomRows = jdbcTemplate.queryForList(
            "SELECT b.*, ii.item_number, ii.item_name AS ii_item_name, ii.division, ii.unit AS item_unit" +
            " FROM bom b LEFT JOIN item_info ii ON b.item_id = ii.id" +
            " WHERE b.id = ? AND b.company_code = ?",
            bomId, companyCode
        );
        if (bomRows.isEmpty()) return new ArrayList<>();

        Map<String, Object> bomHeader = bomRows.get(0);
        List<Map<String, Object>> flatList = new ArrayList<>();

        Map<String, Object> headerEntry = new LinkedHashMap<>();
        headerEntry.put("level", 0);
        headerEntry.put("item_number", bomHeader.getOrDefault("item_number", ""));
        headerEntry.put("item_name", bomHeader.getOrDefault("ii_item_name", bomHeader.getOrDefault("item_name", "")));
        headerEntry.put("quantity", bomHeader.getOrDefault("base_qty", "1"));
        headerEntry.put("unit", bomHeader.getOrDefault("item_unit", bomHeader.getOrDefault("unit", "")));
        headerEntry.put("process_type", "");
        headerEntry.put("remark", bomHeader.getOrDefault("remark", ""));
        flatList.add(headerEntry);

        Object versionId = bomHeader.get("current_version_id");
        List<Map<String, Object>> details;
        if (versionId != null) {
            details = jdbcTemplate.queryForList(
                "SELECT bd.*, ii.item_number, ii.item_name, ii.division, ii.unit AS item_unit, ii.size, ii.material" +
                " FROM bom_detail bd LEFT JOIN item_info ii ON bd.child_item_id = ii.id" +
                " WHERE bd.bom_id = ? AND bd.company_code = ? AND bd.version_id = ?" +
                " ORDER BY bd.parent_detail_id NULLS FIRST, CAST(COALESCE(bd.seq_no, '0') AS int)",
                bomId, companyCode, versionId
            );
        } else {
            details = jdbcTemplate.queryForList(
                "SELECT bd.*, ii.item_number, ii.item_name, ii.division, ii.unit AS item_unit, ii.size, ii.material" +
                " FROM bom_detail bd LEFT JOIN item_info ii ON bd.child_item_id = ii.id" +
                " WHERE bd.bom_id = ? AND bd.company_code = ? AND bd.version_id IS NULL" +
                " ORDER BY bd.parent_detail_id NULLS FIRST, CAST(COALESCE(bd.seq_no, '0') AS int)",
                bomId, companyCode
            );
        }

        Map<Object, List<Map<String, Object>>> childrenMap = new LinkedHashMap<>();
        List<Map<String, Object>> roots = new ArrayList<>();
        for (Map<String, Object> d : details) {
            Object parentId = d.get("parent_detail_id");
            if (parentId == null) {
                roots.add(d);
            } else {
                childrenMap.computeIfAbsent(parentId, k -> new ArrayList<>()).add(d);
            }
        }

        dfsBomDetail(roots, 1, childrenMap, flatList);
        return flatList;
    }

    private void dfsBomDetail(List<Map<String, Object>> nodes, int depth,
                               Map<Object, List<Map<String, Object>>> childrenMap,
                               List<Map<String, Object>> flatList) {
        for (Map<String, Object> node : nodes) {
            Map<String, Object> entry = new LinkedHashMap<>();
            entry.put("level", depth);
            entry.put("item_number", node.getOrDefault("item_number", ""));
            entry.put("item_name", node.getOrDefault("item_name", ""));
            entry.put("quantity", node.getOrDefault("quantity", "1"));
            entry.put("unit", node.get("unit") != null ? node.get("unit") : node.getOrDefault("item_unit", ""));
            entry.put("process_type", node.getOrDefault("process_type", ""));
            entry.put("remark", node.getOrDefault("remark", ""));
            flatList.add(entry);
            List<Map<String, Object>> children = childrenMap.getOrDefault(node.get("id"), new ArrayList<>());
            if (!children.isEmpty()) dfsBomDetail(children, depth + 1, childrenMap, flatList);
        }
    }

    // ─── 유틸리티 ───

    private double toDouble(Object val) {
        if (val == null) return 0.0;
        if (val instanceof Number) return ((Number) val).doubleValue();
        try { return Double.parseDouble(val.toString()); } catch (Exception e) { return 0.0; }
    }

    private int toInt(Object val) {
        if (val == null) return 0;
        if (val instanceof Number) return ((Number) val).intValue();
        try { return Integer.parseInt(val.toString()); } catch (Exception e) { return 0; }
    }
}
