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

import java.io.BufferedInputStream;
import java.io.BufferedOutputStream;
import java.io.BufferedReader;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStreamReader;
import java.net.URI;
import java.nio.charset.StandardCharsets;
import java.nio.file.DirectoryStream;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.nio.file.StandardCopyOption;
import java.nio.file.StandardOpenOption;
import java.sql.Date;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Comparator;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.UUID;
import java.util.stream.Collectors;
import java.util.zip.ZipEntry;
import java.util.zip.ZipInputStream;

import com.fasterxml.jackson.core.JsonProcessingException;
import org.apache.ibatis.session.SqlSession;
import org.apache.ibatis.session.SqlSessionFactory;
import org.geotools.api.data.DataStore;
import org.geotools.api.data.DataStoreFactorySpi;
import org.geotools.api.data.DataStoreFinder;
import org.geotools.api.data.FeatureSource;
import org.geotools.api.data.FeatureStore;
import org.geotools.api.data.SimpleFeatureSource;
import org.geotools.api.feature.Property;
import org.geotools.api.feature.simple.SimpleFeature;
import org.geotools.api.feature.simple.SimpleFeatureType;
import org.geotools.api.feature.type.AttributeDescriptor;
import org.geotools.api.feature.type.GeometryDescriptor;
import org.geotools.api.referencing.FactoryException;
import org.geotools.api.referencing.crs.CoordinateReferenceSystem;
import org.geotools.data.simple.SimpleFeatureCollection;
import org.geotools.feature.FeatureCollection;
import org.geotools.feature.FeatureIterator;
import org.geotools.feature.simple.SimpleFeatureTypeBuilder;
import org.geotools.referencing.CRS;
import org.locationtech.jts.geom.Geometry;
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.http.HttpEntity;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
import org.springframework.stereotype.Service;
import org.springframework.web.client.HttpClientErrorException;
import org.springframework.web.client.RestTemplate;
import org.springframework.web.multipart.MultipartFile;

import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;

import incheon.ags.mrb.upload.service.ProcessCsvFileService;
import incheon.ags.mrb.upload.service.ProcessDxfFileService;
import incheon.ags.mrb.upload.service.ProcessFileService;
import incheon.ags.mrb.upload.service.ProcessShapeFileService;
import incheon.ags.mrb.upload.vo.FileUploadRequestDTO;

@Service
public class ProcessFileServiceImpl implements ProcessFileService {

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

    private final ProcessCsvFileService processCsvFileService;
    private final ProcessShapeFileService processShapeFileService;
    private final ProcessDxfFileService processDxfFileService;
    @Autowired private RestTemplate restTemplate;
    @Autowired private ObjectMapper objectMapper;

    @Value("${gis.server.url}/rest")
    private String BASE_URL;

    @Autowired
    public ProcessFileServiceImpl(ProcessCsvFileService processCsvFileService,
            ProcessShapeFileService processShapeFileService,
            ProcessDxfFileService processDxfFileService) { // 추가
        this.processCsvFileService = processCsvFileService;
        this.processShapeFileService = processShapeFileService;
        this.processDxfFileService = processDxfFileService;
    }

    public Map<String, Object> getShapeColumns(MultipartFile file, int previewCounter, String encoding)
            throws Exception {
        return processShapeFileService.getShapeColumns(file, previewCounter, encoding);
    }

    public Map<String, Object> previewShapeFile(MultipartFile file, int previewCounter, String coordinate,
            String encoding) throws FactoryException, IOException {
        return processShapeFileService.previewShapeFile(file, previewCounter, coordinate, encoding);
    }

    public Map<String, Object> previewCsvFile(MultipartFile file, int previewCounter, String coordinate,
            String firstColumn, String secondColumn, String encoding) throws Exception {
        return processCsvFileService.previewCsvFile(file, previewCounter, coordinate, firstColumn, secondColumn,
                encoding);
    }

    public Map<String, Object> previewCsvWktFile(MultipartFile file, int previewCounter, String coordinate,
            String wktColumn, String encoding) throws Exception {
        return processCsvFileService.previewCsvWktFile(file, previewCounter, coordinate, wktColumn, encoding);
    }

    @Override
    public Map<String, Object> previewDxfLayer(MultipartFile file, String layerName, String coordinate, int limit)
            throws IOException, InterruptedException {
        return processDxfFileService.previewLayer(file, layerName, coordinate, limit);
    }

    @Override
    public Map<String, Object> analyzeDxfFile(MultipartFile file, String coordinate, int limit)
            throws IOException, InterruptedException {
        return processDxfFileService.analyzeDxfFile(file, coordinate, limit);
    }

    public Map<String, Object> processFile(FileUploadRequestDTO requestDTO) throws IOException {
        MultipartFile file = requestDTO.getFile();
        if (file == null || file.isEmpty()) {
            throw new IllegalArgumentException("업로드된 파일이 없습니다.");
        }

        String originalFilename = file.getOriginalFilename();
        String extension = originalFilename.substring(originalFilename.lastIndexOf(".") + 1).toLowerCase();

        Map<String, Object> processResult;
        if ("csv".equals(extension)) {
            if (requestDTO.getWktColumn() != null) {
                processResult = processCsvFileService.processCsvWktFile(requestDTO);
            } else {
                processResult = processCsvFileService.processCsvXyFile(requestDTO);
            }
        } else if ("zip".equals(extension)) {
            processResult = processShapeFileService.processShapeFile(requestDTO);
        } else if ("dxf".equals(extension)) {
            processResult = processDxfFileService.processDxfFile(requestDTO);
        } else {
            throw new IllegalArgumentException("지원하지 않는 파일 형식입니다: " + extension);
        }

        publishLayerAfterProcess(processResult);

        return processResult;
    }

    private void publishLayerAfterProcess(Map<String, Object> processResult) {
        if (processResult != null && processResult.containsKey("safeTableName")
                && processResult.containsKey("srid")) {
            String safeTableName = (String) processResult.get("safeTableName");
            Integer srid = (Integer) processResult.get("srid");

            // ✅ DB는 원본 좌표계(srid)로 저장되었고, 화면 표시는 EPSG:3857로 발행
            logger.info("레이어 발행 준비 - 테이블: {}, DB 저장 SRID: EPSG:{}, 화면 표시 SRID: EPSG:3857",
                    safeTableName, srid);

            int maxRetries = 5;
            int retryDelay = 1000; // 1초

            for (int i = 0; i < maxRetries; i++) {
                // 화면 표시용으로 무조건 EPSG:3857로 발행 (웹 지도 표준)
                try {
                    publishExistingLayer("incheon", "icmrb", safeTableName, safeTableName, 3857);
                    processResult.put("layerPublished", true);
                    processResult.put("publishMessage", "Layer published successfully: " + safeTableName);
                    logger.info("레이어 발행 성공 (시도 {}회) - DB: EPSG:{}, 화면: EPSG:3857", i + 1, srid);
                    return;
                } catch (JsonProcessingException | RuntimeException e) {
                    logger.warn("레이어 발행 시도 {} 실패", i + 1, e);
                    if (i < maxRetries - 1) {
                        try {
                            Thread.sleep(retryDelay);
                            retryDelay *= 2;
                        } catch (InterruptedException ie) {
                            Thread.currentThread().interrupt();
                        }
                    } else {
                        logger.error("레이어 발행 실패", e);
                        processResult.put("layerPublished", false);
                        processResult.put("publishMessage", "Layer publish failed");
                    }
                }
            }
        }
    }

    public List<String> getPublishedLayers(String workspace, String storage) {
        String url = String.format("%s/workspace/%s/storage/%s/tables", BASE_URL, workspace, storage);
        ResponseEntity<Map> response = restTemplate.getForEntity(url, Map.class);
        if (response.getStatusCode() == HttpStatus.OK && response.getBody() != null) {
            Object tablesObj = response.getBody().get("tables");
            if (tablesObj instanceof List) {
                return (List<String>) tablesObj;
            }
        }
        return List.of();
    }

    public void publishExistingLayer(String workspace, String storage, String layerName, String tableName,
            int serviceSrid) throws JsonProcessingException {
        List<String> publishedLayers = getPublishedLayers(workspace, storage);

        if (!publishedLayers.contains(tableName)) {
            boolean success = publishLayer(workspace, storage, layerName, tableName, serviceSrid);
            if (success) {
                logger.debug("New layer published successfully: {}", layerName);
            } else {
                logger.error("Failed to publish layer: {}", layerName);
            }
        } else {
            logger.info("Layer already exists: {}", tableName);
        }
    }

    private boolean publishLayer(String workspace, String storage, String layerName, String tableName, int serviceSrid)
            throws JsonProcessingException {
        String url = BASE_URL + "/layer";
        logger.debug("Publishing layer - workspace: {}, storage: {}, tableName: {}", workspace, storage, tableName);

        HttpHeaders headers = new HttpHeaders();
        headers.setContentType(MediaType.APPLICATION_JSON);

        Map<String, Object> jsonMap = new HashMap<>();
        jsonMap.put("name", layerName);
        jsonMap.put("workspace", workspace);
        jsonMap.put("storage", storage);
        jsonMap.put("tableName", tableName);
        jsonMap.put("serviceSrid", serviceSrid);
        jsonMap.put("minx", 746110.2599834986);
        jsonMap.put("miny", 1881540.2538266997);
        jsonMap.put("maxx", 937637.2836452124);
        jsonMap.put("maxy", 2001987.241769713);

        HttpEntity<Map<String, Object>> request = new HttpEntity<>(jsonMap, headers);

        String jsonString = objectMapper.writeValueAsString(jsonMap);
        logger.debug("Request Body JSON: {}", jsonString);

        try {
            ResponseEntity<String> response = restTemplate.postForEntity(url, request, String.class);
            logger.debug("Full request URL: {}", url);
            logger.debug("Request headers: {}", headers.toString());
            logger.debug("Request body: {}", jsonMap.toString());

            if (response.getStatusCode() == HttpStatus.OK || response.getStatusCode() == HttpStatus.CREATED) {
                logger.info("Layer published successfully: {}", layerName);
                return true;
            } else {
                logger.error("Failed to publish layer: {}", response.getStatusCode());
                return false;
            }
        } catch (HttpClientErrorException ex) {
            logger.error("HTTP error during layer publish: {} - {}", ex.getStatusCode(), ex.getResponseBodyAsString(),
                    ex);
            return false;
        } catch (Exception ex) {
            logger.error("Unexpected error during layer publish", ex);
            return false;
        }
    }

    public boolean deleteLayer(String workspace, String layerName) {
        try {
            String urlTemplate = String.format("%s/workspace/%s/layer/%s", BASE_URL, workspace, layerName);
            URI uri = new URI(urlTemplate);

            logger.debug("=== DELETE Layer ===");
            logger.debug("URL: {}", urlTemplate);
            logger.debug("Workspace: {}", workspace);
            logger.debug("LayerName: {}", layerName);

            restTemplate.delete(uri);
            logger.info("Layer deleted successfully: {}", layerName);
            return true;
        } catch (HttpClientErrorException ex) {
            if (ex.getStatusCode() == HttpStatus.NOT_FOUND) {
                logger.warn("Layer not found: {} in workspace: {}", layerName, workspace);
                return true;
            } else {
                logger.error("HTTP Error [{}]: {}", ex.getStatusCode(), ex.getResponseBodyAsString());
                return false;
            }
        } catch (Exception ex) {
            logger.error("Unexpected error: {}", ex.getMessage(), ex);
            return false;
        }
    }
}