package com.erp.service;

import com.erp.common.BaseService;
import com.fasterxml.jackson.databind.ObjectMapper;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

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

@Service
@Slf4j
public class DataAdvancedService extends BaseService {

    @Autowired private DataService dataService;
    @Autowired private CommonService commonService;
    @Autowired private JdbcTemplate jdbcTemplate;
    @Autowired private ObjectMapper objectMapper;

    private static final Pattern IDENTIFIER = Pattern.compile("^[a-zA-Z_][a-zA-Z0-9_]*$");
    private static final Set<String> SYSTEM_COLUMNS = new HashSet<>(Arrays.asList(
            "id", "company_code", "writer", "created_date", "updated_date"));

    // ── 기존 위임 메서드 ──────────────────────────────────────────────────────

    @Transactional
    public Map<String, Object> upsertGrouped(Map<String, Object> params) {
        return dataService.upsertGroupedRecords(params);
    }

    @Transactional
    public Map<String, Object> deleteByCondition(Map<String, Object> params) {
        return dataService.deleteByCompositeKey(params);
    }

    @Transactional
    public Map<String, Object> deleteGroup(Map<String, Object> params) {
        return dataService.deleteGroupRecords(params);
    }

    // ── 다중 테이블 자동 감지 ─────────────────────────────────────────────────

    @SuppressWarnings("unchecked")
    public Map<String, Object> autoDetectMultiTable(Map<String, Object> params) {
        String rootTable = (String) params.get("root_table");
        if (rootTable == null || !IDENTIFIER.matcher(rootTable).matches()) {
            throw new IllegalArgumentException("유효한 rootTable 파라미터가 필요합니다.");
        }
        Integer exists = jdbcTemplate.queryForObject(
                "SELECT COUNT(*) FROM information_schema.tables WHERE table_schema='public' AND table_name=?",
                Integer.class, rootTable);
        if (exists == null || exists == 0) {
            throw new IllegalArgumentException("테이블이 존재하지 않습니다: " + rootTable);
        }

        String companyCode = (String) params.get("company_code");
        List<Map<String, Object>> levels = new ArrayList<>();
        buildLevelChain(rootTable, companyCode, levels, 0, 3);

        List<Map<String, Object>> uploadModes = new ArrayList<>();
        for (int depth = 0; depth < levels.size(); depth++) {
            List<Integer> activeLevels = new ArrayList<>();
            for (int i = 0; i <= depth; i++) activeLevels.add(i);
            String levelNames = activeLevels.stream()
                    .map(i -> (String) levels.get(i).get("label"))
                    .collect(Collectors.joining(" + "));
            Map<String, Object> mode = new LinkedHashMap<>();
            mode.put("id", "level_" + depth);
            mode.put("label", levelNames + " 업로드");
            mode.put("description", levelNames + " 데이터를 일괄 업로드합니다.");
            mode.put("active_levels", activeLevels);
            uploadModes.add(mode);
        }

        Map<String, Object> config = new LinkedHashMap<>();
        config.put("id", rootTable + "_chain");
        config.put("name", rootTable + " 체인 업로드");
        config.put("description", "자동 감지된 테이블 체인");
        config.put("levels", levels);
        config.put("upload_modes", uploadModes);
        return config;
    }

    private void buildLevelChain(String tableName, String companyCode,
                                  List<Map<String, Object>> levels, int depth, int maxDepth) {
        if (depth >= maxDepth) return;
        boolean alreadyAdded = levels.stream().anyMatch(l -> tableName.equals(l.get("table_name")));
        if (alreadyAdded) return;

        List<Map<String, Object>> dbCols = jdbcTemplate.queryForList(
                "SELECT column_name, data_type FROM information_schema.columns " +
                "WHERE table_schema='public' AND table_name=? ORDER BY ordinal_position",
                tableName);

        List<Map<String, Object>> columns = dbCols.stream()
                .filter(c -> !SYSTEM_COLUMNS.contains(c.get("column_name")))
                .map(c -> {
                    Map<String, Object> col = new LinkedHashMap<>();
                    col.put("db_column", c.get("column_name"));
                    col.put("excel_header", c.get("column_name"));
                    col.put("required", false);
                    return col;
                })
                .collect(Collectors.toList());

        String parentFkColumn = null;
        String parentRefColumn = null;
        if (depth > 0 && !levels.isEmpty()) {
            String parentTable = (String) levels.get(levels.size() - 1).get("table_name");
            Map<String, String> fkInfo = findFkColumn(tableName, parentTable);
            if (fkInfo != null) {
                parentFkColumn = fkInfo.get("fk_column");
                parentRefColumn = fkInfo.get("ref_column");
            }
        }

        Map<String, Object> level = new LinkedHashMap<>();
        level.put("table_name", tableName);
        level.put("label", tableName);
        level.put("parent_fk_column", parentFkColumn);
        level.put("parent_ref_column", parentRefColumn);
        level.put("upsert_mode", "upsert");
        level.put("upsert_key_columns", Collections.emptyList());
        level.put("columns", columns);
        levels.add(level);

        List<Map<String, Object>> children = jdbcTemplate.queryForList(
                "SELECT DISTINCT kcu2.table_name AS child_table " +
                "FROM information_schema.referential_constraints rc " +
                "JOIN information_schema.key_column_usage kcu1 " +
                "  ON kcu1.constraint_name = rc.unique_constraint_name AND kcu1.table_schema='public' " +
                "JOIN information_schema.key_column_usage kcu2 " +
                "  ON kcu2.constraint_name = rc.constraint_name AND kcu2.table_schema='public' " +
                "WHERE kcu1.table_name=? LIMIT 5",
                tableName);

        for (Map<String, Object> child : children) {
            String childTable = (String) child.get("child_table");
            if (childTable != null && IDENTIFIER.matcher(childTable).matches()) {
                buildLevelChain(childTable, companyCode, levels, depth + 1, maxDepth);
            }
        }
    }

    private Map<String, String> findFkColumn(String childTable, String parentTable) {
        List<Map<String, Object>> result = jdbcTemplate.queryForList(
                "SELECT kcu2.column_name AS fk_col, kcu1.column_name AS ref_col " +
                "FROM information_schema.referential_constraints rc " +
                "JOIN information_schema.key_column_usage kcu1 " +
                "  ON kcu1.constraint_name = rc.unique_constraint_name AND kcu1.table_schema='public' " +
                "JOIN information_schema.key_column_usage kcu2 " +
                "  ON kcu2.constraint_name = rc.constraint_name AND kcu2.table_schema='public' " +
                "WHERE kcu1.table_name=? AND kcu2.table_name=? LIMIT 1",
                parentTable, childTable);
        if (result.isEmpty()) return null;
        Map<String, String> fk = new HashMap<>();
        fk.put("fk_column", (String) result.get(0).get("fk_col"));
        fk.put("ref_column", (String) result.get(0).get("ref_col"));
        return fk;
    }

    // ── 마스터-디테일 관계 조회 ───────────────────────────────────────────────

    @SuppressWarnings("unchecked")
    public Map<String, Object> getMasterDetailRelation(Map<String, Object> params) {
        String screenIdStr = String.valueOf(params.get("screen_id"));
        int screenId;
        try {
            screenId = Integer.parseInt(screenIdStr);
        } catch (NumberFormatException e) {
            throw new IllegalArgumentException("유효한 screenId가 필요합니다.");
        }

        List<Map<String, Object>> layoutRows = jdbcTemplate.queryForList(
                "SELECT properties->>'componentConfig' AS config " +
                "FROM screen_layouts " +
                "WHERE screen_id=? AND component_type='component' " +
                "  AND properties->>'componentType'='split-panel-layout' LIMIT 1",
                screenId);

        if (layoutRows.isEmpty() || layoutRows.get(0).get("config") == null) {
            Map<String, Object> result = new LinkedHashMap<>();
            result.put("relation", null);
            result.put("message", "마스터-디테일 구조가 아닙니다.");
            return result;
        }

        try {
            String configJson = (String) layoutRows.get(0).get("config");
            Map<String, Object> config = objectMapper.readValue(configJson, Map.class);
            Map<String, Object> leftPanel = (Map<String, Object>) config.get("left_panel");
            Map<String, Object> rightPanel = (Map<String, Object>) config.get("right_panel");

            String masterTable = leftPanel != null ? (String) leftPanel.get("table_name") : null;
            String detailTable = rightPanel != null ? (String) rightPanel.get("table_name") : null;

            if (masterTable == null || detailTable == null) {
                Map<String, Object> result = new LinkedHashMap<>();
                result.put("relation", null);
                result.put("message", "마스터 또는 디테일 테이블명 없음");
                return result;
            }

            String masterKeyColumn = null;
            String detailFkColumn = null;
            Map<String, Object> relationCfg = rightPanel != null
                    ? (Map<String, Object>) rightPanel.get("relation") : null;
            if (relationCfg != null) {
                List<?> keys = (List<?>) relationCfg.get("keys");
                if (keys != null && !keys.isEmpty()) {
                    Map<String, Object> firstKey = (Map<String, Object>) keys.get(0);
                    masterKeyColumn = (String) firstKey.get("left_column");
                    detailFkColumn = (String) firstKey.get("right_column");
                } else {
                    masterKeyColumn = (String) relationCfg.get("left_column");
                    detailFkColumn = (String) relationCfg.get("foreign_key");
                }
            }

            if (masterKeyColumn == null || detailFkColumn == null) {
                List<Map<String, Object>> entityRows = jdbcTemplate.queryForList(
                        "SELECT column_name, reference_column FROM table_type_columns " +
                        "WHERE table_name=? AND input_type='entity' AND reference_table=? " +
                        "AND company_code='*' LIMIT 1",
                        detailTable, masterTable);
                if (!entityRows.isEmpty()) {
                    detailFkColumn = (String) entityRows.get(0).get("column_name");
                    masterKeyColumn = (String) entityRows.get(0).get("reference_column");
                }
            }

            if (masterKeyColumn == null || detailFkColumn == null) {
                Map<String, Object> result = new LinkedHashMap<>();
                result.put("relation", null);
                result.put("message", "조인 키 정보를 찾을 수 없음");
                return result;
            }

            Map<String, String> masterLabels = getColumnLabels(masterTable);
            Map<String, String> detailLabels = getColumnLabels(detailTable);

            List<Map<String, Object>> masterColumns = new ArrayList<>();
            List<?> leftCols = leftPanel != null ? (List<?>) leftPanel.get("columns") : Collections.emptyList();
            for (Object colObj : leftCols) {
                Map<String, Object> col = (Map<String, Object>) colObj;
                String colName = (String) col.get("name");
                Map<String, Object> ci = new LinkedHashMap<>();
                ci.put("name", colName);
                ci.put("label", masterLabels.getOrDefault(colName, colName));
                ci.put("input_type", "text");
                ci.put("is_from_master", true);
                masterColumns.add(ci);
            }

            final String finalDetailFkColumn = detailFkColumn;
            List<Map<String, Object>> detailColumns = new ArrayList<>();
            List<?> rightCols = rightPanel != null ? (List<?>) rightPanel.get("columns") : Collections.emptyList();
            for (Object colObj : rightCols) {
                Map<String, Object> col = (Map<String, Object>) colObj;
                String colName = (String) col.get("name");
                if (colName.equals(finalDetailFkColumn)) continue;
                Map<String, Object> ci = new LinkedHashMap<>();
                ci.put("name", colName);
                ci.put("label", detailLabels.getOrDefault(colName, colName));
                ci.put("input_type", "text");
                ci.put("is_from_master", false);
                detailColumns.add(ci);
            }

            Map<String, Object> result = new LinkedHashMap<>();
            result.put("master_table", masterTable);
            result.put("detail_table", detailTable);
            result.put("master_key_column", masterKeyColumn);
            result.put("detail_fk_column", detailFkColumn);
            result.put("master_columns", masterColumns);
            result.put("detail_columns", detailColumns);
            return result;

        } catch (Exception e) {
            log.error("마스터-디테일 관계 조회 실패: {}", e.getMessage());
            throw new RuntimeException("마스터-디테일 관계 조회 중 오류가 발생했습니다.", e);
        }
    }

    private Map<String, String> getColumnLabels(String tableName) {
        Map<String, String> labels = new LinkedHashMap<>();
        try {
            List<Map<String, Object>> rows = jdbcTemplate.queryForList(
                    "SELECT column_name, column_label FROM table_type_columns WHERE table_name=? AND company_code='*'",
                    tableName);
            for (Map<String, Object> row : rows) {
                String col = (String) row.get("column_name");
                String lbl = (String) row.get("column_label");
                labels.put(col, lbl != null ? lbl : col);
            }
        } catch (Exception e) {
            log.warn("컬럼 라벨 조회 실패: {}", e.getMessage());
        }
        return labels;
    }

    // ── 마스터-디테일 다운로드 ────────────────────────────────────────────────

    @SuppressWarnings("unchecked")
    public Map<String, Object> downloadMasterDetail(Map<String, Object> params) {
        Map<String, Object> relation = getMasterDetailRelation(params);
        if (relation.get("master_table") == null) {
            throw new IllegalArgumentException(
                    (String) relation.getOrDefault("message", "마스터-디테일 구조가 아닙니다."));
        }

        String masterTable = (String) relation.get("master_table");
        String detailTable = (String) relation.get("detail_table");
        String masterKeyColumn = (String) relation.get("master_key_column");
        String detailFkColumn = (String) relation.get("detail_fk_column");
        List<Map<String, Object>> masterColumns = (List<Map<String, Object>>) relation.get("master_columns");
        List<Map<String, Object>> detailColumns = (List<Map<String, Object>>) relation.get("detail_columns");
        String companyCode = (String) params.get("company_code");
        Map<String, Object> filters = params.get("filters") instanceof Map
                ? (Map<String, Object>) params.get("filters") : null;

        List<String> selectParts = new ArrayList<>();
        List<String> headers = new ArrayList<>();
        List<String> columns = new ArrayList<>();
        List<String> masterColNames = new ArrayList<>();
        List<String> detailColNames = new ArrayList<>();

        for (Map<String, Object> col : masterColumns) {
            String name = (String) col.get("name");
            selectParts.add(name.contains(".") ? "NULL AS \"" + name + "\"" : "m.\"" + name + "\"");
            headers.add((String) col.get("label"));
            columns.add(name);
            masterColNames.add(name);
        }
        for (Map<String, Object> col : detailColumns) {
            String name = (String) col.get("name");
            selectParts.add(name.contains(".") ? "NULL AS \"" + name + "\"" : "d.\"" + name + "\"");
            headers.add((String) col.get("label"));
            columns.add(name);
            detailColNames.add(name);
        }

        List<String> whereConditions = new ArrayList<>();
        List<Object> args = new ArrayList<>();
        if (companyCode != null && !companyCode.isEmpty() && !"*".equals(companyCode)) {
            whereConditions.add("m.company_code = ?");
            args.add(companyCode);
        }
        if (filters != null) {
            for (Map.Entry<String, Object> entry : filters.entrySet()) {
                String key = entry.getKey();
                if (entry.getValue() == null || key.contains(".") || !IDENTIFIER.matcher(key).matches()) continue;
                boolean isMasterCol = masterColNames.contains(key);
                whereConditions.add((isMasterCol ? "m" : "d") + ".\"" + key + "\" = ?");
                args.add(entry.getValue());
            }
        }
        String whereClause = whereConditions.isEmpty()
                ? "" : "WHERE " + String.join(" AND ", whereConditions);

        String detailOrderCol = "d.\"" + detailFkColumn + "\"";
        try {
            Integer idExists = jdbcTemplate.queryForObject(
                    "SELECT COUNT(*) FROM information_schema.columns WHERE table_name=? AND column_name='id'",
                    Integer.class, detailTable);
            if (idExists != null && idExists > 0) detailOrderCol = "d.\"id\"";
        } catch (Exception ignored) {}

        String sql = "SELECT " + String.join(", ", selectParts) +
                " FROM \"" + masterTable + "\" m" +
                " LEFT JOIN \"" + detailTable + "\" d" +
                " ON m.\"" + masterKeyColumn + "\" = d.\"" + detailFkColumn + "\"" +
                " AND m.company_code = d.company_code" +
                " " + whereClause +
                " ORDER BY m.\"" + masterKeyColumn + "\", " + detailOrderCol;

        List<Map<String, Object>> data = jdbcTemplate.queryForList(sql, args.toArray());

        Map<String, Object> result = new LinkedHashMap<>();
        result.put("headers", headers);
        result.put("columns", columns);
        result.put("data", data);
        result.put("master_columns", masterColNames);
        result.put("detail_columns", detailColNames);
        result.put("join_key", masterKeyColumn);
        return result;
    }

    // ── 마스터-디테일 업로드 ──────────────────────────────────────────────────

    @Transactional
    @SuppressWarnings("unchecked")
    public Map<String, Object> uploadMasterDetail(Map<String, Object> params) {
        Map<String, Object> relation = getMasterDetailRelation(params);
        if (relation.get("master_table") == null) {
            throw new IllegalArgumentException(
                    (String) relation.getOrDefault("message", "마스터-디테일 구조가 아닙니다."));
        }

        String masterTable = (String) relation.get("master_table");
        String detailTable = (String) relation.get("detail_table");
        String masterKeyColumn = (String) relation.get("master_key_column");
        String detailFkColumn = (String) relation.get("detail_fk_column");
        List<Map<String, Object>> masterCols = (List<Map<String, Object>>) relation.get("master_columns");
        String companyCode = (String) params.get("company_code");
        String userId = (String) params.get("user_id");
        List<?> data = (List<?>) params.get("data");
        if (data == null) throw new IllegalArgumentException("data 배열이 필요합니다.");

        Set<String> masterColSet = masterCols.stream()
                .map(c -> (String) c.get("name")).collect(Collectors.toSet());
        Set<String> masterValidCols = getValidColumns(masterTable);
        Set<String> detailValidCols = getValidColumns(detailTable);

        int masterInserted = 0, masterUpdated = 0, detailInserted = 0;
        List<String> errors = new ArrayList<>();

        for (Object rowObj : data) {
            if (!(rowObj instanceof Map)) continue;
            Map<String, Object> row = (Map<String, Object>) rowObj;

            Map<String, Object> masterData = new LinkedHashMap<>();
            Map<String, Object> detailData = new LinkedHashMap<>();
            for (Map.Entry<String, Object> entry : row.entrySet()) {
                String key = entry.getKey();
                if (masterColSet.contains(key) && masterValidCols.contains(key)) {
                    masterData.put(key, entry.getValue());
                } else if (detailValidCols.contains(key)) {
                    detailData.put(key, entry.getValue());
                }
            }
            if (companyCode != null && !companyCode.isEmpty()) {
                if (masterValidCols.contains("company_code")) masterData.put("company_code", companyCode);
                if (detailValidCols.contains("company_code")) detailData.put("company_code", companyCode);
            }
            if (userId != null) {
                if (masterValidCols.contains("writer")) masterData.put("writer", userId);
                if (detailValidCols.contains("writer")) detailData.put("writer", userId);
            }

            Object masterKeyValue = masterData.get(masterKeyColumn);
            if (masterKeyValue == null) continue;

            try {
                List<Map<String, Object>> existing = jdbcTemplate.queryForList(
                        "SELECT * FROM \"" + masterTable + "\" WHERE \"" + masterKeyColumn + "\" = ? LIMIT 1",
                        masterKeyValue);
                if (existing.isEmpty()) {
                    insertRow(masterTable, masterData, masterValidCols);
                    masterInserted++;
                } else {
                    updateRow(masterTable, masterKeyColumn, masterKeyValue, masterData, masterValidCols);
                    masterUpdated++;
                }
                detailData.put(detailFkColumn, masterKeyValue);
                insertRow(detailTable, detailData, detailValidCols);
                detailInserted++;
            } catch (Exception e) {
                errors.add("마스터키 " + masterKeyValue + " 처리 실패: " + e.getMessage());
                log.error("마스터-디테일 업로드 행 처리 실패: {}", e.getMessage());
            }
        }

        Map<String, Object> result = new LinkedHashMap<>();
        result.put("master_inserted", masterInserted);
        result.put("master_updated", masterUpdated);
        result.put("detail_inserted", detailInserted);
        result.put("detail_updated", 0);
        result.put("detail_deleted", 0);
        result.put("errors", errors);
        result.put("success", errors.isEmpty());
        return result;
    }

    // ── 마스터-디테일 간단 모드 업로드 ───────────────────────────────────────

    @Transactional
    @SuppressWarnings("unchecked")
    public Map<String, Object> uploadMasterDetailSimple(Map<String, Object> params) {
        Map<String, Object> relation = getMasterDetailRelation(params);
        if (relation.get("master_table") == null) {
            throw new IllegalArgumentException(
                    (String) relation.getOrDefault("message", "마스터-디테일 구조가 아닙니다."));
        }

        String masterTable = (String) relation.get("master_table");
        String detailTable = (String) relation.get("detail_table");
        String masterKeyColumn = (String) relation.get("master_key_column");
        String detailFkColumn = (String) relation.get("detail_fk_column");
        String companyCode = (String) params.get("company_code");
        String userId = (String) params.get("user_id");
        List<?> detailData = (List<?>) params.get("detail_data");
        Map<String, Object> masterFieldValues = params.get("master_field_values") instanceof Map
                ? (Map<String, Object>) params.get("master_field_values") : new HashMap<>();
        Object numberingRuleId = params.get("numbering_rule_id");
        if (detailData == null) throw new IllegalArgumentException("detailData 배열이 필요합니다.");

        Set<String> masterValidCols = getValidColumns(masterTable);
        Set<String> detailValidCols = getValidColumns(detailTable);

        String generatedKey = numberingRuleId != null
                ? generateKeyFromRule(numberingRuleId.toString(), companyCode)
                : UUID.randomUUID().toString();

        Map<String, Object> masterData = new LinkedHashMap<>(masterFieldValues);
        masterData.put(masterKeyColumn, generatedKey);
        if (companyCode != null && masterValidCols.contains("company_code")) {
            masterData.put("company_code", companyCode);
        }
        if (userId != null && masterValidCols.contains("writer")) {
            masterData.put("writer", userId);
        }
        insertRow(masterTable, masterData, masterValidCols);

        int detailInserted = 0;
        List<String> errors = new ArrayList<>();
        for (Object rowObj : detailData) {
            if (!(rowObj instanceof Map)) continue;
            Map<String, Object> row = new LinkedHashMap<>((Map<String, Object>) rowObj);
            row.put(detailFkColumn, generatedKey);
            if (companyCode != null && detailValidCols.contains("company_code")) {
                row.put("company_code", companyCode);
            }
            if (userId != null && detailValidCols.contains("writer")) {
                row.put("writer", userId);
            }
            try {
                insertRow(detailTable, row, detailValidCols);
                detailInserted++;
            } catch (Exception e) {
                errors.add("디테일 행 삽입 실패: " + e.getMessage());
                log.error("디테일 행 삽입 실패: {}", e.getMessage());
            }
        }

        Map<String, Object> result = new LinkedHashMap<>();
        result.put("master_inserted", 1);
        result.put("detail_inserted", detailInserted);
        result.put("generated_key", generatedKey);
        result.put("errors", errors);
        result.put("success", errors.isEmpty());
        return result;
    }

    // ── 다중 테이블 업로드 ────────────────────────────────────────────────────

    @Transactional
    @SuppressWarnings("unchecked")
    public Map<String, Object> uploadMultiTable(Map<String, Object> params) {
        Map<String, Object> config = params.get("config") instanceof Map
                ? (Map<String, Object>) params.get("config") : null;
        String modeId = (String) params.get("mode_id");
        List<?> rows = (List<?>) params.get("rows");
        String companyCode = (String) params.get("company_code");
        String userId = (String) params.get("user_id");

        if (config == null || modeId == null || rows == null) {
            throw new IllegalArgumentException("config, modeId, rows 배열이 필요합니다.");
        }

        List<?> allLevels = config.get("levels") instanceof List
                ? (List<?>) config.get("levels") : Collections.emptyList();
        List<?> uploadModes = config.get("upload_modes") instanceof List
                ? (List<?>) config.get("upload_modes") : Collections.emptyList();

        Map<String, Object> mode = null;
        for (Object m : uploadModes) {
            if (m instanceof Map && modeId.equals(((Map<?, ?>) m).get("id"))) {
                mode = (Map<String, Object>) m;
                break;
            }
        }
        if (mode == null) throw new IllegalArgumentException("업로드 모드를 찾을 수 없습니다: " + modeId);

        List<?> activeLevelIndices = (List<?>) mode.get("active_levels");
        if (activeLevelIndices == null || activeLevelIndices.isEmpty()) {
            throw new IllegalArgumentException("활성화된 테이블 레벨이 없습니다.");
        }

        List<Map<String, Object>> activeLevels = new ArrayList<>();
        for (Object idx : activeLevelIndices) {
            int i = ((Number) idx).intValue();
            if (i < allLevels.size()) activeLevels.add((Map<String, Object>) allLevels.get(i));
        }

        List<Map<String, Object>> results = new ArrayList<>();
        for (Map<String, Object> lv : activeLevels) {
            Map<String, Object> lr = new LinkedHashMap<>();
            lr.put("table_name", lv.get("table_name"));
            lr.put("inserted", 0);
            lr.put("updated", 0);
            results.add(lr);
        }

        Map<String, Set<String>> colsCache = new LinkedHashMap<>();
        for (Map<String, Object> lv : activeLevels) {
            String tbl = (String) lv.get("table_name");
            colsCache.put(tbl, getValidColumns(tbl));
        }

        List<String> errors = new ArrayList<>();
        List<Map<String, Object>> pkCaches = new ArrayList<>();
        for (int i = 0; i < activeLevels.size(); i++) pkCaches.add(new LinkedHashMap<>());

        for (int rowIdx = 0; rowIdx < rows.size(); rowIdx++) {
            Object rowObj = rows.get(rowIdx);
            if (!(rowObj instanceof Map)) continue;
            Map<String, Object> row = (Map<String, Object>) rowObj;

            try {
                Object parentId = null;
                Map<String, Object> parentLevelData = new LinkedHashMap<>();

                for (int lvlIdx = 0; lvlIdx < activeLevels.size(); lvlIdx++) {
                    Map<String, Object> lv = activeLevels.get(lvlIdx);
                    String tableName = (String) lv.get("table_name");
                    Set<String> existingCols = colsCache.get(tableName);
                    Map<String, Object> levelResult = results.get(lvlIdx);

                    Map<String, Object> levelData = new LinkedHashMap<>();
                    List<?> colDefs = (List<?>) lv.get("columns");
                    if (colDefs != null) {
                        for (Object colObj : colDefs) {
                            Map<String, Object> colDef = (Map<String, Object>) colObj;
                            String excelHeader = (String) colDef.get("excel_header");
                            String dbCol = (String) colDef.get("db_column");
                            Object val = row.get(excelHeader);
                            if (val != null && !val.toString().isEmpty()) {
                                levelData.put(dbCol, val);
                            } else if (colDef.get("default_value") != null) {
                                levelData.put(dbCol, colDef.get("default_value"));
                            }
                        }
                    }

                    if (levelData.isEmpty() && lvlIdx > 0) break;

                    String parentFkColumn = (String) lv.get("parent_fk_column");
                    String parentRefColumn = (String) lv.get("parent_ref_column");
                    if (lvlIdx > 0 && parentFkColumn != null && parentId != null) {
                        if (parentRefColumn != null && !"id".equals(parentRefColumn)
                                && parentLevelData.containsKey(parentRefColumn)) {
                            levelData.put(parentFkColumn, String.valueOf(parentLevelData.get(parentRefColumn)));
                        } else {
                            levelData.put(parentFkColumn, String.valueOf(parentId));
                        }
                    }

                    if (existingCols.contains("company_code") && companyCode != null) {
                        levelData.put("company_code", companyCode);
                    }
                    if (existingCols.contains("writer") && userId != null) {
                        levelData.put("writer", userId);
                    }

                    String upsertMode = (String) lv.get("upsert_mode");
                    List<?> upsertKeyCols = (List<?>) lv.get("upsert_key_columns");
                    String upsertKey = null;
                    if (upsertKeyCols != null && !upsertKeyCols.isEmpty()) {
                        upsertKey = upsertKeyCols.stream()
                                .map(k -> String.valueOf(levelData.getOrDefault(k, "")))
                                .collect(Collectors.joining("|||"));
                    }

                    Object returnedId;
                    if ("upsert".equals(upsertMode) && upsertKey != null) {
                        if (pkCaches.get(lvlIdx).containsKey(upsertKey)) {
                            returnedId = pkCaches.get(lvlIdx).get(upsertKey);
                        } else {
                            returnedId = upsertRowForChain(
                                    tableName, lv, levelData, existingCols, companyCode, levelResult);
                            pkCaches.get(lvlIdx).put(upsertKey, returnedId);
                        }
                    } else {
                        returnedId = insertRow(tableName, levelData, existingCols);
                        levelResult.put("inserted", ((Number) levelResult.get("inserted")).intValue() + 1);
                    }

                    parentId = returnedId;
                    parentLevelData = new LinkedHashMap<>(levelData);
                }
            } catch (Exception e) {
                errors.add("[행 " + (rowIdx + 1) + "] 처리 실패: " + e.getMessage());
                log.error("[행 {}] 처리 실패:", rowIdx + 1, e);
            }
        }

        boolean success = errors.isEmpty() || results.stream()
                .anyMatch(r -> ((Number) r.get("inserted")).intValue()
                        + ((Number) r.get("updated")).intValue() > 0);

        Map<String, Object> result = new LinkedHashMap<>();
        result.put("success", success);
        result.put("results", results);
        result.put("total_rows", rows.size());
        result.put("errors", errors);
        return result;
    }

    // ── 공통 헬퍼 ────────────────────────────────────────────────────────────

    private Set<String> getValidColumns(String tableName) {
        List<Map<String, Object>> cols = jdbcTemplate.queryForList(
                "SELECT column_name FROM information_schema.columns WHERE table_schema='public' AND table_name=?",
                tableName);
        return cols.stream().map(c -> (String) c.get("column_name")).collect(Collectors.toSet());
    }

    private Object insertRow(String tableName, Map<String, Object> data, Set<String> validCols) {
        List<String> cols = new ArrayList<>();
        List<Object> vals = new ArrayList<>();
        for (Map.Entry<String, Object> e : data.entrySet()) {
            if (!validCols.contains(e.getKey()) || "id".equals(e.getKey())) continue;
            cols.add("\"" + e.getKey() + "\"");
            vals.add(e.getValue());
        }
        if (cols.isEmpty()) return null;
        boolean hasCreatedDate = validCols.contains("created_date");
        String colStr = String.join(", ", cols) + (hasCreatedDate ? ", created_date" : "");
        String placeholders = cols.stream().map(c -> "?").collect(Collectors.joining(", "));
        String valStr = placeholders + (hasCreatedDate ? ", NOW()" : "");
        String sql = "INSERT INTO \"" + tableName + "\" (" + colStr + ") VALUES (" + valStr + ") RETURNING id";
        try {
            List<Map<String, Object>> returned = jdbcTemplate.queryForList(sql, vals.toArray());
            return returned.isEmpty() ? null : returned.get(0).get("id");
        } catch (Exception e) {
            // id 컬럼이 없거나 RETURNING 미지원 시 fallback
            String sqlNoReturn = "INSERT INTO \"" + tableName + "\" (" + colStr + ") VALUES (" + valStr + ")";
            jdbcTemplate.update(sqlNoReturn, vals.toArray());
            return null;
        }
    }

    private void updateRow(String tableName, String pkCol, Object pkVal,
                           Map<String, Object> data, Set<String> validCols) {
        List<String> sets = new ArrayList<>();
        List<Object> args = new ArrayList<>();
        for (Map.Entry<String, Object> e : data.entrySet()) {
            if (!validCols.contains(e.getKey()) || e.getKey().equals(pkCol)) continue;
            sets.add("\"" + e.getKey() + "\" = ?");
            args.add(e.getValue());
        }
        if (sets.isEmpty()) return;
        if (validCols.contains("updated_date")) sets.add("updated_date = NOW()");
        args.add(pkVal);
        jdbcTemplate.update("UPDATE \"" + tableName + "\" SET " + String.join(", ", sets) +
                " WHERE \"" + pkCol + "\" = ?", args.toArray());
    }

    @SuppressWarnings("unchecked")
    private Object upsertRowForChain(String tableName, Map<String, Object> level,
                                      Map<String, Object> data, Set<String> existingCols,
                                      String companyCode, Map<String, Object> levelResult) {
        List<?> upsertKeyCols = (List<?>) level.get("upsert_key_columns");
        if (upsertKeyCols == null || upsertKeyCols.isEmpty()) {
            Object id = insertRow(tableName, data, existingCols);
            levelResult.put("inserted", ((Number) levelResult.get("inserted")).intValue() + 1);
            return id;
        }

        List<String> whereParts = new ArrayList<>();
        List<Object> whereArgs = new ArrayList<>();
        for (Object k : upsertKeyCols) {
            whereParts.add("\"" + k + "\" = ?");
            whereArgs.add(data.get(k));
        }
        if (existingCols.contains("company_code") && companyCode != null && !"*".equals(companyCode)) {
            whereParts.add("company_code = ?");
            whereArgs.add(companyCode);
        }

        List<Map<String, Object>> existing = jdbcTemplate.queryForList(
                "SELECT id FROM \"" + tableName + "\" WHERE " +
                String.join(" AND ", whereParts) + " LIMIT 1",
                whereArgs.toArray());

        if (!existing.isEmpty()) {
            Object existingId = existing.get(0).get("id");
            Set<String> skipCols = new HashSet<>(Arrays.asList("id", "company_code", "created_date"));
            upsertKeyCols.forEach(k -> skipCols.add(k.toString()));
            List<String> sets = new ArrayList<>();
            List<Object> setArgs = new ArrayList<>();
            for (Map.Entry<String, Object> e : data.entrySet()) {
                if (!existingCols.contains(e.getKey()) || skipCols.contains(e.getKey())) continue;
                sets.add("\"" + e.getKey() + "\" = ?");
                setArgs.add(e.getValue());
            }
            if (!sets.isEmpty()) {
                if (existingCols.contains("updated_date")) sets.add("updated_date = NOW()");
                setArgs.add(existingId);
                jdbcTemplate.update("UPDATE \"" + tableName + "\" SET " +
                        String.join(", ", sets) + " WHERE id = ?", setArgs.toArray());
            }
            levelResult.put("updated", ((Number) levelResult.get("updated")).intValue() + 1);
            return existingId;
        } else {
            Object id = insertRow(tableName, data, existingCols);
            levelResult.put("inserted", ((Number) levelResult.get("inserted")).intValue() + 1);
            return id;
        }
    }

    private String generateKeyFromRule(String ruleId, String companyCode) {
        try {
            List<Map<String, Object>> rules = jdbcTemplate.queryForList(
                    "SELECT * FROM numbering_rules WHERE id::text = ? " +
                    "AND (company_code = ? OR company_code = '*') LIMIT 1",
                    ruleId, companyCode);
            if (!rules.isEmpty()) {
                Map<String, Object> rule = rules.get(0);
                String prefix = rule.get("prefix") != null ? rule.get("prefix").toString() : "";
                return prefix + UUID.randomUUID().toString().substring(0, 8).toUpperCase();
            }
        } catch (Exception e) {
            log.warn("채번 규칙 조회 실패: {}", e.getMessage());
        }
        return UUID.randomUUID().toString();
    }
}
