package incheon.uis.uld.service.impl;

import com.fasterxml.jackson.databind.ObjectMapper;
import incheon.uis.uld.service.UisGdalConversionService;

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

import java.io.*;
import java.nio.charset.StandardCharsets;
import java.nio.file.Paths;
import java.util.*;
import java.util.stream.Collectors;

@Service
public class UisGdalConversionServiceImpl implements UisGdalConversionService {
    @Autowired private ObjectMapper objectMapper;

    @Value("${geojson.output.dir:}")
    private String geoJsonOutputDirProp;
    private String geoJsonOutputDir() {
        if (geoJsonOutputDirProp != null && !geoJsonOutputDirProp.isBlank()) {
            if (!geoJsonOutputDirProp.endsWith("/") && !geoJsonOutputDirProp.endsWith("\\")) {
                return geoJsonOutputDirProp + File.separator;
            }
            return geoJsonOutputDirProp;

        }
        return System.getProperty("java.io.tmpdir");
    }

    private static final String TARGET_EPSG = "EPSG:4326";

    public Map<String, Object> getShapeFileSampleWithColumns(
            String sourceFilePath, int limit, String coordinate, boolean isEPSG, boolean isZip,
            String firstColumn, String secondColumn, String encoding) throws Exception {

        Map<String, Object> multi = getShapeFileSampleWithColumnsMulti(
                sourceFilePath, limit, coordinate, isEPSG, isZip, firstColumn, secondColumn, encoding
        );

        @SuppressWarnings("unchecked")
        List<Map<String, Object>> previews = (List<Map<String, Object>>) multi.get("previews");
        if (previews != null && !previews.isEmpty()) {
            Map<String, Object> first = previews.get(0);
            Map<String, Object> result = new HashMap<>();
            result.put("columns", first.get("columns"));
            result.put("rows", first.get("rows"));
            result.put("geojson", first.get("geojson"));
            result.put("layer", first.get("layer"));
            return result;
        }
        throw new IllegalStateException("레이어를 찾지 못했습니다.");
    }

    @Override
    public List<String> convertToGeoJson(
            String sourceFilePath, String originalFileName, String coordinate, String encoding,
            List<String> shapeColumns, String firstColumn, String secondColumn) throws Exception {

        boolean isZip = originalFileName != null && originalFileName.toLowerCase().endsWith(".zip");

        List<String> layerNames = listLayers(sourceFilePath, isZip);
        if (layerNames.isEmpty()) throw new IllegalStateException("레이어를 찾지 못했습니다.");

        List<String> outPaths = new ArrayList<>();
        for (String layer : layerNames) {
            String safeFileName = sanitizeFileName(stripExt(originalFileName));
            String safeLayer = sanitizeFileName(layer);
            String out = Paths.get(geoJsonOutputDir(), safeFileName + "_" + safeLayer + "_" + System.currentTimeMillis() + ".geojson").toString();

            List<String> command = new ArrayList<>();
            command.add("ogr2ogr");
            command.add("-f"); command.add("GeoJSON");
            if (coordinate != null && !coordinate.isBlank()) {
                command.add("-s_srs"); command.add(coordinate);
                command.add("-t_srs"); command.add(TARGET_EPSG);
            }
            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 == null ? "UTF-8" : encoding));
            command.add(out);
            command.add(isZip ? "/vsizip/" + sourceFilePath : sourceFilePath);
            command.add(layer);

            ProcessBuilder pb = new ProcessBuilder(command);
            pb.redirectErrorStream(true);
            Process p = pb.start();
            String log;
            try (BufferedReader br = new BufferedReader(new InputStreamReader(p.getInputStream(), StandardCharsets.UTF_8))) {
                log = br.lines().collect(Collectors.joining("\n"));
            }
            int code = p.waitFor();
            if (code != 0) {
                System.err.println("[GDAL] 변환 실패(" + layer + "):\n" + log);
                continue;
            }
            outPaths.add(out);
        }
        return outPaths;
    }

    private Map<String, Object> getShapeFileSampleWithColumnsMulti(
            String sourceFilePath, int limit, String coordinate, boolean isEPSG, boolean isZip,
            String firstColumn, String secondColumn, String encoding) throws Exception {

        List<String> layerNames = listLayers(sourceFilePath, isZip);
        if (layerNames.isEmpty()) throw new IllegalStateException("레이어를 찾지 못했습니다.");

        List<Map<String, Object>> previews = new ArrayList<>();

        for (String layer : layerNames) {
            String safeLayer = sanitizeFileName(layer);
            String tempGeoJsonPath = Paths.get(
                    System.getProperty("java.io.tmpdir"),
                    "preview_" + safeLayer + "_" + 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);
            command.add(isZip ? "/vsizip/" + sourceFilePath : sourceFilePath);
            command.add(layer);

            ProcessBuilder processBuilder = new ProcessBuilder(command);
            processBuilder.redirectErrorStream(true);
            Process process = processBuilder.start();

            String gdalLog;
            try (BufferedReader br = new BufferedReader(new InputStreamReader(process.getInputStream(), StandardCharsets.UTF_8))) {
                gdalLog = br.lines().collect(Collectors.joining("\n"));
            }
            int exitCode = process.waitFor();
            if (exitCode != 0) {
                System.err.println("[GDAL] 변환 실패(" + layer + ")\n" + gdalLog);
                continue;
            }

            Map<String, Object> geoJsonData;
            try (FileReader fr = new FileReader(tempGeoJsonPath)) {
                geoJsonData = objectMapper.readValue(fr, Map.class);
            }

            List<String> columns = new ArrayList<>();
            List<Map<String, Object>> rows = new ArrayList<>();

            Object featuresObj = geoJsonData.get("features");
            if (featuresObj instanceof List<?> features && !features.isEmpty()) {
                Object firstFeature = features.get(0);
                if (firstFeature instanceof Map<?, ?> firstFeatureMap) {
                    Object propsObj = ((Map<?, ?>) firstFeatureMap).get("properties");
                    if (propsObj instanceof Map<?, ?> props) {
                        for (Object k : ((Map<?, ?>) props).keySet()) columns.add(String.valueOf(k));
                    }
                }
                for (Object fo : features) {
                    if (fo instanceof Map<?, ?> fMap) {
                        Object po = ((Map<?, ?>) fMap).get("properties");
                        if (po instanceof Map<?, ?> pmap) {
                            @SuppressWarnings("unchecked")
                            Map<String, Object> casted =
                                    new LinkedHashMap<>((Map<String, Object>) pmap);
                            rows.add(casted);
                        }
                    }
                }
            }

            Map<String, Object> one = new HashMap<>();
            one.put("layer", layer);
            one.put("columns", columns);
            one.put("rows", rows);
            one.put("geojsonPath", tempGeoJsonPath);
            one.put("geojson", geoJsonData);

            previews.add(one);
        }

        Map<String, Object> result = new HashMap<>();
        result.put("layers", layerNames);
        result.put("previews", previews);
        return result;
    }

    private List<String> listLayers(String sourcePath, boolean isZip) throws Exception {
        List<String> cmd = new ArrayList<>();
        cmd.add("ogrinfo");
        cmd.add("-ro"); cmd.add("-so"); cmd.add("-q");
        cmd.add(isZip ? "/vsizip/" + sourcePath : sourcePath);

        ProcessBuilder pb = new ProcessBuilder(cmd);
        pb.redirectErrorStream(true);

        Process p = pb.start();
        List<String> layers = new ArrayList<>();
        try (BufferedReader r = new BufferedReader(new InputStreamReader(p.getInputStream(), StandardCharsets.UTF_8))) {
            String line;
            while ((line = r.readLine()) != null) {
                int idx = line.indexOf(':');
                if (idx > -1) {
                    String name = line.substring(idx + 1).trim();
                    int sp = name.indexOf(' ');
                    if (sp > 0) name = name.substring(0, sp);
                    if (!name.isBlank()) layers.add(name);
                }
            }
        }
        p.waitFor();
        return layers;
    }

    private String stripExt(String name) {
        if (name == null) return "file";
        // 경로에서 파일명만 추출
        int pathSep = Math.max(name.lastIndexOf('/'), name.lastIndexOf('\\'));
        if (pathSep >= 0) {
            name = name.substring(pathSep + 1);
        }
        int i = name.lastIndexOf('.');
        return i > 0 ? name.substring(0, i) : name;
    }

    /**
     * 파일시스템에서 허용되지 않는 문자 제거
     */
    private String sanitizeFileName(String name) {
        if (name == null) return "layer";
        // Windows에서 허용되지 않는 문자: \ / : * ? " < > |
        // 그 외 특수문자도 제거
        return name.replaceAll("[\\\\/:*?\"<>|`'\\s]", "_");
    }
}
