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

import java.io.BufferedReader;
import java.io.File;
import java.io.FileReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.UUID;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.TimeUnit;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import java.util.stream.Collectors;

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

import com.fasterxml.jackson.core.JsonToken;
import com.fasterxml.jackson.core.type.TypeReference;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;

import incheon.ags.mrb.upload.mapper.UserLayerMapper;
import incheon.ags.mrb.upload.service.GdalConversionService;

import com.fasterxml.jackson.core.JsonParser;

@Service
public class GdalConversionServiceImpl implements GdalConversionService {

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

	private List<String> detectedColumns = new ArrayList<>();
    private static final String TARGET_EPSG = "EPSG:3857";
	private UserLayerMapper userLayer = null;
    private String layerName = null;
    private String layerType = null;
    @Autowired private ObjectMapper objectMapper;

    @Override
    public String convertToGeoJson(String sourceFilePath, 
                                   String originalFileName, 
                                   String coordinate, 
                                   String encoding, 
                                   List<String> shapeColumns, 
                                   String FirstColumn, 
                                   String SecondColumn,
                                   UserLayerMapper userLayerMapper) throws IOException, InterruptedException, Exception {

        // 1. GeoJSON 출력 경로
        String tempFileName = "output" + UUID.randomUUID().toString() + ".geojson";
        Path tempOutputFilePath = Paths.get(System.getProperty("java.io.tmpdir"), tempFileName);
        String outputFilePathStr = tempOutputFilePath.toAbsolutePath().toString();

        ProcessBuilder processBuilder = null;
        if (originalFileName == null || !originalFileName.contains(".")) {
            throw new IllegalArgumentException("원본 파일명이 없거나 확장자가 없습니다.");
        }
        String fileExtension = originalFileName.substring(originalFileName.lastIndexOf('.')).toLowerCase();

        // 2. GDAL 커맨드 준비
        if (".csv".equals(fileExtension) || ".xlsx".equals(fileExtension)) {
            detectedColumns = getFileColumns(sourceFilePath, false);
            processBuilder = csvCommand(sourceFilePath, outputFilePathStr, coordinate, FirstColumn, SecondColumn, encoding);
        } else if (".zip".equals(fileExtension)) {
            String sqlQuery = null;
            if (shapeColumns != null && !shapeColumns.isEmpty()) {
                detectedColumns = getFileColumns(sourceFilePath, true);
                sqlQuery = buildRenameSQL(detectedColumns, shapeColumns, layerName);
            }
            processBuilder = zipCommand(sourceFilePath, outputFilePathStr, coordinate, sqlQuery);
        } else if(".dxf".equals(fileExtension)) {
            detectedColumns = getFileColumns(sourceFilePath, false);
            processBuilder = dxfCommand(sourceFilePath, outputFilePathStr, coordinate);
        } else {
            throw new IllegalArgumentException("지원하지 않는 파일 확장자입니다 : " + fileExtension);
        }

        // 3. 테이블 생성
        if (userLayerMapper != null && detectedColumns != null && !detectedColumns.isEmpty()) {
            String columnsDefinition = detectedColumns.stream()
                    .map(col -> "\"" + col + "\" TEXT")
                    .collect(Collectors.joining(", "));
            userLayerMapper.createLayerTable(layerName, columnsDefinition);
            logger.debug("테이블 생성 완료: " + layerName);
        }

        // 4. GDAL 실행
        Process process = processBuilder.start();
        try (BufferedReader errorReader = new BufferedReader(new InputStreamReader(process.getErrorStream()))) {
            String errorLog = errorReader.lines().collect(Collectors.joining("\n"));
            if (!errorLog.trim().isEmpty()) {
                System.err.println("GDAL 오류 로그:\n" + errorLog);
            }
        }

        int exitCode = process.waitFor();
        if (exitCode != 0) {
            throw new RuntimeException("GeoJSON 변환 실패. Exit code: " + exitCode);
        }

        return outputFilePathStr;
    }

    
    private ProcessBuilder makeCommand(String inputfile, String outputfile, String coordinate) {
        List<String> command = new ArrayList<>();
        command.add("ogr2ogr");
        command.add("-f");
        command.add("GeoJSON");
        command.add("-s_srs");
        command.add(coordinate);
        command.add("-t_srs");
        command.add(TARGET_EPSG);
        command.add("-preserve_fid");
        command.add(outputfile);
        command.add(inputfile);

        return new ProcessBuilder(command);
    }

    private ProcessBuilder makeCommandCSV(String inputfile, String outputfile, String coordinate, String xColumnName, String yColumnName, String encoding) {
        List<String> command = new ArrayList<>();
        command.add("ogr2ogr");
        command.add("-f");
        command.add("GeoJSON");
        command.add("-s_srs");
        command.add(coordinate);
        command.add("-t_srs");
        command.add(TARGET_EPSG);
        command.add("-preserve_fid");
        command.add("-oo");
        command.add("X_POSSIBLE_NAMES=" + xColumnName);
        command.add("-oo");
        command.add("Y_POSSIBLE_NAMES=" + yColumnName);
        command.add("-oo"); 
        command.add("ENCODING=" + encoding);
        command.add(outputfile);
        command.add(inputfile);

        return new ProcessBuilder(command);
    }
    
    private ProcessBuilder makeCommandShp(String inputfile, String outputfile, String coordinate, String sqlQuery) {
        ProcessBuilder pb = new ProcessBuilder();
        List<String> command = new ArrayList<>();
        command.add("ogr2ogr");
        command.add("-f"); 
        command.add("GeoJSON");
        command.add("-s_srs"); 
        command.add(coordinate);
        command.add("-t_srs"); 
        command.add(TARGET_EPSG);
        if (sqlQuery != null && !sqlQuery.trim().isEmpty()) {
            command.add("-sql");
            command.add(sqlQuery);
        }
        command.add("-preserve_fid");
        command.add("-oo");
        command.add("ENCODING=UTF-8");
        command.add(outputfile);
        command.add(inputfile);
        
        pb.command(command);
        return pb;
    }
    
    // CSV 파일용
    private ProcessBuilder csvCommand(String inputfile, String outputfile, String coordinate, String xColumnName, String yColumnName, String encoding) {
        return makeCommandCSV(inputfile, outputfile, coordinate, xColumnName, yColumnName, encoding);
    }

    // ZIP 파일용  
    private ProcessBuilder zipCommand(String inputfile, String outputfile, String coordinate, String sqlQuery) {
        return makeCommandShp("/vsizip/" + inputfile, outputfile, coordinate, sqlQuery);
    }
    
    private String buildRenameSQL(List<String> originalColumns, List<String> modifiedColumns, String layerName) {
        StringBuilder selectClause = new StringBuilder("SELECT ");

        for (int i = 0; i < originalColumns.size(); i++) {
            String originalCol = originalColumns.get(i);
            String modifiedCol = modifiedColumns.get(i);
            selectClause.append(String.format("\"%s\" AS \"%s\"", originalCol, modifiedCol));

            if (i < originalColumns.size() - 1) {
                selectClause.append(", ");
            }
        }
        
        selectClause.append(" FROM \"").append(layerName).append("\"");
        return selectClause.toString();
    }

    // DXF 파일용
    private ProcessBuilder dxfCommand(String inputfile, String outputfile, String coordinate) {
        return makeCommand(inputfile, outputfile, coordinate);
    }

	@Override
	public List<String> getFileColumns(String sourceFilePath, boolean isZip) throws Exception {
	    String pathToRead;
	    if (isZip) {
	        pathToRead = "/vsizip/" + sourceFilePath;
	    } else {
	        pathToRead = sourceFilePath;
	    }

	    // 2. ogrinfo 명령어 생성
	    ProcessBuilder processBuilder = new ProcessBuilder("ogrinfo", "-so", "-json", pathToRead);
	    Process process = processBuilder.start();
        
        String jsonOutput;
        try (BufferedReader reader = new BufferedReader(new InputStreamReader(process.getInputStream()))) {
            jsonOutput = reader.lines().collect(Collectors.joining("\n"));
        }
        
        List<String> columns = new ArrayList<>();
        JsonNode root = objectMapper.readTree(jsonOutput);

        JsonNode layersNode = root.path("layers");
        if (layersNode.isArray() && layersNode.size() > 0) {
            JsonNode firstLayer = layersNode.get(0);
            layerName = firstLayer.path("name").asText();
            layerType = firstLayer.path("geometryType").asText();

            JsonNode fieldsNode = firstLayer.path("fields");
            if (fieldsNode.isArray()) {
                for (JsonNode field : fieldsNode) {
                    columns.add(field.path("name").asText());
                }
            }
        }
        
        int exitCode = process.waitFor();

        if (exitCode != 0) {
            throw new RuntimeException("파일을 읽는데 실패하였습니다. Exit code: " + exitCode);
        }
        
        return columns;
	}
	
	public Map<String, Object> getShapeFileSampleWithColumns(String sourceFilePath, int limit, String coordinate, boolean isEPSG, boolean isZip, String firstColumn, String secondColumn, String encoding) throws Exception {
		String tempGeoJsonPath = Paths.get(System.getProperty("java.io.tmpdir"),"preview_" + java.util.UUID.randomUUID() + ".geojson").toString();
		
		List<String> command = new ArrayList<>();
		command.add("ogr2ogr");
		command.add("-f");
		command.add("GeoJSON");
		if(isEPSG) {
	        command.add("-s_srs");
	        command.add(coordinate);
	        command.add("-t_srs");
	        command.add(TARGET_EPSG);
		}
		command.add("-limit");
		command.add(String.valueOf(limit));
        if (firstColumn != null && !firstColumn.isEmpty() && secondColumn != null && !secondColumn.isEmpty()) {
            command.add("-oo");
            command.add("X_POSSIBLE_NAMES=" + firstColumn);
            command.add("-oo");
            command.add("Y_POSSIBLE_NAMES=" + secondColumn);
        }
		command.add("-oo"); 
        command.add("ENCODING=" + encoding);
		command.add(tempGeoJsonPath);
		if(isZip) {
			command.add("/vsizip/" + sourceFilePath);
		} else {
			command.add(sourceFilePath);
		}

		ProcessBuilder processBuilder = new ProcessBuilder(command);
		
        Process process = processBuilder.start();
        try (BufferedReader errorReader = new BufferedReader(new InputStreamReader(process.getErrorStream()))) {
            String errorLog = errorReader.lines().collect(Collectors.joining("\n"));
            System.err.println("GDAL 오류 로그:\n" + errorLog);
        }
        
        int exitCode = process.waitFor();

        if (exitCode != 0) {
            throw new RuntimeException("ogr2ogr 실행 실패, exitCode = " + exitCode);
        }
		
        Map<String, Object> geoJsonData;
        try (java.io.FileReader fr = new java.io.FileReader(tempGeoJsonPath)) {
            geoJsonData = objectMapper.readValue(fr, Map.class);
        }

        try {
            java.nio.file.Files.deleteIfExists(java.nio.file.Paths.get(tempGeoJsonPath));
        } catch (IOException e) {
            logger.warn("임시 geojson 파일 삭제 실패", e);
        }
        
        List<String> columns = new ArrayList<>();
        List<Map<String, Object>> rows = new ArrayList<>();
        
        Object featuresObj = geoJsonData.get("features");
        if (featuresObj instanceof List) {
            List features = (List) featuresObj;
            if (!features.isEmpty()) {
                Object firstFeature = features.get(0);
                if (firstFeature instanceof Map) {
                    Map firstFeatureMap = (Map) firstFeature;
                    Object propertiesObj = firstFeatureMap.get("properties");
                    if (propertiesObj instanceof Map) {
                        columns.addAll(((Map) propertiesObj).keySet());
                    }
                }
            }

            for (Object featureObj : features) {
                if (featureObj instanceof Map) {
                    Map featureMap = (Map) featureObj;
                    Object propsObj = featureMap.get("properties");
                    if (propsObj instanceof Map) {
                        rows.add((Map<String, Object>) propsObj);
                    }
                }
            }
        }
		
        Map<String, Object> result = new HashMap<>();
        result.put("columns", columns);
        result.put("rows", rows);
        result.put("geojson", geoJsonData);

		return result;
	}
	
	public String getLayerName() { return layerName; }
    public void setLayerName(String layerName) { this.layerName = layerName; }
    public String getLayerType() { return layerType; }
    public void setLayerType(String layerType) { this.layerType = layerType; }
}
