package incheon.cmm.g2f.download.service.Impl;

import java.util.LinkedHashMap;
import java.util.concurrent.atomic.AtomicReference;
import java.util.Arrays;
import java.util.Set;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.atomic.AtomicReference;
import java.util.function.Consumer;
import java.util.stream.Collectors;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.io.Serializable;
import java.net.URL;
import java.net.URLEncoder;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.StandardOpenOption;
import java.util.ArrayList;
import java.util.Comparator;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.zip.ZipEntry;
import java.util.zip.ZipOutputStream;
import java.nio.charset.StandardCharsets; // for UTF-8
import java.util.Set; // for Set

import org.geotools.api.data.DataStore;
import org.geotools.api.data.FeatureWriter;
import org.geotools.api.data.Transaction;
import org.geotools.api.feature.simple.SimpleFeature;
import org.geotools.api.feature.simple.SimpleFeatureType;
import org.geotools.api.referencing.FactoryException;
import org.geotools.api.referencing.crs.CoordinateReferenceSystem;
import org.geotools.data.DefaultTransaction;
import org.geotools.data.shapefile.ShapefileDataStore;
import org.geotools.data.shapefile.ShapefileDataStoreFactory;
import org.geotools.feature.simple.SimpleFeatureTypeBuilder;
import org.geotools.referencing.CRS;
import org.locationtech.jts.geom.Geometry;
import org.locationtech.jts.geom.MultiLineString;
import org.locationtech.jts.io.ParseException;
import org.locationtech.jts.io.WKTReader;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.core.io.FileSystemResource;
import org.springframework.core.io.Resource;
import org.springframework.http.HttpHeaders;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
import org.springframework.stereotype.Service;

import java.io.BufferedWriter;
import java.io.FileOutputStream;

import org.apache.poi.ss.usermodel.Cell;
import org.apache.poi.ss.usermodel.Row;
import org.apache.poi.xssf.streaming.SXSSFCell;
import org.apache.poi.xssf.streaming.SXSSFRow;
import org.apache.poi.xssf.streaming.SXSSFSheet;
import org.apache.poi.xssf.streaming.SXSSFWorkbook;
import org.apache.poi.xssf.usermodel.XSSFCell;
import org.apache.poi.xssf.usermodel.XSSFRow;
import org.apache.poi.xssf.usermodel.XSSFSheet;
import org.apache.poi.xssf.usermodel.XSSFWorkbook;
import org.apache.poi.ss.usermodel.Sheet;
import org.apache.poi.ss.util.WorkbookUtil;

import incheon.cmm.g2f.download.service.DownloadUtilService;

@Service
public class DownloadUtilServiceImpl implements DownloadUtilService {

	private static final String[] SHAPEFILE_EXTENSIONS = { ".shp", ".shx", ".dbf", ".prj", ".cpg" };

	@Override
	public Path createDownloadTempDirectory() throws IOException {
		return Files.createTempDirectory("download_shapefile_");
	}

	public Path createShapefileFromPostGIS(Path tempDir, String tableName, String layerName, int srid, String spceTy,
			List<Map<String, Object>> shapeData, String geomColumn) throws IOException {

		if (shapeData == null || shapeData.isEmpty()) {
			throw new IOException("데이터가 없습니다");
		}

		Path shapefilePath = tempDir.resolve(layerName + ".shp");
		File shapeFile = shapefilePath.toFile();

		ShapefileDataStore dataStore = null;
		Transaction transaction = new DefaultTransaction("create");
		WKTReader wktReader = new WKTReader();

		try {
// 1. 좌표계 설정
			CoordinateReferenceSystem crs = CRS.decode("EPSG:" + srid);

// 2. 지오메트리 타입 결정
			Class<?> geometryClass = determineGeometryClass(spceTy);
			if (geometryClass == Geometry.class) {
				geometryClass = MultiLineString.class;
			}

// 3. 스키마 빌더
			SimpleFeatureTypeBuilder builder = new SimpleFeatureTypeBuilder();
			builder.setName(layerName);
			builder.setCRS(crs);
			builder.add("the_geom", geometryClass);

// 속성 추가
			Map<String, Object> firstRow = shapeData.get(0);
			for (Map.Entry<String, Object> entry : firstRow.entrySet()) {
				String columnName = entry.getKey();
				if (!"gid".equals(columnName) && !"wkt_geometry".equals(columnName)
						&& !"wkb_geometry".equals(columnName) && !"geometry_srid".equals(columnName)
						&& !geomColumn.equals(columnName)) {

					Class<?> attributeType = determineAttributeType(entry.getValue());
					builder.add(columnName, attributeType);
				}
			}

			SimpleFeatureType featureType = builder.buildFeatureType();

// 4. DataStore 생성
			Map<String, Serializable> params = new HashMap<>();
			params.put("url", shapeFile.toURI().toURL());
			params.put("create spatial index", Boolean.TRUE);

			ShapefileDataStoreFactory factory = new ShapefileDataStoreFactory();
			dataStore = (ShapefileDataStore) factory.createNewDataStore(params);
			dataStore.createSchema(featureType);
			dataStore.setCharset(StandardCharsets.UTF_8);

// 5. 데이터 쓰기
			String typeName = dataStore.getTypeNames()[0];

			try (FeatureWriter<SimpleFeatureType, SimpleFeature> writer = dataStore.getFeatureWriter(typeName,
					transaction)) {

				for (Map<String, Object> rowData : shapeData) {
					String wktGeometry = (String) rowData.get("wkt_geometry");
					if (wktGeometry == null || wktGeometry.trim().isEmpty()) {
						continue;
					}

					try {
						Geometry geometry = wktReader.read(wktGeometry);

						SimpleFeature feature = writer.next();
						feature.setDefaultGeometry(geometry);

						for (Map.Entry<String, Object> entry : rowData.entrySet()) {
							String columnName = entry.getKey();
							if (!"gid".equals(columnName) && !"wkt_geometry".equals(columnName)
									&& !"wkb_geometry".equals(columnName) && !"geometry_srid".equals(columnName)
									&& !geomColumn.equals(columnName)) {

								Object value = entry.getValue();
								try {
									feature.setAttribute(columnName, value != null ? value.toString() : "");
								} catch (Exception e) {
									// 속성 설정 실패해도 계속
								}
							}
						}

						writer.write();

					} catch (Exception e) {
						continue;
					}
				}

				transaction.commit();

			} catch (Exception e) {
				if (transaction != null) {
					transaction.rollback();
				}
				throw new IOException("Feature 쓰기 실패: " + e.getMessage(), e);
			}

		} catch (Exception e) {
			throw new IOException("Shapefile 생성 실패: " + e.getMessage(), e);

		} finally {
			if (transaction != null) {
				try {
					transaction.close();
				} catch (Exception e) {
				}
			}
			if (dataStore != null) {
				dataStore.dispose();
			}
		}

		return createShapefileZip(tempDir, layerName);
	}

	private SimpleFeatureType createFeatureType(String layerName, int srid, String spceTy,
			Map<String, Object> sampleData, String geomColumn) throws IOException {
		try {
			SimpleFeatureTypeBuilder builder = new SimpleFeatureTypeBuilder();
			builder.setName(layerName);

			CoordinateReferenceSystem crs = CRS.decode("EPSG:" + srid);
			builder.setCRS(crs);

			Class<?> geometryClass = determineGeometryClass(spceTy);
			if (geometryClass == Geometry.class) {
				geometryClass = MultiLineString.class;
			}

			builder.add("the_geom", geometryClass);

			for (Map.Entry<String, Object> entry : sampleData.entrySet()) {
				String columnName = entry.getKey();
				if (!"gid".equals(columnName) && !"wkt_geometry".equals(columnName)
						&& !"wkb_geometry".equals(columnName) && !"geometry_srid".equals(columnName)
						&& !geomColumn.equals(columnName)) {

					Class<?> attributeType = determineAttributeType(entry.getValue());
					builder.add(columnName, attributeType);
				}
			}

			return builder.buildFeatureType();
		} catch (FactoryException e) {
			throw new IOException("좌표계 생성 실패: " + e.getMessage(), e);
		}
	}

	private Path createShapefileWithGeoTools(Path tempDir, String layerName, SimpleFeatureType featureType,
			List<Map<String, Object>> shapeData, String geomColumn) throws IOException, ParseException {
		Path shapefilePath = tempDir.resolve(layerName + ".shp");
		File shapeFile = shapefilePath.toFile();

		ShapefileDataStore dataStore = null;
		Transaction transaction = null;
		WKTReader wktReader = new WKTReader();

		try {
			Map<String, Serializable> params = new HashMap<>();
			params.put("url", shapeFile.toURI().toURL());
			params.put("create spatial index", Boolean.TRUE);

			dataStore = (ShapefileDataStore) new ShapefileDataStoreFactory().createNewDataStore(params);
			dataStore.createSchema(featureType);
			dataStore.setCharset(StandardCharsets.UTF_8);

			transaction = new DefaultTransaction("create");

			try (FeatureWriter<SimpleFeatureType, SimpleFeature> writer = dataStore
					.getFeatureWriterAppend(transaction)) {

				int processedCount = 0;
				for (Map<String, Object> rowData : shapeData) {
					SimpleFeature feature = writer.next();

					// WKT 사용 (여전히 필요 - GeoTools가 WKT를 받음)
					String wktGeometry = (String) rowData.get("wkt_geometry");
					if (wktGeometry != null && !wktGeometry.trim().isEmpty()) {
						Geometry geometry = wktReader.read(wktGeometry);
						feature.setDefaultGeometry(geometry);
					} else {
						continue;
					}

					// 속성 설정
					for (Map.Entry<String, Object> entry : rowData.entrySet()) {
						String columnName = entry.getKey();
						if (!"gid".equals(columnName) && !"wkt_geometry".equals(columnName)
								&& !"wkb_geometry".equals(columnName) && !"geometry_srid".equals(columnName)
								&& !geomColumn.equals(columnName)) {
							Object value = entry.getValue();
							if (value != null) {
								feature.setAttribute(columnName, value.toString());
							}
						}
					}

					writer.write();
					processedCount++;
				}
				transaction.commit();

			} catch (IOException | ParseException e) {
				transaction.rollback();
				throw e;
			}
		} finally {
			if (transaction != null) {
				transaction.close();
			}
			if (dataStore != null) {
				dataStore.dispose();
			}
		}

		return shapefilePath;
	}

	private Path createShapefileWithGeoTools(Path tempDir, String layerName, SimpleFeatureType featureType,
			List<Map<String, Object>> shapeData) throws Exception {
		Path shapefilePath = tempDir.resolve(layerName + ".shp");
		File shapeFile = shapefilePath.toFile();

		ShapefileDataStore dataStore = null;
		Transaction transaction = null;
		WKTReader wktReader = new WKTReader();

		try {
			// ShapefileDataStore 생성
			Map<String, Serializable> params = new HashMap<>();
			params.put("url", shapeFile.toURI().toURL());
			params.put("create spatial index", Boolean.TRUE);

			dataStore = (ShapefileDataStore) new ShapefileDataStoreFactory().createNewDataStore(params);
			dataStore.createSchema(featureType);
			dataStore.setCharset(java.nio.charset.StandardCharsets.UTF_8);

			// Feature 데이터 쓰기
			transaction = new DefaultTransaction("create");

			try (FeatureWriter<SimpleFeatureType, SimpleFeature> writer = dataStore
					.getFeatureWriterAppend(transaction)) {

				int processedCount = 0;
				for (Map<String, Object> rowData : shapeData) {
					SimpleFeature feature = writer.next();

					// 지오메트리 설정
					String wktGeometry = (String) rowData.get("wkt_geometry");
					if (wktGeometry != null && !wktGeometry.trim().isEmpty()) {
						Geometry geometry = wktReader.read(wktGeometry);
						feature.setDefaultGeometry(geometry);
					} else {
						continue;
					}

					// 속성 설정
					for (Map.Entry<String, Object> entry : rowData.entrySet()) {
						String columnName = entry.getKey();
						if (!"gid".equals(columnName) && !"wkt_geometry".equals(columnName)
								&& !"geometry_srid".equals(columnName) && !"geom".equals(columnName)) {

							Object value = entry.getValue();
							if (value != null) {
								feature.setAttribute(columnName, value.toString());
							}
						}
					}

					writer.write();
					processedCount++;
				}

				transaction.commit();

			} catch (RuntimeException e) {
				if (transaction != null) {
					transaction.rollback();
				}
				throw e;
			}
		} finally {
			if (transaction != null) {
				transaction.close();
			}
			if (dataStore != null) {
				dataStore.dispose();
			}
		}
		return shapefilePath;
	}

	private Path createShapefileZip(Path tempDir, String layerName) throws IOException {
		Path zipFile = tempDir.resolve(layerName + ".zip");

		// 압축할 파일들 확인
		List<Path> filesToZip = new ArrayList<>();
		for (String extension : SHAPEFILE_EXTENSIONS) {
			Path componentFile = tempDir.resolve(layerName + extension);
			if (Files.exists(componentFile)) {
				filesToZip.add(componentFile);
			}
		}

		if (filesToZip.isEmpty()) {
			throw new IOException("압축할 Shapefile 컴포넌트를 찾을 수 없습니다.");
		}

		try (ZipOutputStream zos = new ZipOutputStream(Files.newOutputStream(zipFile, StandardOpenOption.CREATE))) {

			for (Path componentFile : filesToZip) {
				String entryName = componentFile.getFileName().toString();
				ZipEntry entry = new ZipEntry(entryName);

				zos.putNextEntry(entry);

				// 파일 내용을 ZIP에 복사
				try (InputStream fis = Files.newInputStream(componentFile)) {
					byte[] buffer = new byte[8192];
					int bytesRead;
					long totalBytes = 0;
					while ((bytesRead = fis.read(buffer)) != -1) {
						zos.write(buffer, 0, bytesRead);
						totalBytes += bytesRead;
					}
				}

				zos.closeEntry();
			}

			zos.flush();
		}
		// ZIP 파일 크기 확인
		long zipSize = Files.size(zipFile);
		if (zipSize < 100) {
			throw new IOException("ZIP 파일이 너무 작습니다. 압축에 실패했을 가능성이 있습니다.");
		}

		return zipFile;
	}

	public ResponseEntity<Resource> createFileDownloadResponse(Path filePath, String downloadFileName)
			throws IOException {

		Resource resource = new FileSystemResource(filePath.toFile());

		// URL-encoded UTF-8 파일명
		String encoded = URLEncoder.encode(downloadFileName, StandardCharsets.UTF_8).replaceAll("\\+", "%20");

		// RFC 5987 only (filename= 제거)
		String contentDisposition = "attachment; filename*=UTF-8''" + encoded;

		MediaType contentType;
		if (downloadFileName.toLowerCase().endsWith(".xlsx")) {
			contentType = MediaType.parseMediaType("application/vnd.openxmlformats-officedocument.spreadsheetml.sheet");
		} else {
			contentType = MediaType.APPLICATION_OCTET_STREAM;
		}

		return ResponseEntity.ok().header(HttpHeaders.CONTENT_DISPOSITION, contentDisposition)
				.header("filename", encoded).contentLength(resource.contentLength()).contentType(contentType)
				.body(resource);
	}

	@Override
	public String sanitizeFileName(String name) {
		if (name == null)
			return "download";

		return name.replaceAll("[\\\\/:*?\"<>|]", "_").replaceAll("\\s+", " ").trim();
	}

	@Override
	public void cleanupDownloadFiles(Path tempDir) throws IOException {
		Files.walk(tempDir).sorted(Comparator.reverseOrder()).map(Path::toFile).forEach(file -> {
		});
	}

	public void validateTableName(String tableName) {
		if (tableName == null || tableName.trim().isEmpty()) {
			throw new IllegalArgumentException("테이블명이 비어있습니다.");
		}

		// 공백 제거
		tableName = tableName.trim();

		// PostgreSQL 테이블명 규칙 검증
		// - 첫 글자: 영문자 또는 언더스코어
		// - 나머지: 영문자, 숫자, 언더스코어만 허용
		if (!tableName.matches("^[a-zA-Z0-9_\\.]+$")) {
			throw new IllegalArgumentException("유효하지 않은 테이블명 형식: " + tableName);
		}

		// 길이 제한 (PostgreSQL 식별자 최대 길이: 63자)
		if (tableName.length() > 63) {
			throw new IllegalArgumentException("테이블명이 너무 깁니다 (최대 63자): " + tableName);
		}

		// 최소 길이 체크
		if (tableName.length() < 1) {
			throw new IllegalArgumentException("테이블명이 너무 짧습니다.");
		}

		// SQL 예약어 방지
		String upperTableName = tableName.toUpperCase();
		String[] sqlKeywords = { "SELECT", "DROP", "DELETE", "UPDATE", "INSERT", "CREATE", "ALTER", "UNION", "WHERE",
				"FROM", "INTO", "VALUES", "SET", "TABLE", "INDEX", "DATABASE", "SCHEMA", "USER", "GRANT", "REVOKE" };

		for (String keyword : sqlKeywords) {
			if (upperTableName.equals(keyword)) {
				throw new IllegalArgumentException("SQL 예약어는 테이블명으로 사용할 수 없습니다: " + tableName);
			}
		}

		// 추가 보안 체크: 위험한 문자열 패턴 방지
		String[] dangerousPatterns = { "--", "/*", "*/", ";", "xp_", "sp_", "\\", "'", "\"" };

		for (String pattern : dangerousPatterns) {
			if (tableName.toLowerCase().contains(pattern)) {
				throw new IllegalArgumentException("위험한 문자열이 포함된 테이블명: " + tableName);
			}
		}
	}

	// CSV 파일 생성
	@Override
	public Path createCsvFile(Path tempDir, String layerName, List<Map<String, Object>> data, String columns)
			throws IOException {
		if (data == null || data.isEmpty()) {
			throw new RuntimeException("CSV로 변환할 데이터가 없습니다.");
		}

		Path csvFile = tempDir.resolve(layerName + ".csv");

		try (BufferedWriter writer = Files.newBufferedWriter(csvFile, StandardCharsets.UTF_8)) {
			// 1. UTF-8 BOM 추가 (엑셀에서 한글 깨짐 방지)
			writer.write('\uFEFF');

			List<String> selectedColumns;
			if (columns != null && !columns.trim().isEmpty()) {
				// 파라미터로 전달된 컬럼들 파싱
				selectedColumns = Arrays.asList(columns.split(","));
				// 공백 제거
				selectedColumns = selectedColumns.stream().map(String::trim).collect(Collectors.toList());

				// 존재하지 않는 컬럼 체크
				Set<String> availableColumns = data.get(0).keySet();
				List<String> invalidColumns = selectedColumns.stream().filter(col -> !availableColumns.contains(col))
						.collect(Collectors.toList());

				if (!invalidColumns.isEmpty()) {
					throw new RuntimeException("존재하지 않는 컬럼: " + String.join(", ", invalidColumns));
				}
			} else {
				// columns 파라미터가 없으면 모든 컬럼 사용
				selectedColumns = new ArrayList<>(data.get(0).keySet());
			}

			// 3. 헤더 작성
			writer.write(String.join(",", selectedColumns));
			writer.newLine();

			// 4. 데이터 작성
			for (Map<String, Object> row : data) {
				List<String> values = selectedColumns.stream().map(col -> {
					Object val = row.get(col);
					if (val == null)
						return "";
					// 콤마, 쌍따옴표 처리
					String str = val.toString().replace("\"", "\"\"");
					if (str.contains(",") || str.contains("\"") || str.contains("\n")) {
						str = "\"" + str + "\"";
					}
					return str;
				}).toList();
				writer.write(String.join(",", values));
				writer.newLine();
			}
		}

		return csvFile;
	}

	@Override
	public Path createExcelFile(Path tempDir, String layerName, List<Map<String, Object>> data, String columns)
			throws IOException {

		if (data == null || data.isEmpty()) {
			throw new IllegalArgumentException("Empty data");
		}
		return writeExcelChunk(tempDir, layerName, data, columns);
	}

	private Path writeExcelChunk(Path tempDir, String layerName, List<Map<String, Object>> data, String columns)
			throws IOException {

		Path excelFile = tempDir.resolve(layerName + ".xlsx");

		try (SXSSFWorkbook workbook = new SXSSFWorkbook(200)) {

			String safeSheetName = WorkbookUtil.createSafeSheetName(layerName);

			SXSSFSheet sheet = workbook.createSheet(safeSheetName);

			List<String> selectedColumns;
			if (columns != null && !columns.trim().isEmpty()) {
				selectedColumns = Arrays.stream(columns.split(",")).map(String::trim).collect(Collectors.toList());

				Set<String> available = data.get(0).keySet();
				List<String> invalid = selectedColumns.stream().filter(c -> !available.contains(c))
						.collect(Collectors.toList());

				if (!invalid.isEmpty()) {
					throw new IllegalArgumentException("Invalid columns: " + String.join(", ", invalid));
				}
			} else {
				selectedColumns = new ArrayList<>(data.get(0).keySet());
			}

			SXSSFRow headerRow = sheet.createRow(0);
			for (int i = 0; i < selectedColumns.size(); i++) {
				headerRow.createCell(i).setCellValue(selectedColumns.get(i));
			}

			for (int r = 0; r < data.size(); r++) {
				SXSSFRow row = sheet.createRow(r + 1);
				Map<String, Object> rowData = data.get(r);

				for (int c = 0; c < selectedColumns.size(); c++) {
					SXSSFCell cell = row.createCell(c);
					Object value = rowData.get(selectedColumns.get(c));

					if (value instanceof Number) {
						cell.setCellValue(((Number) value).doubleValue());
					} else if (value instanceof Boolean) {
						cell.setCellValue((Boolean) value);
					} else {
						cell.setCellValue(value != null ? value.toString() : "");
					}
				}
			}

			for (int i = 0; i < selectedColumns.size(); i++) {
				sheet.setColumnWidth(i, 6000);
			}

			try (OutputStream os = Files.newOutputStream(excelFile)) {
				workbook.write(os);
			}
		}

		return excelFile;
	}

	@Override
	public Path createDxfFile(Path tempDir, String layerName,
	                          Consumer<Consumer<Map<String, Object>>> dataProvider,
	                          String columns) throws IOException {

	    Path dxfFile = tempDir.resolve(layerName + ".dxf");

	    try (BufferedWriter writer = Files.newBufferedWriter(dxfFile, StandardCharsets.UTF_8)) {

	        // DXF 헤더
	        writer.write("0\nSECTION\n2\nHEADER\n");
	        writer.write("9\n$ACADVER\n1\nAC1015\n"); // AutoCAD 2000 버전
	        writer.write("0\nENDSEC\n");
	        
	        writer.write("0\nSECTION\n2\nTABLES\n");
	        writer.write("0\nTABLE\n2\nLAYER\n70\n1\n");
	        writer.write("0\nLAYER\n2\n0\n70\n0\n62\n7\n6\nCONTINUOUS\n");
	        writer.write("0\nENDTAB\n");
	        writer.write("0\nENDSEC\n");
	        
	        writer.write("0\nSECTION\n2\nBLOCKS\n0\nENDSEC\n");
	        writer.write("0\nSECTION\n2\nENTITIES\n");

	        // 버퍼링으로 성능 향상
	        final int BUFFER_SIZE = 1000;
	        final StringBuilder buffer = new StringBuilder(100000); // 100KB 버퍼
	        final AtomicInteger count = new AtomicInteger(0);

	        dataProvider.accept(row -> {
	            try {
	                String wkt = (String) row.get("wkt_geometry");
	                if (wkt == null || wkt.isEmpty()) {
	                    return;
	                }

	                String dxfEntity = convertWktToDxfEntity(wkt, row, columns);
	                buffer.append(dxfEntity);
	                
	                // 버퍼가 일정 크기 이상이면 파일에 쓰기
	                if (count.incrementAndGet() % BUFFER_SIZE == 0) {
	                    writer.write(buffer.toString());
	                    buffer.setLength(0); // 버퍼 초기화
	                }
	                
	            } catch (IOException e) {
	                throw new RuntimeException(e);
	            }
	        });

	        // 남은 버퍼 쓰기
	        if (buffer.length() > 0) {
	            writer.write(buffer.toString());
	        }

	        // DXF 푸터
	        writer.write("0\nENDSEC\n0\nEOF\n");
	        writer.flush();
	    }

	    return dxfFile;
	}

	private String convertWktToDxfEntity(String wkt, Map<String, Object> attributes, String columns) {
	    if (wkt == null || wkt.isEmpty()) {
	        return "";
	    }

	    wkt = wkt.trim().toUpperCase();
	    StringBuilder entityBuilder = new StringBuilder(512); // 초기 용량 설정

	    double[] centroid = null;

	    try {
	        if (wkt.startsWith("POINT")) {
	            String coords = wkt.substring(wkt.indexOf('(') + 1, wkt.indexOf(')')).trim();
	            String[] xy = coords.split(" ");
	            if (xy.length >= 2) {
	                entityBuilder.append("0\nPOINT\n8\n0\n10\n")
	                    .append(xy[0]).append("\n20\n").append(xy[1]).append("\n");
	                centroid = new double[]{Double.parseDouble(xy[0]), Double.parseDouble(xy[1])};
	            }
	            
	        } else if (wkt.startsWith("LINESTRING")) {
	            String coords = wkt.substring(wkt.indexOf('(') + 1, wkt.lastIndexOf(')')).trim();
	            String[] points = coords.split(",");
	            entityBuilder.append("0\nPOLYLINE\n8\n0\n66\n1\n70\n0\n");
	            
	            for (String point : points) {
	                String[] xy = point.trim().split(" ");
	                if (xy.length >= 2) {
	                    entityBuilder.append("0\nVERTEX\n10\n")
	                        .append(xy[0]).append("\n20\n").append(xy[1]).append("\n");
	                }
	            }
	            entityBuilder.append("0\nSEQEND\n");
	            centroid = calculateCentroid(wkt);
	            
	        } else if (wkt.startsWith("POLYGON")) {
	            String coords = wkt.substring(wkt.indexOf("((") + 2, wkt.lastIndexOf(")")).trim();
	            String[] points = coords.split(",");
	            entityBuilder.append("0\nPOLYLINE\n8\n0\n66\n1\n70\n1\n");
	            
	            for (String point : points) {
	                String[] xy = point.trim().split(" ");
	                if (xy.length >= 2) {
	                    entityBuilder.append("0\nVERTEX\n10\n")
	                        .append(xy[0]).append("\n20\n").append(xy[1]).append("\n");
	                }
	            }
	            
	            // 폴리곤 닫기 (첫 점과 마지막 점 연결)
	            if (points.length > 0) {
	                String[] firstPoint = points[0].trim().split(" ");
	                if (firstPoint.length >= 2) {
	                    entityBuilder.append("0\nVERTEX\n10\n")
	                        .append(firstPoint[0]).append("\n20\n").append(firstPoint[1]).append("\n");
	                }
	            }
	            entityBuilder.append("0\nSEQEND\n");
	            centroid = calculateCentroid(wkt);
	            
	        } else if (wkt.startsWith("MULTIPOINT")) {
	            String coords = wkt.substring(wkt.indexOf('(') + 1, wkt.lastIndexOf(')')).trim();
	            String[] points = coords.split(",");
	            
	            for (String point : points) {
	                point = point.replace("(", "").replace(")", "").trim();
	                String[] xy = point.split(" ");
	                if (xy.length >= 2) {
	                    entityBuilder.append("0\nPOINT\n8\n0\n10\n")
	                        .append(xy[0]).append("\n20\n").append(xy[1]).append("\n");
	                }
	            }
	            centroid = calculateCentroid(wkt);
	            
	        } else if (wkt.startsWith("MULTILINESTRING")) {
	            String coords = wkt.substring(wkt.indexOf('(') + 1, wkt.lastIndexOf(')')).trim();
	            String[] lines = coords.split("\\)\\s*,\\s*\\(");
	            
	            for (String line : lines) {
	                line = line.replace("(", "").replace(")", "").trim();
	                String[] points = line.split(",");
	                entityBuilder.append("0\nPOLYLINE\n8\n0\n66\n1\n70\n0\n");
	                
	                for (String point : points) {
	                    String[] xy = point.trim().split(" ");
	                    if (xy.length >= 2) {
	                        entityBuilder.append("0\nVERTEX\n10\n")
	                            .append(xy[0]).append("\n20\n").append(xy[1]).append("\n");
	                    }
	                }
	                entityBuilder.append("0\nSEQEND\n");
	            }
	            centroid = calculateCentroid(wkt);
	            
	        } else if (wkt.startsWith("MULTIPOLYGON")) {
	            String coords = wkt.substring(wkt.indexOf("(((") + 3, wkt.lastIndexOf(")))")).trim();
	            String[] polygons = coords.split("\\)\\)\\s*,\\s*\\(\\(");
	            
	            for (String poly : polygons) {
	                String[] points = poly.replace("(", "").replace(")", "").split(",");
	                entityBuilder.append("0\nPOLYLINE\n8\n0\n66\n1\n70\n1\n");
	                
	                for (String point : points) {
	                    String[] xy = point.trim().split(" ");
	                    if (xy.length >= 2) {
	                        entityBuilder.append("0\nVERTEX\n10\n")
	                            .append(xy[0]).append("\n20\n").append(xy[1]).append("\n");
	                    }
	                }
	                
	                if (points.length > 0) {
	                    String[] firstPoint = points[0].trim().split(" ");
	                    if (firstPoint.length >= 2) {
	                        entityBuilder.append("0\nVERTEX\n10\n")
	                            .append(firstPoint[0]).append("\n20\n").append(firstPoint[1]).append("\n");
	                    }
	                }
	                entityBuilder.append("0\nSEQEND\n");
	            }
	            centroid = calculateCentroid(wkt);
	            
	        } else {
	            return "";
	        }

	        // 속성 텍스트 추가 (선택적)
	        if (centroid != null && centroid.length == 2 && columns != null && !columns.trim().isEmpty()) {
	            entityBuilder.append(createAttributeTexts(centroid, attributes, columns));
	        }

	    } catch (Exception e) {
	        // 파싱 오류 시 해당 엔티티 건너뛰기
	        return "";
	    }

	    return entityBuilder.toString();
	}

	/**
	 * WKT 지오메트리의 중심점 계산 (간단한 bbox 중심)
	 */
	private double[] calculateCentroid(String wkt) {
	    try {
	        String coords = wkt.replaceAll("[A-Z]+\\(+", "").replaceAll("\\)+", "");
	        String[] points = coords.split(",");

	        double minX = Double.MAX_VALUE, minY = Double.MAX_VALUE;
	        double maxX = Double.MIN_VALUE, maxY = Double.MIN_VALUE;

	        for (String point : points) {
	            String[] xy = point.trim().split("\\s+");
	            if (xy.length >= 2) {
	                try {
	                    double x = Double.parseDouble(xy[0]);
	                    double y = Double.parseDouble(xy[1]);
	                    minX = Math.min(minX, x);
	                    minY = Math.min(minY, y);
	                    maxX = Math.max(maxX, x);
	                    maxY = Math.max(maxY, y);
	                } catch (NumberFormatException e) {
	                    continue;
	                }
	            }
	        }

	        if (minX != Double.MAX_VALUE && maxX != Double.MIN_VALUE) {
	            return new double[]{(minX + maxX) / 2, (minY + maxY) / 2};
	        }
	    } catch (Exception e) {
	        // 오류 시 null 반환
	    }
	    return null;
	}

	private String createAttributeTexts(double[] centroid, Map<String, Object> attributes, String columns) {
	    if (attributes == null || attributes.isEmpty() || centroid == null) {
	        return "";
	    }

	    StringBuilder texts = new StringBuilder(256);
	    
	    // columns 파라미터 파싱
	    List<String> selectedColumns;
	    if (columns != null && !columns.trim().isEmpty()) {
	        selectedColumns = Arrays.asList(columns.split(","));
	        selectedColumns = selectedColumns.stream()
	            .map(String::trim)
	            .limit(5) // 최대 5개 컬럼만 텍스트로 표시 (성능 고려)
	            .collect(Collectors.toList());
	    } else {
	        return ""; // columns 지정 안하면 텍스트 생략 (대용량 성능)
	    }

	    double yOffset = 0;

	    for (String columnName : selectedColumns) {
	        if ("wkt_geometry".equals(columnName) || "geometry_srid".equals(columnName) 
	            || "geom".equals(columnName) || "gid".equals(columnName)) {
	            continue;
	        }

	        if (!attributes.containsKey(columnName)) {
	            continue;
	        }

	        Object value = attributes.get(columnName);
	        String displayText = value != null ? value.toString() : "";
	        
	        // 텍스트 길이 제한 (50자)
	        if (displayText.length() > 50) {
	            displayText = displayText.substring(0, 50) + "...";
	        }

	        texts.append("0\nTEXT\n8\n0\n10\n")
	            .append(centroid[0]).append("\n20\n")
	            .append(centroid[1] + yOffset).append("\n40\n5.0\n1\n")
	            .append(sanitizeDxfString(displayText)).append("\n");

	        yOffset -= 10;
	    }

	    return texts.toString();
	}

	/**
	 * DXF 문자열에서 문제가 될 수 있는 문자 제거
	 */
	private String sanitizeDxfString(String str) {
		if (str == null)
			return "";
		// DXF에서 문제될 수 있는 특수문자 제거
		return str.replace("\n", " ").replace("\r", " ").replace("\t", " ").trim();
	}

	private Class<?> determineGeometryClass(String spceTy) {
	    if (spceTy == null) {
	        return Geometry.class;
	    }

	    switch (spceTy.toUpperCase()) {
	        case "POINT":
	            return org.locationtech.jts.geom.Point.class;
	        case "LINESTRING":
	            return org.locationtech.jts.geom.LineString.class;
	        case "POLYGON":
	            return org.locationtech.jts.geom.Polygon.class;
	        case "MULTIPOINT":
	            return org.locationtech.jts.geom.MultiPoint.class;
	        case "MULTILINESTRING":
	            return org.locationtech.jts.geom.MultiLineString.class;
	        case "MULTIPOLYGON":
	            return org.locationtech.jts.geom.MultiPolygon.class;
	        default:
	            return Geometry.class;
	    }
	}

	private Class<?> determineAttributeType(Object value) {
	    if (value == null) {
	        return String.class;
	    }

	    if (value instanceof Integer) {
	        return Integer.class;
	    }
	    if (value instanceof Long) {
	        return Long.class;
	    }
	    if (value instanceof Double) {
	        return Double.class;
	    }
	    if (value instanceof Float) {
	        return Float.class;
	    }
	    if (value instanceof Boolean) {
	        return Boolean.class;
	    }

	    return String.class;
	}

	/**
	 * WKT를 GeoJSON geometry로 변환
	 */
	private String convertWktToGeoJsonGeometry(String wkt) {
		if (wkt == null || wkt.isEmpty())
			return "null";

		wkt = wkt.trim();
		String wktUpper = wkt.toUpperCase();

		if (wktUpper.startsWith("POINT")) {
			// POINT(x y)
			String coords = wkt.substring(wkt.indexOf('(') + 1, wkt.indexOf(')')).trim();
			String[] xy = coords.split("\\s+");
			return String.format("{\"type\":\"Point\",\"coordinates\":[%s,%s]}", xy[0], xy[1]);
		} else if (wktUpper.startsWith("LINESTRING")) {
			// LINESTRING(x1 y1, x2 y2, ...)
			String coords = wkt.substring(wkt.indexOf('(') + 1, wkt.lastIndexOf(')')).trim();
			String[] points = coords.split(",");
			StringBuilder sb = new StringBuilder("{\"type\":\"LineString\",\"coordinates\":[");
			for (int i = 0; i < points.length; i++) {
				String[] xy = points[i].trim().split("\\s+");
				sb.append("[").append(xy[0]).append(",").append(xy[1]).append("]");
				if (i < points.length - 1)
					sb.append(",");
			}
			sb.append("]}");
			return sb.toString();
		} else if (wktUpper.startsWith("POLYGON")) {
			// POLYGON((x1 y1, x2 y2, ..., x1 y1))
			String coords = wkt.substring(wkt.indexOf("((") + 2, wkt.lastIndexOf("))")).trim();
			String[] rings = coords.split("\\),\\s*\\(");
			StringBuilder sb = new StringBuilder("{\"type\":\"Polygon\",\"coordinates\":[");

			for (int r = 0; r < rings.length; r++) {
				String ring = rings[r].replace("(", "").replace(")", "").trim();
				String[] points = ring.split(",");
				sb.append("[");
				for (int i = 0; i < points.length; i++) {
					String[] xy = points[i].trim().split("\\s+");
					sb.append("[").append(xy[0]).append(",").append(xy[1]).append("]");
					if (i < points.length - 1)
						sb.append(",");
				}
				sb.append("]");
				if (r < rings.length - 1)
					sb.append(",");
			}
			sb.append("]}");
			return sb.toString();
		} else if (wktUpper.startsWith("MULTIPOINT")) {
			// MULTIPOINT((x1 y1), (x2 y2), ...)
			String coords = wkt.substring(wkt.indexOf('(') + 1, wkt.lastIndexOf(')')).trim();
			coords = coords.replace("(", "").replace(")", "");
			String[] points = coords.split(",");
			StringBuilder sb = new StringBuilder("{\"type\":\"MultiPoint\",\"coordinates\":[");
			for (int i = 0; i < points.length; i++) {
				String[] xy = points[i].trim().split("\\s+");
				sb.append("[").append(xy[0]).append(",").append(xy[1]).append("]");
				if (i < points.length - 1)
					sb.append(",");
			}
			sb.append("]}");
			return sb.toString();
		} else if (wktUpper.startsWith("MULTILINESTRING")) {
			// MULTILINESTRING((x1 y1, x2 y2), (x3 y3, x4 y4))
			String coords = wkt.substring(wkt.indexOf('(') + 1, wkt.lastIndexOf(')')).trim();
			String[] lines = coords.split("\\),\\s*\\(");
			StringBuilder sb = new StringBuilder("{\"type\":\"MultiLineString\",\"coordinates\":[");

			for (int l = 0; l < lines.length; l++) {
				String line = lines[l].replace("(", "").replace(")", "").trim();
				String[] points = line.split(",");
				sb.append("[");
				for (int i = 0; i < points.length; i++) {
					String[] xy = points[i].trim().split("\\s+");
					sb.append("[").append(xy[0]).append(",").append(xy[1]).append("]");
					if (i < points.length - 1)
						sb.append(",");
				}
				sb.append("]");
				if (l < lines.length - 1)
					sb.append(",");
			}
			sb.append("]}");
			return sb.toString();
		} else if (wktUpper.startsWith("MULTIPOLYGON")) {
			// MULTIPOLYGON(((x1 y1, x2 y2, x1 y1)), ((x3 y3, x4 y4, x3 y3)))
			String coords = wkt.substring(wkt.indexOf("(((") + 3, wkt.lastIndexOf(")))")).trim();
			String[] polygons = coords.split("\\)\\),\\s*\\(\\(");
			StringBuilder sb = new StringBuilder("{\"type\":\"MultiPolygon\",\"coordinates\":[");

			for (int p = 0; p < polygons.length; p++) {
				sb.append("[[");
				String[] points = polygons[p].replace("(", "").replace(")", "").split(",");
				for (int i = 0; i < points.length; i++) {
					String[] xy = points[i].trim().split("\\s+");
					sb.append("[").append(xy[0]).append(",").append(xy[1]).append("]");
					if (i < points.length - 1)
						sb.append(",");
				}
				sb.append("]]");
				if (p < polygons.length - 1)
					sb.append(",");
			}
			sb.append("]}");
			return sb.toString();
		}
		return "null";
	}

	/**
	 * JSON 문자열 이스케이프
	 */
	private String escapeJson(String str) {
		if (str == null)
			return "";
		return str.replace("\\", "\\\\").replace("\"", "\\\"").replace("\n", "\\n").replace("\r", "\\r").replace("\t",
				"\\t");
	}

	// 속성 컬럼인지 체크하는 헬퍼 메서드
	private boolean isAttributeColumn(String col, String geomColumn) {
		List<String> skipCols = Arrays.asList("gid", "wkt_geometry", "wkb_geometry", "geometry_srid", geomColumn);
		return !skipCols.contains(col);
	}
}