package incheon.product.geoview2d.download.service.impl;

import incheon.com.cmm.exception.BusinessException;
import incheon.com.cmm.exception.EntityNotFoundException;
import incheon.product.common.config.GeoViewProperties;
import incheon.product.geoview2d.download.mapper.DownloadMapper;
import incheon.product.geoview2d.download.repository.SpatialDataRepository;
import incheon.product.geoview2d.download.service.FileFormatConverter;
import incheon.product.geoview2d.download.service.SpatialDownloadService;
import lombok.extern.slf4j.Slf4j;
import org.springframework.http.HttpStatus;
import org.egovframe.rte.fdl.cmmn.EgovAbstractServiceImpl;
import org.springframework.core.io.FileSystemResource;
import org.springframework.http.HttpHeaders;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
import org.springframework.stereotype.Service;

import javax.annotation.PreDestroy;
import javax.annotation.Resource;
import java.io.IOException;
import java.net.URLEncoder;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Path;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.util.Arrays;
import java.util.List;
import java.util.Map;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;

/**
 * 공간 데이터 다운로드 서비스 구현체.
 * 레이어 메타데이터 조회 → 포맷별 변환 → 파일 응답 생성.
 */
@Slf4j
@Service("productSpatialDownloadService")
public class SpatialDownloadServiceImpl extends EgovAbstractServiceImpl implements SpatialDownloadService {

    @Resource(name = "productDownloadMapper")
    private DownloadMapper downloadMapper;

    @Resource(name = "spatialDataRepository")
    private SpatialDataRepository dataRepository;

    @Resource(name = "productFileFormatConverter")
    private FileFormatConverter fileFormatConverter;

    @Resource(name = "geoViewProperties")
    private GeoViewProperties geoViewProperties;

    private static final DateTimeFormatter TIMESTAMP_FMT = DateTimeFormatter.ofPattern("yyyyMMdd_HHmmss");

    private final ScheduledExecutorService cleanupExecutor = Executors.newSingleThreadScheduledExecutor(r -> {
        Thread t = new Thread(r, "download-cleanup");
        t.setDaemon(true);
        return t;
    });

    @PreDestroy
    public void shutdown() {
        cleanupExecutor.shutdown();
    }

    @Override
    public ResponseEntity<org.springframework.core.io.Resource> download(Long layerId, String layerType, String format, String columns) {
        // 1. 메타데이터 조회
        Map<String, Object> meta = resolveLayerMeta(layerId, layerType);
        if (meta == null) {
            throw new EntityNotFoundException("레이어를 찾을 수 없습니다: " + layerId);
        }

        String tableName = resolveTableName(meta, layerType);
        String layerName = resolveLayerName(meta, layerType);
        String spceTy = resolveGeomType(meta, layerType);
        int srid = resolveSrid(meta);

        // 2. 컬럼 목록
        List<String> columnList;
        if (columns != null && !columns.isEmpty()) {
            columnList = Arrays.stream(columns.split(",")).map(String::trim).collect(Collectors.toList());
        } else {
            List<Map<String, Object>> metaColumns = downloadMapper.selectTableColumns(tableName);
            columnList = metaColumns.stream().map(c -> (String) c.get("column_name")).collect(Collectors.toList());
        }

        String geomColumn = dataRepository.selectGeometryColumnName(tableName);
        String timestamp = LocalDateTime.now().format(TIMESTAMP_FMT);
        String baseName = fileFormatConverter.sanitizeFileName(layerName) + "_" + timestamp;

        Path tempDir = null;
        try {
            tempDir = Files.createTempDirectory("geoview_download_");
            Path resultFile = processFormat(format, tempDir, baseName, tableName, geomColumn, columnList, spceTy, srid);
            return buildResponse(resultFile, baseName, format);
        } catch (IOException e) {
            log.error("다운로드 처리 실패 - layerId: {}, format: {}", layerId, format, e);
            throw new BusinessException("다운로드 처리 중 오류가 발생했습니다.", e);
        } finally {
            if (tempDir != null) {
                scheduleDelayedCleanup(tempDir);
            }
        }
    }

    private Path processFormat(String format, Path tempDir, String baseName, String tableName,
                               String geomColumn, List<String> columns, String spceTy, int srid) throws IOException {
        return switch (format.toLowerCase()) {
            case "shapefile" -> {
                List<Map<String, Object>> data = dataRepository.selectShapefileData(tableName, geomColumn, columns);
                yield fileFormatConverter.createShapefile(tempDir, baseName, data, columns, spceTy, srid);
            }
            case "csv" -> fileFormatConverter.createCsvFile(tempDir, baseName, columns,
                    consumer -> dataRepository.streamData(tableName, geomColumn, columns, consumer));
            case "excel" -> fileFormatConverter.createExcelFile(tempDir, baseName, columns,
                    consumer -> dataRepository.streamData(tableName, geomColumn, columns, consumer));
            case "dxf" -> fileFormatConverter.createDxfFile(tempDir, baseName, columns,
                    consumer -> dataRepository.streamDxfData(tableName, geomColumn, columns, consumer));
            case "geojson" -> fileFormatConverter.createGeoJsonFile(tempDir, baseName,
                    consumer -> dataRepository.streamGeoJsonFeatures(tableName, geomColumn, columns, consumer));
            default -> throw new BusinessException("지원하지 않는 포맷: " + format, HttpStatus.BAD_REQUEST);
        };
    }

    private ResponseEntity<org.springframework.core.io.Resource> buildResponse(Path file, String baseName, String format) throws IOException {
        String extension = switch (format.toLowerCase()) {
            case "shapefile" -> ".zip";
            case "csv" -> ".csv";
            case "excel" -> ".xlsx";
            case "dxf" -> ".dxf";
            case "geojson" -> ".geojson";
            default -> "";
        };

        String fileName = baseName + extension;
        String encoded = URLEncoder.encode(fileName, StandardCharsets.UTF_8);

        MediaType mediaType = "excel".equalsIgnoreCase(format)
                ? MediaType.parseMediaType("application/vnd.openxmlformats-officedocument.spreadsheetml.sheet")
                : MediaType.APPLICATION_OCTET_STREAM;

        return ResponseEntity.ok()
                .header(HttpHeaders.CONTENT_DISPOSITION, "attachment; filename*=UTF-8''" + encoded)
                .contentType(mediaType)
                .contentLength(Files.size(file))
                .body(new FileSystemResource(file));
    }

    private Map<String, Object> resolveLayerMeta(Long layerId, String layerType) {
        if ("TASK".equalsIgnoreCase(layerType)) {
            return downloadMapper.selectDownloadLayerInfo(layerId);
        }
        return downloadMapper.selectDownloadUserLayerInfo(layerId);
    }

    private String resolveTableName(Map<String, Object> meta, String layerType) {
        return (String) meta.get("lyr_phys_nm");
    }

    private String resolveLayerName(Map<String, Object> meta, String layerType) {
        if ("TASK".equalsIgnoreCase(layerType)) {
            return (String) meta.get("task_lyr_nm");
        }
        return (String) meta.get("user_lyr_nm");
    }

    private String resolveGeomType(Map<String, Object> meta, String layerType) {
        return (String) meta.get("spce_ty");
    }

    private int resolveSrid(Map<String, Object> meta) {
        Object srid = meta.get("cntm");
        if (srid instanceof Number) {
            return ((Number) srid).intValue();
        }
        return geoViewProperties.getCoordinate().getDefaultSrid();
    }

    private void scheduleDelayedCleanup(Path tempDir) {
        GeoViewProperties.Download downloadConfig = geoViewProperties.getDownload();
        long delayMs = downloadConfig != null ? downloadConfig.getCleanupDelayMillis() : 10_000L;
        cleanupExecutor.schedule(() -> {
            try {
                Files.walk(tempDir)
                        .sorted(java.util.Comparator.reverseOrder())
                        .forEach(path -> {
                            try { Files.deleteIfExists(path); } catch (IOException e) { log.trace("임시 파일 삭제 실패: {}", path, e); }
                        });
            } catch (Exception e) {
                log.warn("임시 파일 정리 실패 - dir: {}", tempDir, e);
            }
        }, delayMs, TimeUnit.MILLISECONDS);
    }
}
