package incheon.uis.uer.web;

import incheon.uis.uer.service.UerShpService;
import incheon.uis.uer.vo.TaskLayerVO;
import incheon.uis.ums.shp.GeoJsonToShpConverter;
import incheon.uis.ums.shp.ShapeDownloadUtil;
import incheon.uis.ums.shp.WfsSchemaUtil;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;

import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.io.InputStream;
import java.net.URLEncoder;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Path;
import java.time.LocalDate;
import java.time.format.DateTimeFormatter;
import java.util.Comparator;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.zip.ZipOutputStream;

@RestController
@RequestMapping("/api/uis/uer")
public class UerShpController {

    private final UerShpService service;

    @Value("${gis.server.url}")
    private String gisServerUrl;

    public UerShpController(UerShpService service) {
        this.service = service;
    }

    /**
     * 레이어 목록 JSON
     * GET /api/uis/uer/layers
     */
    @GetMapping("/layers.do")
    public ResponseEntity<Map<String, Object>> getLayerList() {
        Map<String, Object> res = new HashMap<>();
        try {
            List<TaskLayerVO> list = service.getTaskLayerList();
            res.put("success", true);
            res.put("data", list);
            return ResponseEntity.ok(res);
        } catch (Exception e) {
            res.put("success", false);
            res.put("message", "레이어 목록 조회 중 오류가 발생했습니다.");
            return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).body(res);
        }
    }

    /**
     * SHP 다운로드
     * GET /api/uis/uer/download?typeName=...&cqlFilter=...&fileName=...
     *
     * - typeName : 레이어 목록의 task_lyr 값 그대로
     */
    @GetMapping("/download.do")
    public void downloadShp(
            @RequestParam("typeName") String typeName,
            @RequestParam(name = "cqlFilter", required = false) String cqlFilter,
            @RequestParam(name = "fileName", required = false) String fileName,
            HttpServletResponse response
    ) throws IOException {

        if (typeName == null || typeName.isBlank()) {
            response.sendError(HttpServletResponse.SC_BAD_REQUEST, "typeName is required");
            return;
        }

        // HTMLTagFilter에 의해 이스케이프된 HTML 엔티티 복원
        if (cqlFilter != null) {
            cqlFilter = cqlFilter
                    .replace("&apos;", "'")
                    .replace("&quot;", "\"")
                    .replace("&lt;", "<")
                    .replace("&gt;", ">")
                    .replace("&amp;", "&");
        }

        // 날짜 생성
        String today = LocalDate.now().format(DateTimeFormatter.BASIC_ISO_DATE);

        // 파일명 결정 (expln > typeName)
        String baseName;
        if (fileName != null && !fileName.isBlank()) {
            baseName = ShapeDownloadUtil.toSafeFileName(fileName);
        } else {
            baseName = ShapeDownloadUtil.toSafeFileName(typeName.replace(':', '_') + "_shp");
        }
        if (baseName == null || baseName.isBlank()) baseName = "download";

        String downloadName = baseName + "_" + today + ".zip";
        String encodedFileName = URLEncoder.encode(downloadName, StandardCharsets.UTF_8)
                .replace("+", "%20");

        response.setContentType("application/zip");
        response.setHeader("Content-Disposition",
                "attachment; filename=\"" + encodedFileName + "\"");

        try (ZipOutputStream zos = new ZipOutputStream(response.getOutputStream())) {
            service.addLayerShpToZip(zos, typeName, cqlFilter, baseName);
            zos.finish();
        } catch (Exception e) {
            if (!response.isCommitted()) {
                response.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, "SHP 다운로드 중 오류가 발생했습니다.");
            }
            // committed면 서버 로그로만
            throw e;
        }
    }

    /**
     * GeoJSON → SHP 변환 다운로드 (이중 ZIP 구조)
     * POST /api/uis/uer/geojsonToShp.do
     *
     * 다운로드 구조:
     * - SHP 파일: {shpFileName}.shp, {shpFileName}.dbf, ...
     * - 첫번째 ZIP: {innerZipName}.zip (SHP 파일들 포함)
     * - 최종 ZIP: {outerZipName}_{날짜}.zip (첫번째 ZIP 포함)
     *
     * @param geojsonStr GeoJSON 문자열
     * @param shpFileName SHP 파일명 (예: "rdl_rdct_l")
     * @param innerZipName 첫번째 ZIP 이름 (예: "extracted_rdl_rdct_l")
     * @param outerZipName 최종 ZIP 이름 (예: "추출_도로중심선")
     * @param srid 좌표계 SRID
     */
    @PostMapping("/geojsonToShp.do")
    public void convertGeoJsonToShp(
            @RequestBody String geojsonStr,
            @RequestParam(name = "shpFileName", required = false, defaultValue = "extracted_layer") String shpFileName,
            @RequestParam(name = "innerZipName", required = false, defaultValue = "extracted_layer") String innerZipName,
            @RequestParam(name = "outerZipName", required = false, defaultValue = "추출_레이어") String outerZipName,
            @RequestParam(name = "srid", required = false, defaultValue = "5186") int srid,
            @RequestParam(name = "typeName", required = false) String typeName,
            HttpServletResponse response
    ) throws IOException {

        if (geojsonStr == null || geojsonStr.isBlank()) {
            response.sendError(HttpServletResponse.SC_BAD_REQUEST, "GeoJSON data is required");
            return;
        }

        // 날짜 생성
        String today = LocalDate.now().format(DateTimeFormatter.BASIC_ISO_DATE);

        // 파일명 안전하게 변환
        String safeShpFileName = ShapeDownloadUtil.toSafeFileName(shpFileName);
        String safeInnerZipName = ShapeDownloadUtil.toSafeFileName(innerZipName);
        String safeOuterZipName = ShapeDownloadUtil.toSafeFileName(outerZipName);

        if (safeShpFileName == null || safeShpFileName.isBlank()) safeShpFileName = "extracted_layer";
        if (safeInnerZipName == null || safeInnerZipName.isBlank()) safeInnerZipName = "extracted_layer";
        if (safeOuterZipName == null || safeOuterZipName.isBlank()) safeOuterZipName = "추출_레이어";

        String downloadName = safeOuterZipName + "_" + today + ".zip";
        String encodedFileName = URLEncoder.encode(downloadName, StandardCharsets.UTF_8)
                .replace("+", "%20");

        response.setContentType("application/zip");
        response.setHeader("Content-Disposition",
                "attachment; filename=\"" + encodedFileName + "\"");

        // DescribeFeatureType으로 스키마 조회 (typeName 있을 때만)
        WfsSchemaUtil.SchemaInfo schemaInfo = null;
        if (typeName != null && !typeName.isBlank()) {
            schemaInfo = WfsSchemaUtil.describeFeatureType(gisServerUrl, typeName);
        }

        Path tempDir = null;
        try {
            // 임시 디렉토리 생성
            tempDir = Files.createTempDirectory("geojson_to_shp_");

            // GeoJSON → SHP 변환 (이중 ZIP 구조)
            Path outerZipFile = GeoJsonToShpConverter.convertWithDoubleZip(
                    geojsonStr, tempDir, safeShpFileName, safeInnerZipName, safeOuterZipName, srid, schemaInfo);

            // ZIP 파일 전송
            try (InputStream in = Files.newInputStream(outerZipFile)) {
                byte[] buffer = new byte[8192];
                int bytesRead;
                while ((bytesRead = in.read(buffer)) != -1) {
                    response.getOutputStream().write(buffer, 0, bytesRead);
                }
            }
            response.getOutputStream().flush();

        } catch (Exception e) {
            if (!response.isCommitted()) {
                response.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR,
                        "GeoJSON → SHP 변환 중 오류가 발생했습니다: " + e.getMessage());
            }
            throw new IOException("GeoJSON → SHP 변환 실패", e);

        } finally {
            // 임시 파일 정리
            if (tempDir != null) {
                try {
                    Files.walk(tempDir)
                            .sorted(Comparator.reverseOrder())
                            .forEach(path -> {
                                try { Files.deleteIfExists(path); } catch (IOException ignored) {}
                            });
                } catch (IOException ignored) {}
            }
        }
    }
}
