package incheon.ags.mrb.chart.service.impl;

import incheon.ags.mrb.chart.web.dto.ChartQueryParams;
import incheon.ags.mrb.chart.mapper.ChartDataMapper;
import incheon.ags.mrb.chart.service.ChartDataService;
import incheon.cmm.g2f.layer.mapper.G2FLayerMapper;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import java.util.Arrays;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.regex.Pattern;

import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;

@Service
public class ChartDataServiceImpl implements ChartDataService {

    private final static Logger logger = LoggerFactory.getLogger(ChartDataServiceImpl.class);

    @Autowired
    private ChartDataMapper chartDataMapper;
    
    @Autowired
    private G2FLayerMapper g2fLayerMapper;

    // 테이블명 검증을 위한 정규식 (schema.table 형식)
    private static final Pattern TABLE_NAME_PATTERN = Pattern.compile("^[a-zA-Z_][a-zA-Z0-9_]*\\.[a-zA-Z_][a-zA-Z0-9_]*$");

    // 하드코딩된 필드 목록 제거 - 실제 레이어에서 동적으로 검증
    private static final List<String> VALID_AGGREGATIONS = Arrays.asList(
            "count", "sum", "avg", "median", "min", "max", "none"
    );
    //    private static final List<String> VALID_SORTS = Arrays.asList("asc", "desc", "yasc", "ydesc");
    private static final List<String> VALID_SORTS = Arrays.asList("asc", "desc", "yasc", "ydesc", "avgasc", "avgdesc", "medianasc", "mediandesc");

    @Override
    public List<Map<String, Object>> getChartData(ChartQueryParams params) {
        // 디버깅: 입력 파라미터 로깅
        logger.debug("Received params: " + params);

        // 기본적인 필수 파라미터 검증 (필드명 검증은 제거 - 실제 레이어에서 동적으로 검증)
        if ("boxplot".equals(params.getChartType())) {
            if (params.getValues() == null || params.getValues().isEmpty()) {
                throw new IllegalArgumentException("At least one value field is required for box chart type");
            }
        }

        if ("histogram".equals(params.getChartType()) && (params.getNumberField() == null || params.getNumberField().isEmpty())) {
            throw new IllegalArgumentException("Number field is required for histogram chart type");
        }
        if ("histogram".equals(params.getChartType()) && (params.getBinCount() == null || params.getBinCount() <= 0)) {
            throw new IllegalArgumentException("Bin count is required and must be positive for histogram chart type");
        }

        if ("line".equals(params.getChartType())) {
            if (params.getCategory() == null || params.getCategory().isEmpty()) {
                throw new IllegalArgumentException("Category is required for line chart type");
            }
            // values 검증 제거 - 빈 배열도 허용 (사용자가 아직 선택하지 않은 중간 상태)
            // if (params.getAggregation() != null && !"count".equals(params.getAggregation()) && (params.getValues() == null || params.getValues().isEmpty())) {
            //     throw new IllegalArgumentException("At least one value field is required for aggregation: " + params.getAggregation());
            // }
        }

        if ("bar".equals(params.getChartType()) || "pie".equals(params.getChartType())) {
            if (params.getAggregation() == null || !VALID_AGGREGATIONS.contains(params.getAggregation())) {
                throw new IllegalArgumentException("Invalid aggregation function: " + params.getAggregation());
            }
            // values 검증 제거 - 빈 배열도 허용 (사용자가 아직 선택하지 않은 중간 상태)
            // if (!"count".equals(params.getAggregation()) && (params.getValues() == null || params.getValues().isEmpty())) {
            //     throw new IllegalArgumentException("At least one value field is required for aggregation: " + params.getAggregation());
            // }
            if (params.getSplit() != null && params.getValues() != null && params.getValues().size() > 1) {
                throw new IllegalArgumentException("Split is not allowed with multiple value fields");
            }
            if (params.getSort() != null && !VALID_SORTS.contains(params.getSort())) {
                throw new IllegalArgumentException("Invalid sort option: " + params.getSort());
            }
            if (params.getMode() != null && !Arrays.asList("category", "field").contains(params.getMode())) {
                throw new IllegalArgumentException("Invalid mode: " + params.getMode());
            }
        }

        // 테이블명 조회 (layerId 필수)
        String tableName = params.getTableName();
        if (tableName == null) {
            if (params.getLayerId() == null) {
                throw new IllegalArgumentException("layerId는 필수입니다. 차트를 생성하려면 레이어를 선택해야 합니다.");
            }
            // 레이어 정보로 테이블명 조회
            tableName = getTableNameByLayerId(params.getLayerId(), params.getLayerType());
        }

        // 필드명 검증: 테이블의 실제 컬럼 목록과 비교
        List<Map<String, Object>> tableColumns = getTableColumns(tableName);
        Set<String> validColumns = new HashSet<>();
        for (Map<String, Object> col : tableColumns) {
            String colName = (String) col.get("column_name");
            if (colName != null) {
                validColumns.add(colName.toLowerCase());
            }
        }

        // 요청된 필드명들 검증
        List<String> invalidFields = new java.util.ArrayList<>();

        if (params.getCategory() != null && !params.getCategory().isEmpty()) {
            if (!validColumns.contains(params.getCategory().toLowerCase())) {
                invalidFields.add("category: " + params.getCategory());
            }
        }

        if (params.getSplit() != null && !params.getSplit().isEmpty()) {
            if (!validColumns.contains(params.getSplit().toLowerCase())) {
                invalidFields.add("split: " + params.getSplit());
            }
        }

        if (params.getNumberField() != null && !params.getNumberField().isEmpty()) {
            if (!validColumns.contains(params.getNumberField().toLowerCase())) {
                invalidFields.add("numberField: " + params.getNumberField());
            }
        }

        if (params.getValues() != null && !params.getValues().isEmpty()) {
            for (String value : params.getValues()) {
                if (!validColumns.contains(value.toLowerCase())) {
                    invalidFields.add("values: " + value);
                }
            }
        }

        if (!invalidFields.isEmpty()) {
            throw new IllegalArgumentException(
                    "존재하지 않는 컬럼이 요청되었습니다. 테이블: " + tableName +
                            ", 잘못된 필드: " + String.join(", ", invalidFields) +
                            ". 사용 가능한 컬럼 목록을 확인해주세요."
            );
        }

        Map<String, Object> queryParams = new HashMap<>();
        queryParams.put("chartType", params.getChartType());
        queryParams.put("binCount", params.getBinCount());
        queryParams.put("aggregation", params.getAggregation());
        queryParams.put("sort", params.getSort());
        queryParams.put("mode", params.getMode());
        queryParams.put("tableName", tableName);
        queryParams.put("zScore", params.getZScore());
        queryParams.put("showOutliers", params.getShowOutliers());


        // 컬럼명 파라미터 (숫자만 있을 경우 따옴표 처리 필요)
        queryParams.put("numberField", escapeColumnName(params.getNumberField()));
        queryParams.put("category", escapeColumnName(params.getCategory()));
        queryParams.put("split", escapeColumnName(params.getSplit()));

        // values 리스트의 각 컬럼명도 따옴표 처리
        if (params.getValues() != null) {
            List<String> escapedValues = new java.util.ArrayList<>();
            for (String value : params.getValues()) {
                escapedValues.add(escapeColumnName(value));
            }
            queryParams.put("values", escapedValues);
        } else {
            queryParams.put("values", null);
        }

        List<Map<String, Object>> results = chartDataMapper.getChartData(queryParams);

        // 디버깅: 파이 차트 필드 모드 결과 확인
        if ("pieChart".equals(params.getChartType()) && "field".equals(params.getMode())) {
            System.out.println("[ChartDataService] 파이 차트 필드 모드 - 쿼리 결과:");
            if (results != null) {
                for (Map<String, Object> row : results) {
                    System.out.println("  row: " + row);
                }
            }
        }

        // chartType 정규화: "bar" 또는 "barChart" 모두 처리
        String chartType = params.getChartType();
        if ("bar".equals(chartType)) chartType = "barChart";
        if ("line".equals(chartType)) chartType = "lineChart";
        if ("pie".equals(chartType)) chartType = "pieChart";
        if ("box".equals(chartType)) chartType = "boxplot";
        
        // 필드 null 값 체크: 모든 값이 null인 필드가 있으면 에러 (한 번의 순회로 최적화)
        if (results != null && !results.isEmpty()) {
            List<String> allNullFields = new java.util.ArrayList<>();
            
            // 체크할 필드 목록 수집
            List<String> fieldsToCheck = new java.util.ArrayList<>();
            
            if ("barChart".equals(chartType) || "lineChart".equals(chartType)) {
                if (params.getCategory() != null && !params.getCategory().isEmpty()) {
                    fieldsToCheck.add("category:" + params.getCategory());
                }
                if (params.getSplit() != null && !params.getSplit().isEmpty()) {
                    fieldsToCheck.add("split:" + params.getSplit());
                }
                if (!"count".equals(params.getAggregation()) && params.getValues() != null && !params.getValues().isEmpty()) {
                    for (String valueField : params.getValues()) {
                        fieldsToCheck.add("values:" + valueField);
                    }
                }
            } else if ("pieChart".equals(chartType)) {
                if ("category".equals(params.getMode()) || params.getMode() == null) {
                    if (params.getCategory() != null && !params.getCategory().isEmpty()) {
                        fieldsToCheck.add("category:" + params.getCategory());
                    }
                    if (!"count".equals(params.getAggregation()) && params.getValues() != null && !params.getValues().isEmpty()) {
                        for (String valueField : params.getValues()) {
                            fieldsToCheck.add("values:" + valueField);
                        }
                    }
                } else if ("field".equals(params.getMode())) {
                    // 필드 모드에서는 쿼리 결과가 { period: "필드명", count: 합계값 } 형태로 반환됨
                    // 원본 필드명이 아닌 "count" 컬럼을 체크해야 함
                    fieldsToCheck.add("pieFieldMode:count");
                }
            } else if ("boxplot".equals(chartType)) {
                if (params.getCategory() != null && !params.getCategory().isEmpty()) {
                    fieldsToCheck.add("category:" + params.getCategory());
                }
                if (params.getSplit() != null && !params.getSplit().isEmpty()) {
                    fieldsToCheck.add("split:" + params.getSplit());
                }
                if (params.getValues() != null && !params.getValues().isEmpty()) {
                    for (String valueField : params.getValues()) {
                        fieldsToCheck.add("values:" + valueField);
                    }
                }
            } else if ("histogram".equals(chartType)) {
                if (params.getNumberField() != null && !params.getNumberField().isEmpty()) {
                    fieldsToCheck.add("numberField:value");
                }
            }
            
            // 각 필드에 대해 null이 아닌 값이 있는지 추적 (초기값: 모두 null로 가정)
            Map<String, Boolean> fieldHasNonNull = new HashMap<>();
            for (String fieldInfo : fieldsToCheck) {
                fieldHasNonNull.put(fieldInfo, false);
            }
            
            // 한 번의 순회로 모든 필드 체크
            for (Map<String, Object> row : results) {
                if (row == null) continue;
                
                for (String fieldInfo : fieldsToCheck) {
                    // 이미 null이 아닌 값을 찾은 필드는 스킵
                    if (fieldHasNonNull.get(fieldInfo)) continue;
                    
                    String[] parts = fieldInfo.split(":", 2);
                    String fieldName = parts[1];
                    
                    Object value = row.get(fieldName);
                    if (value != null) {
                        // null이 아닌 모든 값은 유효한 값으로 간주
                        // 0도 유효한 데이터 값이므로 null이 아닌 것으로 처리
                        fieldHasNonNull.put(fieldInfo, true);
                    }
                }
                
                // 모든 필드에서 null이 아닌 값을 찾았으면 조기 종료
                boolean allFound = true;
                for (Boolean hasNonNull : fieldHasNonNull.values()) {
                    if (!hasNonNull) {
                        allFound = false;
                        break;
                    }
                }
                if (allFound) break;
            }
            
            // 디버깅: null 체크 결과 로깅
            if ("pieChart".equals(chartType) && "field".equals(params.getMode())) {
                System.out.println("[ChartDataService] 파이 차트 필드 모드 - null 체크 결과:");
                System.out.println("  fieldsToCheck: " + fieldsToCheck);
                System.out.println("  fieldHasNonNull: " + fieldHasNonNull);
            }

            // 모두 null인 필드 수집
            for (String fieldInfo : fieldsToCheck) {
                if (!fieldHasNonNull.get(fieldInfo)) {
                    String[] parts = fieldInfo.split(":", 2);
                    String fieldType = parts[0];
                    String fieldName = parts[1];
                    // pieFieldMode는 사용자 친화적 메시지로 변환
                    if ("pieFieldMode".equals(fieldType)) {
                        allNullFields.add("선택한 숫자 필드들의 합계");
                    } else if ("numberField".equals(fieldType)) {
                        allNullFields.add("numberField: " + params.getNumberField());
                    } else {
                        allNullFields.add(fieldType + ": " + fieldName);
                    }
                }
            }
            
            // 모두 null인 필드가 있으면 에러 (0도 null로 간주하여 메시지 통일)
            if (!allNullFields.isEmpty()) {
                throw new IllegalArgumentException(
                    "다음 필드의 모든 값이 null입니다: " + String.join(", ", allNullFields) + 
                    ". 차트를 생성할 수 없습니다."
                );
            }
        }
        
        // 차트 렌더링 항목 개수 제한 체크 (30개 초과 시 에러)
        // 가독성 기준: 실제 차트에 그려지는 요소의 개수
        // chartType은 위에서 이미 정규화됨
        // 최적화: results를 한 번만 순회하여 필요한 모든 정보 수집
        
        System.out.println("[ChartDataService] chartType: " + chartType + ", category: " + params.getCategory() + ", split: " + params.getSplit());
        
        int itemCount = 0;
        boolean shouldCheck = false;
        
        // results를 한 번만 순회하여 필요한 정보 수집
        if (results != null && !results.isEmpty() && 
            ("barChart".equals(chartType) || "lineChart".equals(chartType) || "boxplot".equals(chartType))) {
            
            shouldCheck = true;
            Set<Object> uniqueCategories = new HashSet<>();
            Set<Object> uniqueSplits = new HashSet<>();
            Set<String> uniqueCombinations = new HashSet<>();
            
            // 한 번의 순회로 category, split, 조합 정보 모두 수집
            for (Map<String, Object> row : results) {
                if (row == null) continue;
                
                // category 수집
                if (params.getCategory() != null && !params.getCategory().isEmpty()) {
                    Object categoryValue = row.get(params.getCategory());
                    if (categoryValue != null) {
                        uniqueCategories.add(categoryValue);
                    }
                }
                
                // split 수집
                if (params.getSplit() != null && !params.getSplit().isEmpty()) {
                    Object splitValue = row.get(params.getSplit());
                    if (splitValue != null) {
                        uniqueSplits.add(splitValue);
                    }
                }
                
                // boxplot용 조합 수집
                if ("boxplot".equals(chartType) && 
                    params.getCategory() != null && !params.getCategory().isEmpty() &&
                    params.getSplit() != null && !params.getSplit().isEmpty()) {
                    Object categoryValue = row.get(params.getCategory());
                    Object splitValue = row.get(params.getSplit());
                    if (categoryValue != null && splitValue != null) {
                        String combination = categoryValue.toString() + "|" + splitValue.toString();
                        uniqueCombinations.add(combination);
                    }
                }
            }
            
            // 차트 타입별 itemCount 계산
            if ("barChart".equals(chartType)) {
                // 막대 차트: 실제 막대 개수
                if (params.getCategory() != null && !params.getCategory().isEmpty()) {
                    int categoryCount = uniqueCategories.size();
                    
                    // split이 있으면: category 개수 × split 개수
                    // values가 여러 개면: category 개수 × values 개수
                    // 둘 다 없으면: category 개수
                    if (params.getSplit() != null && !params.getSplit().isEmpty()) {
                        itemCount = categoryCount * uniqueSplits.size();
                    } else {
                        List<String> values = params.getValues();
                        if (values != null && values.size() > 1) {
                            // values가 여러 개면: category 개수 × values 개수
                            itemCount = categoryCount * values.size();
                        } else {
                            // values가 1개이거나 없으면: category 개수
                            itemCount = categoryCount;
                        }
                    }
                } else {
                    itemCount = 0;
                }
            } else if ("lineChart".equals(chartType)) {
                // 선형 차트: 점의 개수 = category 고유값 개수 (X축 항목)
                // split이 있으면 선의 개수도 체크 (가독성 저하)
                if (params.getCategory() != null && !params.getCategory().isEmpty()) {
                    int categoryCount = uniqueCategories.size();
                    
                    // split이 있으면: 선의 개수 = split 고유값 개수
                    if (params.getSplit() != null && !params.getSplit().isEmpty()) {
                        // 점의 개수와 선의 개수 중 큰 값을 체크 (둘 다 가독성에 영향)
                        itemCount = Math.max(categoryCount, uniqueSplits.size());
                    } else {
                        // split이 없으면: 점의 개수만 체크
                        itemCount = categoryCount;
                    }
                } else {
                    itemCount = 0;
                }
            } else if ("boxplot".equals(chartType)) {
                // 박스 플롯: 실제 박스 개수
                // 프론트엔드에서 boxData.push를 할 때:
                // - category와 split이 모두 있으면: (category, split) 조합 개수 × values 개수
                // - category만 있으면: category 개수 × values 개수
                // - split만 있으면: split 개수 × values 개수
                // - 둘 다 없으면: values 개수만
                List<String> values = params.getValues();
                int valueCount = (values != null) ? values.size() : 0;
                
                if (params.getCategory() != null && !params.getCategory().isEmpty()) {
                    if (params.getSplit() != null && !params.getSplit().isEmpty()) {
                        // category와 split이 모두 있으면: 실제 (category, split) 조합의 고유 개수 × values 개수
                        itemCount = uniqueCombinations.size() * valueCount;
                        System.out.println("[ChartDataService] 박스플롯 계산: category=" + params.getCategory() + 
                            ", split=" + params.getSplit() + ", 조합개수=" + uniqueCombinations.size() + 
                            ", values개수=" + valueCount + ", 총박스개수=" + itemCount);
                    } else {
                        // category만 있으면: category 개수 × values 개수
                        itemCount = uniqueCategories.size() * valueCount;
                        System.out.println("[ChartDataService] 박스플롯 계산: category=" + params.getCategory() + 
                            ", category개수=" + uniqueCategories.size() + ", values개수=" + valueCount + 
                            ", 총박스개수=" + itemCount);
                    }
                } else if (params.getSplit() != null && !params.getSplit().isEmpty()) {
                    // split만 있으면: split 개수 × values 개수
                    itemCount = uniqueSplits.size() * valueCount;
                    System.out.println("[ChartDataService] 박스플롯 계산: split=" + params.getSplit() + 
                        ", split개수=" + uniqueSplits.size() + ", values개수=" + valueCount + 
                        ", 총박스개수=" + itemCount);
                } else {
                    // category와 split이 모두 없으면: values 개수만
                    itemCount = valueCount;
                    System.out.println("[ChartDataService] 박스플롯 계산: category/split 없음, values개수=" + valueCount + 
                        ", 총박스개수=" + itemCount);
                }
            }
        } else if ("pieChart".equals(chartType)) {
            // 파이 차트: 슬라이스 개수
            shouldCheck = true;
            if ("field".equals(params.getMode())) {
                // field 모드: value 필드 개수 = 슬라이스 개수
                List<String> values = params.getValues();
                itemCount = (values != null) ? values.size() : 0;
            } else {
                // category 모드: category 고유값 개수 = 슬라이스 개수
                itemCount = results != null ? results.size() : 0;
            }
        }
        
        System.out.println("[ChartDataService] shouldCheck: " + shouldCheck + ", itemCount: " + itemCount);
        
        // 차트 타입별 제한 개수 설정
        int limit = 30; // 기본값
        if ("boxplot".equals(chartType)) {
            limit = 30; // 박스플롯은 30개까지 허용
        }
        
        if (shouldCheck && itemCount > limit) {
            System.out.println("[ChartDataService] ERROR: 차트 항목이 " + limit + "개를 초과합니다. (현재: " + itemCount + "개)");
            throw new IllegalArgumentException(
                "CHART_ITEM_LIMIT_EXCEEDED:" + itemCount
            );
        }
        
        return results;
    }
    @Autowired private ObjectMapper objectMapper;

    public int saveChartConfig(Map<String, Object> chartConfig) {
        Map<String, Object> params = new HashMap<>();
        int chart_id = 0;
        try {
            // chartConfig에서 title 추출
            String title = chartConfig.get("chartTitleText") != null ? chartConfig.get("chartTitleText").toString() : null;
            params.put("title", title);
            params.put("chartType", chartConfig.get("chartType") != null ? chartConfig.get("chartType").toString() : null);
            params.put("configJson", objectMapper.writeValueAsString(chartConfig));
        } catch (JsonProcessingException e) {
            throw new RuntimeException("Failed to serialize chartConfig to JSON string", e);
        }

        Object chartIdObj = chartConfig.get("chartId");
        int affectedRows;
        if (chartIdObj != null) {
            params.put("chartId", chartIdObj); // chartId 추가
            affectedRows = chartDataMapper.updateChartConfig(params); // UPDATE 호출
        } else {
            affectedRows = chartDataMapper.insertChartConfig(params); // INSERT 호출
        }
        System.out.println("Affected rows: " + affectedRows); // 디버깅: 삽입/수정된 행 수
        System.out.println("Params after operation: " + params); // 디버깅: params 맵 확인

        if( affectedRows > 0 ) {
            chartIdObj = params.get("chartId");
            if (chartIdObj != null) chart_id = ((Number) chartIdObj).intValue();
        }

        return chart_id;
    }

    public Map<String, Object> loadChartConfig(int chartId) throws JsonProcessingException {
        Map<String, Object> row = chartDataMapper.selectChartConfig(Map.of("chartId", chartId));
        if (row == null) return null;
        Map<String, Object> data = new HashMap<>();
        data.put("chartConfig", objectMapper.readValue((String) row.get("config_json"), Map.class));
        data.put("title", row.get("title"));
        return data;
    }


    public List<Map<String, Object>> getChartList() {
        return chartDataMapper.selectAllChartConfigs();
    }

    public void deleteChartConfig(int chartId) {
        if (chartDataMapper.deleteChartConfig(Map.of("chartId", chartId)) == 0) {
            throw new IllegalArgumentException("No chart found with chartId: " + chartId);
        }
    }

    @Override
    public List<Map<String, Object>> getTableColumns(String tableName) {
        // 테이블명 유효성 검사 (SQL Injection 방지)
        if (tableName == null || tableName.trim().isEmpty()) {
            throw new IllegalArgumentException("테이블명은 필수입니다.");
        }

        if (!TABLE_NAME_PATTERN.matcher(tableName).matches()) {
            throw new IllegalArgumentException("유효하지 않은 테이블명 형식입니다. (schema.table 형식이어야 합니다)");
        }
        // schema.table 분리
        String[] parts = tableName.split("\\.");
        String schema = parts[0];
        String table = parts[1];

        Map<String, Object> params = new HashMap<>();
        params.put("schemaName", schema);
        params.put("tableName", table);

        List<Map<String, Object>> columns = chartDataMapper.selectTableColumns(params);

        if (columns == null || columns.isEmpty()) {
            throw new IllegalArgumentException("테이블을 찾을 수 없거나 컬럼이 없습니다: " + tableName);
        }
        return columns;
    }
    
    /**
     * 레이어 ID로 테이블명 조회
     */
    private String getTableNameByLayerId(String layerId, String layerType) {
        Map<String, Object> layerMeta = null;
        if ("TASK".equals(layerType)) {
            layerMeta = g2fLayerMapper.selectTaskLayerMetadata(layerId);
        } else if ("USER".equals(layerType)) {
            layerMeta = g2fLayerMapper.selectUserLayerMetadata(layerId);
        } else {
            throw new IllegalArgumentException("레이어 타입은 TASK 또는 USER만 가능합니다: " + layerType);
        }

        if (layerMeta == null) {
            throw new IllegalArgumentException("레이어를 찾을 수 없습니다: " + layerId);
        }

        String lyrPhysNm = (String) layerMeta.get("lyr_phys_nm");
        if (lyrPhysNm == null || !lyrPhysNm.contains(".")) {
            throw new IllegalArgumentException("레이어 물리명이 유효하지 않습니다: " + lyrPhysNm);
        }
        return lyrPhysNm;
    }
    
    private String escapeColumnName(String columnName) {
        if (columnName == null || columnName.isEmpty()) {
            return columnName;
        }
        
        if ((columnName.startsWith("\"") && columnName.endsWith("\"")) ||
            (columnName.startsWith("'") && columnName.endsWith("'"))) {
            return columnName;
        }
        
        // 예: "4", "123", "4.5" 등
        if (columnName.matches("^\\d+(\\.\\d+)?$")) {
            return "\"" + columnName + "\"";
        }
        
        return columnName;
    }
}