package incheon.ags.drm.web;

import com.fasterxml.jackson.core.type.TypeReference;
import com.fasterxml.jackson.databind.ObjectMapper;
import incheon.ags.drm.service.agsDrmService;
import incheon.ags.drm.service.impl.agsDrmServiceImpl;
import incheon.ags.drm.vo.DrmContentVO;
import incheon.ags.drm.vo.DrmOrthoVO;
import incheon.ags.drm.vo.DrmPoiVO;
import incheon.ags.drm.vo.DrmResultVO;
import incheon.ags.mrb.analysis.web.SpatialAnalysisController;
import incheon.cmm.g2f.layer.vo.TaskLayerSearchRequestDTO;
import incheon.cmm.g2f.layer.vo.TaskLayerVO;
import incheon.com.security.annotation.RequirePermission;
import incheon.com.security.util.SecurityUtil;
import lombok.RequiredArgsConstructor;
import org.egovframe.rte.ptl.mvc.tags.ui.pagination.PaginationInfo;
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.core.io.Resource;
import org.springframework.dao.DataAccessException;
import org.springframework.dao.DataIntegrityViolationException;
import org.springframework.http.*;
import org.springframework.stereotype.Controller;
import org.springframework.ui.ModelMap;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.client.RestClientResponseException;
import org.springframework.web.client.RestTemplate;
import org.springframework.web.multipart.MultipartFile;
import org.springframework.web.util.HtmlUtils;

import java.io.FileNotFoundException;
import java.nio.file.*;
import java.util.*;
import java.util.stream.Collectors;

@Controller
@RequestMapping("/drm")
@RequiredArgsConstructor
public class agsDrmController {

    private static final Logger logger = LoggerFactory.getLogger(SpatialAnalysisController.class);
    private final agsDrmService agsDrmService;
    @Value("${gis.server.url}")
    private String BASE_URL;
    @Autowired ObjectMapper objectMapper;
    @GetMapping("/main.do")
    public String viewMap(ModelMap model) throws Exception {
        if (!SecurityUtil.canAccessSystem("DRM")) {
            return "redirect:/error/403";
        }
//        LoginVO user = RequestContext.getCurrentUser();
//        String userId = RequestContext.getCurrentUserId();
//        String userName = RequestContext.getCurrentUserName();
//        String deptCd = RequestContext.getCurrentUserDeptCd();
//        String menuCd = RequestContext.getCurrentMenuCd();
//        String sysCd = RequestContext.getCurrentSysCd();

        Map<String, Boolean> permission_data = new HashMap<>();
        /* 정보공개 등급별 데이터 권한 */
        permission_data.put("permission_data_unclassified", SecurityUtil.hasPermission("DRM", "PERM_DATA_DRMUN"));
        permission_data.put("permission_data_public", SecurityUtil.hasPermission("DRM", "PERM_DATA_DRMPB"));
        permission_data.put("permission_data_limit", SecurityUtil.hasPermission("DRM", "PERM_DATA_DRMLM"));
        permission_data.put("permission_data_private", SecurityUtil.hasPermission("DRM", "PERM_DATA_DRMPR"));
        /* 관리자 및 담당자 권한 */
        permission_data.put("permission_data_admin", SecurityUtil.hasRole("DRM", "ROLE_DRM_ADMIN"));
        permission_data.put("permission_data_task", SecurityUtil.hasRole("DRM", "ROLE_DRM_TASK"));

        String jsonPermissionData = objectMapper.writeValueAsString(permission_data);

        model.addAttribute("permission_data", jsonPermissionData);

        return "ags/drm/drmView";
    }

    @GetMapping("/getLayerList.do")
    public ResponseEntity<Map<String, Object>> getLayerList(TaskLayerSearchRequestDTO searchVO) {
        Map<String, Object> response = new HashMap<>();

        try {
            searchVO.setLyrGroupCd("pba");
            List<TaskLayerVO> taskLayerList = agsDrmService.getLayerList(searchVO);
            response.put("taskLayerList", taskLayerList);
            searchVO.setLyrGroupCd("lnd");
            List<TaskLayerVO> lndLayerList = agsDrmService.getLayerList(searchVO);
            response.put("lndLayerList", lndLayerList);
            searchVO.setLyrGroupCd("drm");
            searchVO.setLyrSclsfCd("poi");
            List<TaskLayerVO> drmPoiList = agsDrmService.getLayerList(searchVO);
            response.put("poiLayerList", drmPoiList);
            searchVO.setLyrSclsfCd("cnt");
            List<TaskLayerVO> drmCntList = agsDrmService.getLayerList(searchVO);
            response.put("contentLayerList", drmCntList);
            searchVO.setLyrSclsfCd("rsl");
            List<TaskLayerVO> drmRslList = agsDrmService.getLayerList(searchVO);
            response.put("resultLayerList", drmRslList);

            return ResponseEntity.ok(response);

        } catch (DataIntegrityViolationException e) {
            logger.warn("시스템 오류가 발생했습니다: {}", "레이어 목록 조회", e);
            return ResponseEntity.status(HttpStatus.CONFLICT).body(response);

        } catch (NullPointerException e) {
            logger.error("시스템 오류가 발생했습니다: {}", "레이어 목록 조회", e);
            return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).build();

        } catch (DataAccessException e) {
            logger.error("시스템 오류가 발생했습니다: {}", "레이어 목록 조회", e);
            return ResponseEntity.status(HttpStatus.SERVICE_UNAVAILABLE).build();

        } catch (Exception e) {
            logger.error("시스템 오류가 발생했습니다: {}", "레이어 목록 조회", e);
            return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).body(response);
        }
    }

    @GetMapping("/getAddress.do")
    public ResponseEntity<Map<String, Object>> getAddressByPoint (@RequestParam String pnu) {
        Map<String, Object> response = new HashMap<>();
        try {
            List<Map<String, Object>> result = agsDrmService.getAddressByPoint(pnu);
            response.put("result", result);
            response.put("status", "success");
            return ResponseEntity.ok(response);

        } catch (DataIntegrityViolationException e) {
            logger.warn("시스템 오류가 발생했습니다: {}", "위치 정보 조회", e);
            return ResponseEntity.status(HttpStatus.CONFLICT).body(response);

        } catch (NullPointerException e) {
            logger.error("시스템 오류가 발생했습니다: {}", "위치 정보 조회", e);
            return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).build();

        } catch (DataAccessException e) {
            logger.error("시스템 오류가 발생했습니다: {}", "위치 정보 조회", e);
            return ResponseEntity.status(HttpStatus.SERVICE_UNAVAILABLE).build();

        } catch (Exception e) {
            logger.error("시스템 오류가 발생했습니다: {}", "위치 정보 조회", e);
            response.put("status", "error");
            response.put("message", "위치 정보 조회 중 오류가 발생했습니다.");
            return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).body(response);
        }
    }

    /* DRM_POI */
    @GetMapping("/showPOIPop.do")
    @RequirePermission(system = "DRM", permissions = "PERM_MENU_ACCESS")
    public String showPOIPop() throws Exception {
        return "ags/drm/poi/poiManage";
    }
    @GetMapping("/poiPanelPop.do")
    @RequirePermission(system = "DRM", permissions = "PERM_MENU_ACCESS")
    public String showPOIPanelPop(@RequestParam(required = false) Long poiId, ModelMap model) {
        try {
            DrmPoiVO drmPoiVO = new DrmPoiVO();
            drmPoiVO.setPoiId(poiId);
            DrmPoiVO resultVO = agsDrmService.searchPoiDetail(drmPoiVO);
            model.addAttribute("resultVO", resultVO);

        } catch (DataIntegrityViolationException e) {
            logger.warn("시스템 오류가 발생했습니다: {}", "poi 패널 상세 조회", e);
        } catch (NullPointerException | DataAccessException e) {
            logger.error("시스템 오류가 발생했습니다: {}", "poi 패널 상세 조회", e);
        } catch (Exception e) {
            logger.error("시스템 오류가 발생했습니다: {}", "poi 패널 상세 조회", e);
        }

        return "ags/drm/poi/poiPanelDetail";
    }
    @GetMapping("/relPoiList.do")
    @RequirePermission(system = "DRM", permissions = "PERM_FUNC_DRMR")
    public ResponseEntity<Map<String, Object>> searchRelPoiList (@RequestParam Long poiId) {
        Map<String, Object> response = new HashMap<>();
        Map<String, Integer> result;
        try {
            result = agsDrmService.countByRelPoiId(poiId);
            response.put("result", result);
            response.put("status", "success");
            return ResponseEntity.ok(response);

        } catch (DataIntegrityViolationException e) {
            logger.warn("시스템 오류가 발생했습니다: {}", "연관 POI 조회", e);
            response.put("status", "error");
            response.put("message", "연관 POI 조회 중 오류가 발생했습니다");
            return ResponseEntity.status(HttpStatus.CONFLICT).body(response);

        } catch (NullPointerException e) {
            logger.error("시스템 오류가 발생했습니다: {}", "연관 POI 조회", e);
            response.put("status", "error");
            response.put("message", "연관 POI 조회 중 오류가 발생했습니다");
            return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).build();

        } catch (DataAccessException e) {
            logger.error("시스템 오류가 발생했습니다: {}", "연관 POI 조회", e);
            response.put("status", "error");
            response.put("message", "연관 POI 조회 중 오류가 발생했습니다");
            return ResponseEntity.status(HttpStatus.SERVICE_UNAVAILABLE).build();

        } catch (Exception e) {
            logger.error("시스템 오류가 발생했습니다: {}", "연관 POI 조회", e);
            response.put("status", "error");
            response.put("message", "연관 POI 조회 중 오류가 발생했습니다");
            return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).body(response);
        }
    }
    @GetMapping("/relPoiDelete.do")
    @RequirePermission(system = "DRM", permissions = "PERM_FUNC_DRMD")
    public ResponseEntity<Map<String, Object>> deleteRelPoi (@RequestParam Long poiId) {
        Map<String, Object> response = new HashMap<>();
        try {
            agsDrmService.deleteByRelPoiId(poiId);
            response.put("status", "success");
            return ResponseEntity.ok(response);

        } catch (DataIntegrityViolationException e) {
            logger.warn("시스템 오류가 발생했습니다: {}", "연관 POI 삭제", e);
            response.put("status", "error");
            response.put("message", "연관 POI 삭제 중 오류가 발생했습니다");
            return ResponseEntity.status(HttpStatus.CONFLICT).body(response);

        } catch (NullPointerException e) {
            logger.error("시스템 오류가 발생했습니다: {}", "연관 POI 삭제", e);
            response.put("status", "error");
            response.put("message", "연관 POI 삭제 중 오류가 발생했습니다");
            return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).build();

        } catch (DataAccessException e) {
            logger.error("시스템 오류가 발생했습니다: {}", "연관 POI 삭제", e);
            response.put("status", "error");
            response.put("message", "연관 POI 삭제 중 오류가 발생했습니다");
            return ResponseEntity.status(HttpStatus.SERVICE_UNAVAILABLE).build();

        } catch (Exception e) {
            logger.error("시스템 오류가 발생했습니다: {}", "연관 POI 삭제", e);
            response.put("status", "error");
            response.put("message", "연관 POI 삭제 중 오류가 발생했습니다");
            return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).body(response);
        }
    }
    @GetMapping("/poiList.do")
    @RequirePermission(system = "DRM", permissions = "PERM_FUNC_DRMR")
    public ResponseEntity<Map<String, Object>> searchPoiList (
            @RequestParam(defaultValue = "1") int page,
            @RequestParam(required = false) String searchPlcNm,
            @RequestParam(required = false) String searchPoiPbadmsGu,
            @RequestParam(required = false) String sortKey,
            @RequestParam(required = false) String sortDirection
    ) {
        DrmPoiVO drmPoiVO = new DrmPoiVO();
        PaginationInfo paginationInfo = new PaginationInfo();
        Map<String, Object> response = new HashMap<>();
        Map<String, Boolean> permission_func = new HashMap<>();

        try {
            /* CRUD 기능별 권한 */
            permission_func.put("permission_func_create", SecurityUtil.hasPermission("DRM", "PERM_FUNC_DRMC"));
            permission_func.put("permission_func_read", SecurityUtil.hasPermission("DRM", "PERM_FUNC_DRMR"));
            permission_func.put("permission_func_update", SecurityUtil.hasPermission("DRM", "PERM_FUNC_DRMU"));
            permission_func.put("permission_func_delete", SecurityUtil.hasPermission("DRM", "PERM_FUNC_DRMD"));

            drmPoiVO.setPageIndex(page);
            drmPoiVO.setPlcNm(searchPlcNm);
            drmPoiVO.setPbadmsGu(searchPoiPbadmsGu);
            drmPoiVO.setSortKey(sortKey);
            drmPoiVO.setSortDirection(sortDirection);
            drmPoiVO.setPageSize(3);

            paginationInfo.setCurrentPageNo(page);
            paginationInfo.setRecordCountPerPage(drmPoiVO.getRecordCountPerPage());
            paginationInfo.setPageSize(drmPoiVO.getPageSize());

            drmPoiVO.setFirstIndex(paginationInfo.getFirstRecordIndex());
            drmPoiVO.setLastIndex(paginationInfo.getLastRecordIndex());
            drmPoiVO.setRecordCountPerPage(paginationInfo.getRecordCountPerPage());

            List<DrmPoiVO> resultList = agsDrmService.selectPOIList(drmPoiVO);
            int totalCount = agsDrmService.selectPOIListCnt(drmPoiVO);
            paginationInfo.setTotalRecordCount(totalCount);

            response.put("permission_func", permission_func);
            response.put("result", resultList);
            response.put("totalCount", totalCount);
            response.put("paginationInfo", paginationInfo);
            return ResponseEntity.ok(response);

        } catch (DataIntegrityViolationException e) {
            logger.warn("시스템 오류가 발생했습니다: {}", "POI 목록 조회", e);
            return ResponseEntity.status(HttpStatus.CONFLICT).body(response);

        } catch (NullPointerException e) {
            logger.error("시스템 오류가 발생했습니다: {}", "POI 목록 조회", e);
            return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).build();

        } catch (DataAccessException e) {
            logger.error("시스템 오류가 발생했습니다: {}", "POI 목록 조회", e);
            return ResponseEntity.status(HttpStatus.SERVICE_UNAVAILABLE).build();

        } catch (Exception e) {
            logger.error("시스템 오류가 발생했습니다: {}", "POI 목록 조회", e);
            return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).body(response);
        }
    }
    @GetMapping("/poiDetail.do")
    @RequirePermission(system = "DRM", permissions = "PERM_FUNC_DRMR")
    public ResponseEntity<Map<String, Object>> searchPoiDetail (@RequestParam(required = false) Long poiId) {
        DrmPoiVO drmPoiVO = new DrmPoiVO();
        Map<String, Object> response = new HashMap<>();
        try {
            drmPoiVO.setPoiId(poiId);
            DrmPoiVO resultVO = agsDrmService.searchPoiDetail(drmPoiVO);
            response.put("result", resultVO);
            return ResponseEntity.ok(response);

        } catch (DataIntegrityViolationException e) {
            logger.warn("시스템 오류가 발생했습니다: {}", "POI 상세 조회", e);
            return ResponseEntity.status(HttpStatus.CONFLICT).body(response);

        } catch (NullPointerException e) {
            logger.error("시스템 오류가 발생했습니다: {}", "POI 상세 조회", e);
            return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).build();

        } catch (DataAccessException e) {
            logger.error("시스템 오류가 발생했습니다: {}", "POI 상세 조회", e);
            return ResponseEntity.status(HttpStatus.SERVICE_UNAVAILABLE).build();

        } catch (Exception e) {
            logger.error("시스템 오류가 발생했습니다: {}", "POI 상세 조회", e);
            return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).body(response);
        }
    }
    @PostMapping("/poiUpdate.do")
    @RequirePermission(system = "DRM", permissions = "PERM_FUNC_DRMU")
    public ResponseEntity<Map<String, Object>> updatePoi (@RequestBody DrmPoiVO drmPoiVO) {
        Map<String, Object> response = new HashMap<>();
        try {
            agsDrmService.updatePoi(drmPoiVO);
            response.put("success", true);
            return ResponseEntity.ok(response);

        } catch (DataIntegrityViolationException e) {
            logger.warn("시스템 오류가 발생했습니다: {}", "POI 수정", e);
            return ResponseEntity.status(HttpStatus.CONFLICT).body(response);

        } catch (NullPointerException e) {
            logger.error("시스템 오류가 발생했습니다: {}", "POI 수정", e);
            return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).build();

        } catch (DataAccessException e) {
            logger.error("시스템 오류가 발생했습니다: {}", "POI 수정", e);
            return ResponseEntity.status(HttpStatus.SERVICE_UNAVAILABLE).build();

        } catch (Exception e) {
            logger.error("시스템 오류가 발생했습니다: {}", "POI 수정", e);
            return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).body(response);
        }
    }
    @PostMapping("/poiDelete.do")
    @RequirePermission(system = "DRM", permissions = "PERM_FUNC_DRMD")
    public ResponseEntity<Map<String, Object>> deletePOI (@RequestParam(required = false) Long poiId) {
        DrmPoiVO drmPoiVO = new DrmPoiVO();
        Map<String, Object> response = new HashMap<>();
        try {
            drmPoiVO.setPoiId(poiId);
            agsDrmService.deletePOI(drmPoiVO);
            response.put("success", true);
            return ResponseEntity.ok(response);

        } catch (DataIntegrityViolationException e) {
            logger.warn("시스템 오류가 발생했습니다: {}", "POI 삭제", e);
            return ResponseEntity.status(HttpStatus.CONFLICT).body(response);

        } catch (NullPointerException e) {
            logger.error("시스템 오류가 발생했습니다: {}", "POI 삭제", e);
            return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).build();

        } catch (DataAccessException e) {
            logger.error("시스템 오류가 발생했습니다: {}", "POI 삭제", e);
            return ResponseEntity.status(HttpStatus.SERVICE_UNAVAILABLE).build();

        } catch (Exception e) {
            logger.error("시스템 오류가 발생했습니다: {}", "POI 삭제", e);
            return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).body(response);
        }
    }

    @PostMapping("/poiRegisterContent.do")
    @RequirePermission(system = "DRM", permissions = "PERM_FUNC_DRMC")
    public ResponseEntity<Map<String, Object>> registerContent (
        @RequestParam("file") MultipartFile file,
        @RequestParam("drmContentData") String drmContentJson
    ) {
        Map<String, Object> response = new HashMap<>();
        try {
            String decodedJson = HtmlUtils.htmlUnescape(drmContentJson);

            DrmContentVO drmContentVO = objectMapper.readValue(decodedJson, DrmContentVO.class);
            agsDrmService.registerContent(drmContentVO, file);
            response.put("success", true);
            return ResponseEntity.ok(response);

        } catch (DataIntegrityViolationException e) {
            logger.warn("시스템 오류가 발생했습니다: {}", "콘텐츠 등록", e);
            return ResponseEntity.status(HttpStatus.CONFLICT).body(response);

        } catch (NullPointerException e) {
            logger.error("시스템 오류가 발생했습니다: {}", "콘텐츠 등록", e);
            return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).build();

        } catch (DataAccessException e) {
            logger.error("시스템 오류가 발생했습니다: {}", "콘텐츠 등록", e);
            return ResponseEntity.status(HttpStatus.SERVICE_UNAVAILABLE).build();

        } catch (Exception e) {
            logger.error("시스템 오류가 발생했습니다: {}", "콘텐츠 등록", e);
            return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).body(response);
        }
    }
    @PostMapping("/poiRegisterResult.do")
    @RequirePermission(system = "DRM", permissions = "PERM_FUNC_DRMC")
    public ResponseEntity<Map<String, Object>> registerResult (@RequestParam("drmResultData") String drmResultJson) {
        Map<String, Object> response = new HashMap<>();
        try {
            String decodedJson = HtmlUtils.htmlUnescape(drmResultJson);

            DrmResultVO drmResultVO = objectMapper.readValue(decodedJson, DrmResultVO.class);
            agsDrmService.registerResult(drmResultVO);
            response.put("success", true);
            return ResponseEntity.ok(response);

        } catch (DataIntegrityViolationException e) {
            logger.warn("시스템 오류가 발생했습니다: {}", "성과물 등록", e);
            return ResponseEntity.status(HttpStatus.CONFLICT).body(response);

        } catch (NullPointerException e) {
            logger.error("시스템 오류가 발생했습니다: {}", "성과물 등록", e);
            return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).build();

        } catch (DataAccessException e) {
            logger.error("시스템 오류가 발생했습니다: {}", "성과물 등록", e);
            return ResponseEntity.status(HttpStatus.SERVICE_UNAVAILABLE).build();

        } catch (Exception e) {
            logger.error("시스템 오류가 발생했습니다: {}", "성과물 등록", e);
            return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).body(response);
        }
    }
    /**
     * POI 관리 목록 엑셀 다운로드
     */
    @GetMapping("/poiExcelDown")
    public ResponseEntity<Resource> downloadPOIListExcel(
            @RequestParam(required = false) String searchPlcNm,
            @RequestParam(required = false) String searchPoiPbadmsGu,
            @RequestParam(required = false) String searchPoiFrstRegDt
    ) {
        DrmPoiVO drmPoiVO = new DrmPoiVO();
        drmPoiVO.setPlcNm(searchPlcNm);
        drmPoiVO.setPbadmsGu(searchPoiPbadmsGu);
        drmPoiVO.setFrstRegDt(searchPoiFrstRegDt);
        drmPoiVO.setRecordCountPerPage(0);

        try {
            List<DrmPoiVO> voList = agsDrmService.selectPOIList(drmPoiVO);
            List<Map<String, Object>> dataList = convertVOListToMapList(voList);
            if (dataList.isEmpty()) {
                throw new RuntimeException("다운로드할 결과 목록이 없습니다.");
            }

            Map<String, String> columnMap = getColumnMap("POI");
            String excelFileName = "POI_목록";

            return agsDrmService.downLoadExcel(dataList, columnMap, excelFileName);

        }  catch (DataIntegrityViolationException e) {
            logger.warn("시스템 오류가 발생했습니다: {}", "엑셀 다운로드", e);
            throw e;

        } catch (NullPointerException e) {
            logger.error("시스템 오류가 발생했습니다: {}", "엑셀 다운로드", e);
            throw e;

        } catch (DataAccessException e) {
            logger.error("시스템 오류가 발생했습니다: {}", "엑셀 다운로드", e);
            throw e;

        } catch (Exception e) {
            logger.error("시스템 오류가 발생했습니다: {}", "엑셀 다운로드", e);
            throw new RuntimeException("엑셀 다운로드 중 오류가 발생했습니다: ", e);
        }
    }

    /* CONTENT */
    @GetMapping("/showContentPop.do")
    @RequirePermission(system = "DRM", permissions = "PERM_MENU_ACCESS")
    public String showContentPop() throws Exception {
        return "ags/drm/content/contentManage";
    }
    @GetMapping("/contentPanelPop.do")
    @RequirePermission(system = "DRM", permissions = "PERM_MENU_ACCESS")
    public String showcontentPanelPop(@RequestParam(required = false) Long contsId, ModelMap model) {
        try {
            DrmContentVO resultVO = agsDrmService.searchContentDetail(contsId);

            // 파일 시스템 경로 확인
            Path filePath = ((agsDrmServiceImpl) agsDrmService).getFilePath(resultVO.getContsClsf(), resultVO.getContsFileNm());
            if (!Files.exists(filePath)) {
                throw new FileNotFoundException("기존 파일이 존재하지 않습니다: " + filePath);
            }

            // 파일 정보
            long fileSize = Files.size(filePath);
            resultVO.setFileSize(fileSize);
            String fileUrl = "/ags/drm/content/" + contsId;
            resultVO.setFileUrl(fileUrl);
            model.addAttribute("resultVO", resultVO);

        } catch (FileNotFoundException e) {
            logger.error("시스템 오류가 발생했습니다: {}", "콘텐츠 파일 조회", e);

        } catch (DataIntegrityViolationException e) {
            logger.warn("시스템 오류가 발생했습니다: {}", "poi 상세 조회", e);

        } catch (NullPointerException e) {
            logger.error("시스템 오류가 발생했습니다: {}", "poi 상세 조회", e);

        } catch (DataAccessException e) {
            logger.error("시스템 오류가 발생했습니다: {}", "poi 상세 조회", e);

        } catch (Exception e) {
            logger.error("시스템 오류가 발생했습니다: {}", "콘텐츠 패널 상세 조회", e);
        }

        return "ags/drm/content/contentPanelDetail";
    }
    @GetMapping("/contentList.do")
    @RequirePermission(system = "DRM", permissions = "PERM_FUNC_DRMR")
    public ResponseEntity<Map<String, Object>> searchContentList (
            @RequestParam(defaultValue = "1") int page,
            @RequestParam(required = false) String searchContsNm,
            @RequestParam(required = false) String searchContsClsf,
            @RequestParam(required = false) String searchContsInfoRlsAuthrt,
            @RequestParam(required = false) String searchContsShtDtStart,
            @RequestParam(required = false) String searchContsShtDtEnd,
            @RequestParam(required = false) String sortKey,
            @RequestParam(required = false) String sortDirection
    ) {
        DrmContentVO drmContentVO = new DrmContentVO();
        PaginationInfo paginationInfo = new PaginationInfo();
        Map<String, Object> response = new HashMap<>();
        Map<String, Boolean> permission_func = new HashMap<>();

        try {
            /* CRUD 기능별 권한 */
            permission_func.put("permission_func_create", SecurityUtil.hasPermission("DRM", "PERM_FUNC_DRMC"));
            permission_func.put("permission_func_read", SecurityUtil.hasPermission("DRM", "PERM_FUNC_DRMR"));
            permission_func.put("permission_func_update", SecurityUtil.hasPermission("DRM", "PERM_FUNC_DRMU"));
            permission_func.put("permission_func_delete", SecurityUtil.hasPermission("DRM", "PERM_FUNC_DRMD"));

            drmContentVO.setPageIndex(page);
            drmContentVO.setContsNm(searchContsNm);
            drmContentVO.setContsClsf(searchContsClsf);
            drmContentVO.setInfoRlsAuthrt(searchContsInfoRlsAuthrt);
            drmContentVO.setContsShtDtStart(searchContsShtDtStart);
            drmContentVO.setContsShtDtEnd(searchContsShtDtEnd);
            drmContentVO.setSortKey(sortKey);
            drmContentVO.setSortDirection(sortDirection);
            drmContentVO.setPageSize(3);

            /* 정보공개 등급별 데이터 권한 */
            drmContentVO.setPermission_data_unclassified(SecurityUtil.hasPermission("DRM", "PERM_DATA_DRMUN"));
            drmContentVO.setPermission_data_public(SecurityUtil.hasPermission("DRM", "PERM_DATA_DRMPB"));
            drmContentVO.setPermission_data_limit(SecurityUtil.hasPermission("DRM", "PERM_DATA_DRMLM"));
            drmContentVO.setPermission_data_private(SecurityUtil.hasPermission("DRM", "PERM_DATA_DRMPR"));

            paginationInfo.setCurrentPageNo(page);
            paginationInfo.setRecordCountPerPage(drmContentVO.getRecordCountPerPage());
            paginationInfo.setPageSize(drmContentVO.getPageSize());

            drmContentVO.setFirstIndex(paginationInfo.getFirstRecordIndex());
            drmContentVO.setLastIndex(paginationInfo.getLastRecordIndex());
            drmContentVO.setRecordCountPerPage(paginationInfo.getRecordCountPerPage());

            List<DrmContentVO> resultList = agsDrmService.selectContentList(drmContentVO);
            int totalCount = agsDrmService.selectContentListCnt(drmContentVO);
            paginationInfo.setTotalRecordCount(totalCount);

            response.put("permission_func", permission_func);
            response.put("result", resultList);
            response.put("totalCount", totalCount);
            response.put("paginationInfo", paginationInfo);
            return ResponseEntity.ok(response);

        } catch (DataIntegrityViolationException e) {
            logger.warn("시스템 오류가 발생했습니다: {}", "콘텐츠 목록 조회", e);
            response.put("message", "콘텐츠 목록 조회 중 오류 발생");
            return ResponseEntity.status(HttpStatus.CONFLICT).body(response);

        } catch (NullPointerException e) {
            logger.error("시스템 오류가 발생했습니다: {}", "콘텐츠 목록 조회", e);
            response.put("message", "콘텐츠 목록 조회 중 오류 발생");
            return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).build();

        } catch (DataAccessException e) {
            logger.error("시스템 오류가 발생했습니다: {}", "콘텐츠 목록 조회", e);
            response.put("message", "콘텐츠 목록 조회 중 오류 발생");
            return ResponseEntity.status(HttpStatus.SERVICE_UNAVAILABLE).build();

        } catch (Exception e) {
            logger.error("시스템 오류가 발생했습니다: {}", "콘텐츠 목록 조회", e);
            response.put("message", "콘텐츠 목록 조회 중 오류 발생");
            return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).body(response);
        }
    }
    @GetMapping("/contentDetail.do")
    @RequirePermission(system = "DRM", permissions = "PERM_FUNC_DRMR")
    public ResponseEntity<Map<String, Object>> searchContentDetail (@RequestParam(required = false) Long contsId) {
        Map<String, Object> response = new HashMap<>();
        try {
            DrmContentVO resultVO = agsDrmService.searchContentDetail(contsId);

            // 파일 시스템 경로 확인
            Path filePath = ((agsDrmServiceImpl) agsDrmService).getFilePath(resultVO.getContsClsf(), resultVO.getContsFileNm());
            if (!Files.exists(filePath)) {
                throw new FileNotFoundException("기존 파일이 존재하지 않습니다: " + filePath);
            }

            // 파일 정보
            long fileSize = Files.size(filePath);
            resultVO.setFileSize(fileSize);
            String fileUrl = "/ags/drm/content/" + contsId;
            resultVO.setFileUrl(fileUrl);
            response.put("result", resultVO);
            return ResponseEntity.ok(response);

        } catch (FileNotFoundException e) {
            logger.error("시스템 오류가 발생했습니다: {}", "콘텐츠 파일 조회", e);
            response.put("status", "error");
            response.put("message", "해당 콘텐츠 파일이 존재하지 않습니다");
            return ResponseEntity.status(HttpStatus.NOT_FOUND).body(response);

        } catch (DataIntegrityViolationException e) {
            logger.warn("시스템 오류가 발생했습니다: {}", "콘텐츠 상세 조회", e);
            response.put("status", "error");
            response.put("message", "콘텐츠 상세 조회 중 오류가 발생했습니다");
            return ResponseEntity.status(HttpStatus.CONFLICT).body(response);

        } catch (NullPointerException e) {
            logger.error("시스템 오류가 발생했습니다: {}", "콘텐츠 상세 조회", e);
            response.put("status", "error");
            response.put("message", "콘텐츠 상세 조회 중 오류가 발생했습니다");
            return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).build();

        } catch (DataAccessException e) {
            logger.error("시스템 오류가 발생했습니다: {}", "콘텐츠 상세 조회", e);
            response.put("status", "error");
            response.put("message", "콘텐츠 상세 조회 중 오류가 발생했습니다");
            return ResponseEntity.status(HttpStatus.SERVICE_UNAVAILABLE).build();

        } catch (Exception e) {
            logger.error("시스템 오류가 발생했습니다: {}", "콘텐츠 상세 조회", e);
            response.put("status", "error");
            response.put("message", "콘텐츠 상세 조회 중 오류가 발생했습니다");
            return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).body(response);
        }
    }
    // 파일 삭제시 브라우저 잠금 해제를 기다리기
    private void deleteFileWithRetry(Path filePath) throws Exception {
        final int MAX_ATTEMPTS = 5;
        final long SLEEP_MILLIS = 100; // 0.1초 대기

        for (int attempt = 1; attempt <= MAX_ATTEMPTS; attempt++) {
            try {
                if (Files.exists(filePath)) {
                    Files.delete(filePath);
                    return; // 삭제 성공
                } else {
                    return; // 파일이 없으면 성공으로 간주
                }
            } catch (FileSystemException e) {
                // "다른 프로세스가 파일을 사용 중이기 때문에 프로세스가 액세스 할 수 없습니다"
                if (attempt == MAX_ATTEMPTS) {
                    throw e; // 최종 시도 실패 시 예외를 상위로 던집니다.
                }
                // 실패 시 잠시 대기하여 브라우저/OS가 잠금을 풀 시간을 줍니다.
                Thread.sleep(SLEEP_MILLIS);
            }
        }
    }
    @PostMapping("/contentUpdate.do")
    @RequirePermission(system = "DRM", permissions = "PERM_FUNC_DRMU")
    public ResponseEntity<Map<String, Object>> updateContent (
            @ModelAttribute DrmContentVO drmContentVO,
            @RequestParam(value = "newFile", required = false) MultipartFile newFile
    ) {
        Map<String, Object> response = new HashMap<>();
        try {
            // 연관 POI 코드 점검
            long poiId = drmContentVO.getRelPoiId();
            DrmPoiVO drmPoiVO = new DrmPoiVO();
            drmPoiVO.setPoiId(poiId);
            int poiCount = agsDrmService.selectPOIListCnt(drmPoiVO);
            if (poiCount == 0) {
                response.put("status", "error");
                response.put("message", "해당 POI 번호가 없는 번호입니다");
                return ResponseEntity.ok(response);
            }

            // 첨부파일 확인
            DrmContentVO oldContentVO = agsDrmService.searchContentDetail(drmContentVO.getContsId());
            boolean isFileReplaced = (newFile != null && !newFile.isEmpty());
            boolean isClassificationChanged = !Objects.equals(oldContentVO.getContsClsf(), drmContentVO.getContsClsf());

            if (isFileReplaced) {
                // 기존 파일 삭제
                Path oldFilePath = ((agsDrmServiceImpl) agsDrmService).getFilePath(oldContentVO.getContsClsf(), oldContentVO.getContsFileNm());
                if (!Files.exists(oldFilePath)) {
                    throw new FileNotFoundException("File not found or unreadable: " + oldContentVO.getContsFileNm());
                } else {
                    deleteFileWithRetry(oldFilePath);
                }

                // 새로운 파일 이름 생성 및 저장
                String fileName = drmContentVO.getContsId() + "_" + newFile.getOriginalFilename();
                Path newFilePath = ((agsDrmServiceImpl) agsDrmService).getFilePath(drmContentVO.getContsClsf(), fileName);
                if (Files.exists(newFilePath)) {
                    throw new FileAlreadyExistsException("해당 파일명이 이미 존재합니다: " + newFilePath);
                } else {
                    newFile.transferTo(newFilePath.toFile());
                }

                drmContentVO.setContsFileNm(fileName);

            } else if (isClassificationChanged) {
                // 기존 파일 확인
                Path oldFilePath = ((agsDrmServiceImpl) agsDrmService).getFilePath(oldContentVO.getContsClsf(), oldContentVO.getContsFileNm());
                if (!Files.exists(oldFilePath)) {
                    throw new FileNotFoundException("File not found or unreadable: " + oldContentVO.getContsFileNm());
                }

                // 파일 이동 (기존 파일과 경로 및 이름이 겹치는 경우 덮어씀)
                Path newFilePath = ((agsDrmServiceImpl) agsDrmService).getFilePath(drmContentVO.getContsClsf(), drmContentVO.getContsFileNm());
                Files.move(oldFilePath, newFilePath, StandardCopyOption.REPLACE_EXISTING);
            }

            // 콘텐츠 수정
            agsDrmService.updateContent(drmContentVO);
            response.put("success", true);
            return ResponseEntity.ok(response);

        } catch (FileNotFoundException e) {
            // 기존 파일이 없으면 404 Not Found
            logger.error("시스템 오류가 발생했습니다: {}", "기존 파일 삭제", e);
            response.put("status", "error");
            response.put("message", "기존 파일 삭제에 실패했습니다");
            return ResponseEntity.status(HttpStatus.NOT_FOUND).body(response);
        } catch (FileAlreadyExistsException e) {
            // 파일 경로 및 이름 중복
            logger.error("시스템 오류가 발생했습니다: {}", "첨부파일 중복", e);
            response.put("status", "error");
            response.put("message", "해당 첨부파일이 중복입니다");
            return ResponseEntity.status(HttpStatus.CONFLICT).body(response);
        } catch (Exception e) {
            logger.error("시스템 오류가 발생했습니다: {}", "콘텐츠 수정", e);
            response.put("status", "error");
            response.put("message", "콘텐츠 수정 중 오류가 발생했습니다");
            return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).body(response);
        }
    }
    @PostMapping("/contentDelete.do")
    @RequirePermission(system = "DRM", permissions = "PERM_FUNC_DRMD")
    public ResponseEntity<Map<String, Object>> deleteContent (@RequestParam(required = false) Long contsId) {
        Map<String, Object> response = new HashMap<>();
        try {
            response = agsDrmService.deleteContent(contsId);
            response.put("success", true);
            return ResponseEntity.ok(response);

        } catch (DataIntegrityViolationException e) {
            logger.warn("시스템 오류가 발생했습니다: {}", "콘텐츠 삭제", e);
            return ResponseEntity.status(HttpStatus.CONFLICT).body(response);

        } catch (NullPointerException e) {
            logger.error("시스템 오류가 발생했습니다: {}", "콘텐츠 삭제", e);
            return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).build();

        } catch (DataAccessException e) {
            logger.error("시스템 오류가 발생했습니다: {}", "콘텐츠 삭제", e);
            return ResponseEntity.status(HttpStatus.SERVICE_UNAVAILABLE).build();

        } catch (Exception e) {
            logger.error("시스템 오류가 발생했습니다: {}", "콘텐츠 삭제", e);
            response.put("message", "콘텐츠 삭제 중 오류 발생");
            return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).body(response);
        }
    }
    /**
     * Content 관리 목록 엑셀 다운로드
     */
    @GetMapping("/contentExcelDown")
    public ResponseEntity<Resource> downloadContentListExcel(
            @RequestParam(required = false) String searchContsNm,
            @RequestParam(required = false) Long searchRelPoiId,
            @RequestParam(required = false) String searchContsClsf,
            @RequestParam(required = false) String searchContsInfoRlsAuthrt,
            @RequestParam(required = false) String searchContsShtDtStart,
            @RequestParam(required = false) String searchContsShtDtEnd
    ) {
        DrmContentVO drmContentVO = new DrmContentVO();
        drmContentVO.setContsNm(searchContsNm);
        drmContentVO.setRelPoiId(searchRelPoiId);
        drmContentVO.setContsClsf(searchContsClsf);
        drmContentVO.setInfoRlsAuthrt(searchContsInfoRlsAuthrt);
        drmContentVO.setFrstRegDtStart(searchContsShtDtStart);
        drmContentVO.setContsShtDtEnd(searchContsShtDtEnd);
        drmContentVO.setRecordCountPerPage(0);

        try {
            List<DrmContentVO> voList = agsDrmService.selectContentList(drmContentVO);
            List<Map<String, Object>> dataList = convertVOListToMapList(voList);
            if (dataList.isEmpty()) {
                throw new RuntimeException("다운로드할 결과 목록이 없습니다.");
            }

            Map<String, String> columnMap = getColumnMap("CONTENT");
            String excelFileName = "콘텐츠_목록";

            return agsDrmService.downLoadExcel(dataList, columnMap, excelFileName);

        } catch (DataIntegrityViolationException e) {
            logger.warn("시스템 오류가 발생했습니다: {}", "엑셀 다운로드", e);
            throw e;

        } catch (NullPointerException e) {
            logger.error("시스템 오류가 발생했습니다: {}", "엑셀 다운로드", e);
            throw e;

        } catch (DataAccessException e) {
            logger.error("시스템 오류가 발생했습니다: {}", "엑셀 다운로드", e);
            throw e;

        } catch (Exception e) {
            throw new RuntimeException("엑셀 다운로드 중 오류가 발생했습니다: ", e);
        }
    }

    /* RESULT */
    @GetMapping("/showResultPop.do")
    @RequirePermission(system = "DRM", permissions = "PERM_MENU_ACCESS")
    public String showResultPop() throws Exception {
        return "ags/drm/result/resultManage";
    }
    @GetMapping("/resultPanelPop.do")
    @RequirePermission(system = "DRM", permissions = "PERM_MENU_ACCESS")
    public String showresultPanelPop(@RequestParam(required = false) Long rsulId, ModelMap model) {
        try {
            DrmResultVO resultVO = agsDrmService.searchResultDetail(rsulId);
            model.addAttribute("resultVO", resultVO);

        } catch (DataIntegrityViolationException e) {
            logger.warn("시스템 오류가 발생했습니다: {}", "성과물 패널 상세 조회", e);

        } catch (NullPointerException e) {
            logger.error("시스템 오류가 발생했습니다: {}", "성과물 패널 상세 조회", e);

        } catch (DataAccessException e) {
            logger.error("시스템 오류가 발생했습니다: {}", "성과물 패널 상세 조회", e);

        } catch (Exception e) {
            logger.error("시스템 오류가 발생했습니다: {}", "성과물 패널 상세 조회", e);
        }
        return "ags/drm/result/resultPanelDetail";
    }
    @GetMapping("/resultList.do")
    @RequirePermission(system = "DRM", permissions = "PERM_FUNC_DRMR")
    public ResponseEntity<Map<String, Object>> searchResultList (
            @RequestParam(defaultValue = "1") int page,
            @RequestParam(required = false) String searchRsulTtl,
            @RequestParam(required = false) String searchTkcgPbadmsGu,
            @RequestParam(required = false) String searchRslInfoRlsAuthrt,
            @RequestParam(required = false) String searchRsulShtDay,
            @RequestParam(required = false) String searchFrstRegDtStart,
            @RequestParam(required = false) String searchFrstRegDtEnd,
            @RequestParam(required = false) String sortKey,
            @RequestParam(required = false) String sortDirection
    ) {
        DrmResultVO drmResultVO = new DrmResultVO();
        PaginationInfo paginationInfo = new PaginationInfo();
        Map<String, Object> response = new HashMap<>();
        Map<String, Boolean> permission_func = new HashMap<>();

        try {
            /* CRUD 기능별 권한 */
            permission_func.put("permission_func_create", SecurityUtil.hasPermission("DRM", "PERM_FUNC_DRMC"));
            permission_func.put("permission_func_read", SecurityUtil.hasPermission("DRM", "PERM_FUNC_DRMR"));
            permission_func.put("permission_func_update", SecurityUtil.hasPermission("DRM", "PERM_FUNC_DRMU"));
            permission_func.put("permission_func_delete", SecurityUtil.hasPermission("DRM", "PERM_FUNC_DRMD"));

            drmResultVO.setPageIndex(page);
            drmResultVO.setRsulTtl(searchRsulTtl);
            drmResultVO.setTkcgPbadmsGu(searchTkcgPbadmsGu);
            drmResultVO.setInfoRlsAuthrt(searchRslInfoRlsAuthrt);
            drmResultVO.setRsulShtDay(searchRsulShtDay);
            drmResultVO.setFrstRegDtStart(searchFrstRegDtStart);
            drmResultVO.setFrstRegDtEnd(searchFrstRegDtEnd);
            drmResultVO.setSortKey(sortKey);
            drmResultVO.setSortDirection(sortDirection);
            drmResultVO.setPageSize(3);

            /* 정보공개 등급별 데이터 권한 */
            drmResultVO.setPermission_data_unclassified(SecurityUtil.hasPermission("DRM", "PERM_DATA_DRMUN"));
            drmResultVO.setPermission_data_public(SecurityUtil.hasPermission("DRM", "PERM_DATA_DRMPB"));
            drmResultVO.setPermission_data_limit(SecurityUtil.hasPermission("DRM", "PERM_DATA_DRMLM"));
            drmResultVO.setPermission_data_private(SecurityUtil.hasPermission("DRM", "PERM_DATA_DRMPR"));

            paginationInfo.setCurrentPageNo(page);
            paginationInfo.setRecordCountPerPage(drmResultVO.getRecordCountPerPage());
            paginationInfo.setPageSize(drmResultVO.getPageSize());

            drmResultVO.setFirstIndex(paginationInfo.getFirstRecordIndex());
            drmResultVO.setLastIndex(paginationInfo.getLastRecordIndex());
            drmResultVO.setRecordCountPerPage(paginationInfo.getRecordCountPerPage());

            List<DrmResultVO> resultList = agsDrmService.selectResultList(drmResultVO);
            int totalCount = agsDrmService.selectResultListCnt(drmResultVO);
            paginationInfo.setTotalRecordCount(totalCount);

            response.put("permission_func", permission_func);
            response.put("result", resultList);
            response.put("totalCount", totalCount);
            response.put("paginationInfo", paginationInfo);
            return ResponseEntity.ok(response);

        } catch (DataIntegrityViolationException e) {
            logger.warn("시스템 오류가 발생했습니다: {}", "성과물 목록 조회", e);
            return ResponseEntity.status(HttpStatus.CONFLICT).body(response);

        } catch (NullPointerException e) {
            logger.error("시스템 오류가 발생했습니다: {}", "성과물 목록 조회", e);
            return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).build();

        } catch (DataAccessException e) {
            logger.error("시스템 오류가 발생했습니다: {}", "성과물 목록 조회", e);
            return ResponseEntity.status(HttpStatus.SERVICE_UNAVAILABLE).build();

        } catch (Exception e) {
            logger.error("시스템 오류가 발생했습니다: {}", "성과물 목록 조회", e);
            return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).body(response);
        }
    }
    @GetMapping("/resultDetail.do")
    @RequirePermission(system = "DRM", permissions = "PERM_FUNC_DRMR")
    public ResponseEntity<Map<String, Object>> searchResultDetail (@RequestParam(required = false) Long rsulId) {
        Map<String, Object> response = new HashMap<>();
        try {
            DrmResultVO resultVO = agsDrmService.searchResultDetail(rsulId);
            response.put("result", resultVO);
            return ResponseEntity.ok(response);

        } catch (DataIntegrityViolationException e) {
            logger.warn("시스템 오류가 발생했습니다: {}", "성과물 상세 조회", e);
            return ResponseEntity.status(HttpStatus.CONFLICT).body(response);

        } catch (NullPointerException e) {
            logger.error("시스템 오류가 발생했습니다: {}", "성과물 상세 조회", e);
            return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).build();

        } catch (DataAccessException e) {
            logger.error("시스템 오류가 발생했습니다: {}", "성과물 상세 조회", e);
            return ResponseEntity.status(HttpStatus.SERVICE_UNAVAILABLE).build();

        } catch (Exception e) {
            logger.error("시스템 오류가 발생했습니다: {}", "성과물 상세 조회", e);
            return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).body(response);
        }
    }
    @PostMapping("/resultUpdate.do")
    @RequirePermission(system = "DRM", permissions = "PERM_FUNC_DRMU")
    public ResponseEntity<Map<String, Object>> updateResult (@RequestBody DrmResultVO drmResultVO) {
        Map<String, Object> response = new HashMap<>();
        try {
            agsDrmService.updateResult(drmResultVO);
            response.put("success", true);
            return ResponseEntity.ok(response);

        } catch (DataIntegrityViolationException e) {
            logger.warn("시스템 오류가 발생했습니다: {}", "성과물 수정", e);
            return ResponseEntity.status(HttpStatus.CONFLICT).body(response);

        } catch (NullPointerException e) {
            logger.error("시스템 오류가 발생했습니다: {}", "성과물 수정", e);
            return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).build();

        } catch (DataAccessException e) {
            logger.error("시스템 오류가 발생했습니다: {}", "성과물 수정", e);
            return ResponseEntity.status(HttpStatus.SERVICE_UNAVAILABLE).build();

        } catch (Exception e) {
            logger.error("시스템 오류가 발생했습니다: {}", "성과물 수정", e);
            return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).body(response);
        }
    }
    @PostMapping("/resultDelete.do")
    @RequirePermission(system = "DRM", permissions = "PERM_FUNC_DRMD")
    public ResponseEntity<Map<String, Object>> deleteResult (@RequestParam(required = false) Long rsulId) {
        Map<String, Object> response = new HashMap<>();
        try {
            agsDrmService.deleteResult(rsulId);
            response.put("success", true);
            return ResponseEntity.ok(response);

        } catch (DataIntegrityViolationException e) {
            logger.warn("시스템 오류가 발생했습니다: {}", "성과물 삭제", e);
            return ResponseEntity.status(HttpStatus.CONFLICT).body(response);

        } catch (NullPointerException e) {
            logger.error("시스템 오류가 발생했습니다: {}", "성과물 삭제", e);
            return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).build();

        } catch (DataAccessException e) {
            logger.error("시스템 오류가 발생했습니다: {}", "성과물 삭제", e);
            return ResponseEntity.status(HttpStatus.SERVICE_UNAVAILABLE).build();

        } catch (Exception e) {
            logger.error("시스템 오류가 발생했습니다: {}", "성과물 삭제", e);
            return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).body(response);
        }
    }
    /**
     * Result 관리 목록 엑셀 다운로드
     */
    @GetMapping("/resultExcelDown")
    public ResponseEntity<Resource> downloadResultListExcel(
            @RequestParam(required = false) String searchRsulTtl,
            @RequestParam(required = false) String searchTkcgPbadmsGu,
            @RequestParam(required = false) String searchRsulShtDay
    ) {
        DrmResultVO drmResultVO = new DrmResultVO();
        drmResultVO.setRsulTtl(searchRsulTtl);
        drmResultVO.setTkcgPbadmsGu(searchTkcgPbadmsGu);
        drmResultVO.setRsulShtDay(searchRsulShtDay);
        drmResultVO.setRecordCountPerPage(0);

        try {
            List<DrmResultVO> voList = agsDrmService.selectResultList(drmResultVO);
            List<Map<String, Object>> dataList = convertVOListToMapList(voList);
            if (dataList.isEmpty()) {
                throw new RuntimeException("다운로드할 결과 목록이 없습니다.");
            }

            Map<String, String> columnMap = getColumnMap("RESULT");
            String excelFileName = "성과물_목록";

            return agsDrmService.downLoadExcel(dataList, columnMap, excelFileName);

        } catch (DataIntegrityViolationException e) {
            logger.warn("시스템 오류가 발생했습니다: {}", "엑셀 다운로드", e);
            throw e;

        } catch (NullPointerException e) {
            logger.error("시스템 오류가 발생했습니다: {}", "엑셀 다운로드", e);
            throw e;

        } catch (DataAccessException e) {
            logger.error("시스템 오류가 발생했습니다: {}", "엑셀 다운로드", e);
            throw e;

        } catch (Exception e) {
            throw new RuntimeException("엑셀 다운로드 중 오류가 발생했습니다: ", e);
        }
    }

    /*ORTHOPHOTO*/
    @GetMapping("/showOrthophotoPop.do")
    @RequirePermission(system = "DRM", permissions = "PERM_MENU_ACCESS")
    public String showOrthophotoPop() throws Exception {
        return "ags/drm/orthophoto/orthoManage";
    }

    @Autowired private RestTemplate restTemplate;

	@GetMapping("/orthoPanelPop.do")
    @RequirePermission(system = "DRM", permissions = "PERM_MENU_ACCESS")
    public String showorthoPanelPop(@RequestParam(required = false) Long pltafsVideoId, ModelMap model) {
        try {
            DrmOrthoVO resultVO = agsDrmService.searchOrthoDetail(pltafsVideoId);
            model.addAttribute("resultVO", resultVO);
            // extent 조회
            String getLayerInfoUrl = String.format("%s/workspace/%s/layer/%s", BASE_URL + "/rest", resultVO.getLyrSrvcJobSpceNm(), resultVO.getLyrSrvcNm());
            ResponseEntity<String> layerInfoResponse = restTemplate.getForEntity(getLayerInfoUrl, String.class);
            Map<String, Object> layerInfoMap = objectMapper.readValue(layerInfoResponse.getBody(), Map.class);

            if (layerInfoMap.containsKey("result")) {
                Map<String, Object> detailResult = (Map<String, Object>) layerInfoMap.get("result");

                if (detailResult.containsKey("minx")) {
                    Double minX = (Double) detailResult.get("minx");
                    Double minY = (Double) detailResult.get("miny");
                    Double maxX = (Double) detailResult.get("maxx");
                    Double maxY = (Double) detailResult.get("maxy");

                    model.addAttribute("minX", minX);
                    model.addAttribute("minY", minY);
                    model.addAttribute("maxX", maxX);
                    model.addAttribute("maxY", maxY);
                    model.addAttribute("srid", detailResult.get("srid"));
                }
            }

        } catch (DataIntegrityViolationException e) {
            logger.warn("시스템 오류가 발생했습니다: {}", "정사영상 패널 상세 조회", e);

        } catch (NullPointerException e) {
            logger.error("시스템 오류가 발생했습니다: {}", "정사영상 패널 상세 조회", e);

        } catch (DataAccessException e) {
            logger.error("시스템 오류가 발생했습니다: {}", "정사영상 패널 상세 조회", e);

        } catch (Exception e) {
            logger.error("시스템 오류가 발생했습니다: {}", "정사영상 패널 상세 조회", e);
        }
        return "ags/drm/orthophoto/orthoPanelDetail";
    }
    @GetMapping("/orthoList.do")
    @RequirePermission(system = "DRM", permissions = "PERM_FUNC_DRMR")
    public ResponseEntity<Map<String, Object>> searchOrthoList (
            @RequestParam(defaultValue = "1") int page,
            @RequestParam(required = false) String searchPltafsVideoTtl,
            @RequestParam(required = false) String searchTkcgPbadmsGu,
            @RequestParam(required = false) String searchLyrSrvcNm,
            @RequestParam(required = false) String searchOrthoInfoRlsAuthrt,
            @RequestParam(required = false) String sortKey,
            @RequestParam(required = false) String sortDirection
    ) {
        DrmOrthoVO drmOrthoVO = new DrmOrthoVO();
        PaginationInfo paginationInfo = new PaginationInfo();
        Map<String, Object> response = new HashMap<>();
        Map<String, Boolean> permission_func = new HashMap<>();

        try {
            /* CRUD 기능별 권한 */
            permission_func.put("permission_func_create", SecurityUtil.hasPermission("DRM", "PERM_FUNC_DRMC"));
            permission_func.put("permission_func_read", SecurityUtil.hasPermission("DRM", "PERM_FUNC_DRMR"));
            permission_func.put("permission_func_update", SecurityUtil.hasPermission("DRM", "PERM_FUNC_DRMU"));
            permission_func.put("permission_func_delete", SecurityUtil.hasPermission("DRM", "PERM_FUNC_DRMD"));

            drmOrthoVO.setPageIndex(page);
            drmOrthoVO.setPltafsVideoTtl(searchPltafsVideoTtl);
            drmOrthoVO.setTkcgPbadmsGu(searchTkcgPbadmsGu);
            drmOrthoVO.setLyrSrvcNm(searchLyrSrvcNm);
            drmOrthoVO.setInfoRlsAuthrt(searchOrthoInfoRlsAuthrt);
            drmOrthoVO.setSortKey(sortKey);
            drmOrthoVO.setSortDirection(sortDirection);
            drmOrthoVO.setPageSize(3);

            /* 정보공개 등급별 데이터 권한 */
            drmOrthoVO.setPermission_data_unclassified(SecurityUtil.hasPermission("DRM", "PERM_DATA_DRMUN"));
            drmOrthoVO.setPermission_data_public(SecurityUtil.hasPermission("DRM", "PERM_DATA_DRMPB"));
            drmOrthoVO.setPermission_data_limit(SecurityUtil.hasPermission("DRM", "PERM_DATA_DRMLM"));
            drmOrthoVO.setPermission_data_private(SecurityUtil.hasPermission("DRM", "PERM_DATA_DRMPR"));

            paginationInfo.setCurrentPageNo(page);
            paginationInfo.setRecordCountPerPage(drmOrthoVO.getRecordCountPerPage());
            paginationInfo.setPageSize(drmOrthoVO.getPageSize());

            drmOrthoVO.setFirstIndex(paginationInfo.getFirstRecordIndex());
            drmOrthoVO.setLastIndex(paginationInfo.getLastRecordIndex());
            drmOrthoVO.setRecordCountPerPage(paginationInfo.getRecordCountPerPage());

            List<DrmOrthoVO> resultList = agsDrmService.selectOrthoList(drmOrthoVO);
            int totalCount = agsDrmService.selectOrthoListCnt(drmOrthoVO);
            paginationInfo.setTotalRecordCount(totalCount);

            response.put("permission_func", permission_func);
            response.put("result", resultList);
            response.put("totalCount", totalCount);
            response.put("paginationInfo", paginationInfo);
            return ResponseEntity.ok(response);

        } catch (DataIntegrityViolationException e) {
            logger.warn("시스템 오류가 발생했습니다: {}", "정사영상 목록 조회", e);
            return ResponseEntity.status(HttpStatus.CONFLICT).body(response);

        } catch (NullPointerException e) {
            logger.error("시스템 오류가 발생했습니다: {}", "정사영상 목록 조회", e);
            return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).build();

        } catch (DataAccessException e) {
            logger.error("시스템 오류가 발생했습니다: {}", "정사영상 목록 조회", e);
            return ResponseEntity.status(HttpStatus.SERVICE_UNAVAILABLE).build();

        } catch (Exception e) {
            logger.error("시스템 오류가 발생했습니다: {}", "정사영상 목록 조회", e);
            return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).body(response);
        }
    }
    @PostMapping("/orthoRegister.do")
    @RequirePermission(system = "DRM", permissions = "PERM_FUNC_DRMC")
    public ResponseEntity<Map<String, Object>> registerOrtho (@RequestBody DrmOrthoVO drmOrthoVO) {
        Map<String, Object> response = new HashMap<>();
        String errorMessage = null;
        try {
            // 작업공간 조회
            String checkWorkspace = String.format("%s/workspace/%s", BASE_URL + "/rest", drmOrthoVO.getLyrSrvcJobSpceNm());
            String checkResponse = checkServerApi(checkWorkspace, "작업공간 조회", "GET", drmOrthoVO);
            if (checkResponse.contains("error")) {
                errorMessage = "작업공간 조회 중 오류가 발생했습니다.";
            }

            if (errorMessage == null) {
                // 저장소 추가
                String pushStorageUrl = String.format("%s/storage", BASE_URL + "/rest");
                String pushResponse = checkServerApi(pushStorageUrl, "저장소 추가", "POST", drmOrthoVO);

                if (pushResponse.contains("error")) {
                    errorMessage = "저장소 추가 중 오류가 발생했습니다.";
                }
            }

            if (errorMessage == null) {
                // 등록한 저장소에서 발행가능한 레이어 조회
                String layerPublishResult = checkLayerAndPublish(drmOrthoVO);

                if (layerPublishResult.contains("error")) {
                    errorMessage = "레이어 조회/발행 중 오류가 발생했습니다.";
                }
            }

            if (errorMessage == null) {
                // DB 등록
                agsDrmService.registerOrtho(drmOrthoVO);
                response.put("success", true);
                response.put("message", "정사영상 레이어 추가 및 DB 데이터 등록 성공");

            } else {
                response.put("success", false);
                response.put("message", errorMessage);
            }

            return ResponseEntity.ok(response);

        } catch (DataIntegrityViolationException e) {
            logger.warn("시스템 오류가 발생했습니다: {}", "정사영상 등록", e);
            response.put("success", false);
            response.put("message", "정사영상 등록 중 오류가 발생했습니다.");
            return ResponseEntity.status(HttpStatus.CONFLICT).body(response);

        } catch (NullPointerException e) {
            logger.error("시스템 오류가 발생했습니다: {}", "정사영상 등록", e);
            response.put("success", false);
            response.put("message", "정사영상 등록 중 오류가 발생했습니다.");
            return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).build();

        } catch (DataAccessException e) {
            logger.error("시스템 오류가 발생했습니다: {}", "정사영상 등록", e);
            response.put("success", false);
            response.put("message", "정사영상 등록 중 오류가 발생했습니다.");
            return ResponseEntity.status(HttpStatus.SERVICE_UNAVAILABLE).build();

        } catch (Exception e) {
            // API 호출 외의 예상치 못한 내부 서버 오류
            logger.error("시스템 오류가 발생했습니다: {}", "정사영상 등록", e);
            response.put("success", false);
            response.put("message", "정사영상 등록 중 오류가 발생했습니다.");

            return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).body(response);
        }
    }
    @GetMapping("/orthoDetail.do")
    @RequirePermission(system = "DRM", permissions = "PERM_FUNC_DRMR")
    public ResponseEntity<Map<String, Object>> searchOrthoDetail (@RequestParam(required = false) Long pltafsVideoId) {
        Map<String, Object> response = new HashMap<>();
        try {
            DrmOrthoVO resultVO = agsDrmService.searchOrthoDetail(pltafsVideoId);
            response.put("result", resultVO);

            // extent 조회
            String getLayerInfoUrl = String.format("%s/workspace/%s/layer/%s", BASE_URL + "/rest", resultVO.getLyrSrvcJobSpceNm(), resultVO.getLyrSrvcNm());
            ResponseEntity<String> layerInfoResponse = restTemplate.getForEntity(getLayerInfoUrl, String.class);
            Map<String, Object> layerInfoMap = objectMapper.readValue(layerInfoResponse.getBody(), Map.class);

            if (layerInfoMap.containsKey("result")) {
                Map<String, Object> detailResult = (Map<String, Object>) layerInfoMap.get("result");

                if (detailResult.containsKey("minx")) {
                    Double minX = (Double) detailResult.get("minx");
                    Double minY = (Double) detailResult.get("miny");
                    Double maxX = (Double) detailResult.get("maxx");
                    Double maxY = (Double) detailResult.get("maxy");

                    double[] extentArray = {minX, minY, maxX, maxY};

                    response.put("layerExtent", extentArray);
                    response.put("srid", detailResult.get("srid"));
                }
            }

            return ResponseEntity.ok(response);

        } catch (DataIntegrityViolationException e) {
            logger.warn("시스템 오류가 발생했습니다: {}", "정사영상 상세 조회", e);
            return ResponseEntity.status(HttpStatus.CONFLICT).body(response);

        } catch (NullPointerException e) {
            logger.error("시스템 오류가 발생했습니다: {}", "정사영상 상세 조회", e);
            return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).build();

        } catch (DataAccessException e) {
            logger.error("시스템 오류가 발생했습니다: {}", "정사영상 상세 조회", e);
            return ResponseEntity.status(HttpStatus.SERVICE_UNAVAILABLE).build();

        } catch (Exception e) {
            logger.error("시스템 오류가 발생했습니다: {}", "정사영상 상세 조회", e);
            return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).body(response);
        }
    }
    @PostMapping("/orthoUpdate.do")
    @RequirePermission(system = "DRM", permissions = "PERM_FUNC_DRMU")
    public ResponseEntity<Map<String, Object>> updateOrtho (@RequestBody DrmOrthoVO drmOrthoVO) {
        Map<String, Object> response = new HashMap<>();
        try {
            agsDrmService.updateOrtho(drmOrthoVO);
            response.put("success", true);
            return ResponseEntity.ok(response);

        } catch (DataIntegrityViolationException e) {
            logger.warn("시스템 오류가 발생했습니다: {}", "정사영상 수정", e);
            return ResponseEntity.status(HttpStatus.CONFLICT).body(response);

        } catch (NullPointerException e) {
            logger.error("시스템 오류가 발생했습니다: {}", "정사영상 수정", e);
            return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).build();

        } catch (DataAccessException e) {
            logger.error("시스템 오류가 발생했습니다: {}", "정사영상 수정", e);
            return ResponseEntity.status(HttpStatus.SERVICE_UNAVAILABLE).build();

        } catch (Exception e) {
            logger.error("시스템 오류가 발생했습니다: {}", "정사영상 수정", e);
            return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).body(response);
        }
    }
    @PostMapping("/orthoDelete.do")
    @RequirePermission(system = "DRM", permissions = "PERM_FUNC_DRMD")
    public ResponseEntity<Map<String, Object>> deleteOrtho (@RequestParam(required = false) Long pltafsVideoId) {
        Map<String, Object> response = new HashMap<>();
        try {
            DrmOrthoVO drmOrthoVO = agsDrmService.searchOrthoDetail(pltafsVideoId);

            String layerUrl = String.format("%s/workspace/%s/layer/%s", BASE_URL + "/rest", drmOrthoVO.getLyrSrvcJobSpceNm(), drmOrthoVO.getLyrSrvcNm());
            restTemplate.delete(layerUrl);

            String storageUrl = String.format("%s/workspace/%s/storage/%s", BASE_URL + "/rest", drmOrthoVO.getLyrSrvcJobSpceNm(), drmOrthoVO.getLyrSrvcStorgeNm());
            restTemplate.delete(storageUrl);

            agsDrmService.deleteOrtho(pltafsVideoId);
            response.put("success", true);
            return ResponseEntity.ok(response);

        } catch (DataIntegrityViolationException e) {
            logger.warn("시스템 오류가 발생했습니다: {}", "정사영상 삭제", e);
            return ResponseEntity.status(HttpStatus.CONFLICT).body(response);

        } catch (NullPointerException e) {
            logger.error("시스템 오류가 발생했습니다: {}", "정사영상 삭제", e);
            return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).build();

        } catch (DataAccessException e) {
            logger.error("시스템 오류가 발생했습니다: {}", "정사영상 삭제", e);
            return ResponseEntity.status(HttpStatus.SERVICE_UNAVAILABLE).build();

        } catch (Exception e) {
            logger.error("시스템 오류가 발생했습니다: {}", "정사영상 삭제", e);
            return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).body(response);
        }
    }
    /**
     * Ortho 관리 목록 엑셀 다운로드
     */
    @GetMapping("/orthoExcelDown")
    public ResponseEntity<Resource> downloadOrthoListExcel(
            @RequestParam(required = false) String searchPltafsVideoTtl,
            @RequestParam(required = false) String searchTkcgPbadmsGu,
            @RequestParam(required = false) String searchLyrSrvcNm
    ) {
        DrmOrthoVO drmOrthoVO = new DrmOrthoVO();
        drmOrthoVO.setPltafsVideoTtl(searchPltafsVideoTtl);
        drmOrthoVO.setTkcgPbadmsGu(searchTkcgPbadmsGu);
        drmOrthoVO.setLyrSrvcNm(searchLyrSrvcNm);
        drmOrthoVO.setRecordCountPerPage(0);

        try {
            List<DrmOrthoVO> voList = agsDrmService.selectOrthoList(drmOrthoVO);
            List<Map<String, Object>> dataList = convertVOListToMapList(voList);
            if (dataList.isEmpty()) {
                throw new RuntimeException("다운로드할 결과 목록이 없습니다.");
            }

            Map<String, String> columnMap = getColumnMap("ORTHO");
            String excelFileName = "정사영상_목록";

            return agsDrmService.downLoadExcel(dataList, columnMap, excelFileName);

        } catch (DataIntegrityViolationException e) {
            logger.warn("시스템 오류가 발생했습니다: {}", "엑셀 다운로드", e);
            throw e;

        } catch (NullPointerException e) {
            logger.error("시스템 오류가 발생했습니다: {}", "엑셀 다운로드", e);
            throw e;

        } catch (DataAccessException e) {
            logger.error("시스템 오류가 발생했습니다: {}", "엑셀 다운로드", e);
            throw e;

        } catch (Exception e) {
            throw new RuntimeException("엑셀 다운로드 중 오류가 발생했습니다: ", e);
        }
    }


    /**
     * VOList를 List<Map<String, Object>>로 변환
     * @param voList 엑셀다운로드할 결과 목록
     */
    private List<Map<String, Object>> convertVOListToMapList(List<?> voList) {
        if (voList == null || voList.isEmpty()) {
            return Collections.emptyList();
        }

        return voList.stream()
                .map(vo -> objectMapper.convertValue(vo, new TypeReference<Map<String, Object>>() {}))
                .collect(Collectors.toList());
    }

    /**
     * 컬럼명 한글 변환
     * @param type 목록 메뉴명
     */
    private Map<String, String> getColumnMap(String type) {
        Map<String, String> columnMap = new LinkedHashMap<>();
        switch (type) {
            case "POI":
                columnMap.put("plcNm", "명칭");
                columnMap.put("poiId", "식별 번호");
                columnMap.put("pbadmsGu", "담당 행정기관");
                columnMap.put("lotnoAddr", "지번 주소");
                columnMap.put("roadNmAddr", "도로명 주소");
                columnMap.put("rmrk", "비고");
                break;
            case "CONTENT":
                columnMap.put("contsNm", "제목");
                columnMap.put("contsId", "식별 번호");
                columnMap.put("contsClsf", "분류");
                columnMap.put("infoRlsAuthrt", "정보 공개 등급");
                columnMap.put("contsFileNm", "파일명");
                columnMap.put("contsShtDay", "촬영일자");
                columnMap.put("tkcgPbadmsGu", "담당 행정기관");
                columnMap.put("rmrk", "비고");
                break;
            case "RESULT":
                columnMap.put("rsulTtl", "제목");
                columnMap.put("rsulId", "식별 번호");
                columnMap.put("rsulClsf", "분류");
                columnMap.put("rsulRqstInst", "요청 기관");
                columnMap.put("infoRlsAuthrt", "정보 공개 등급");
                columnMap.put("rsulShtCo", "제작 회사");
                columnMap.put("rsulShtDay", "촬영일자");
                columnMap.put("tkcgPbadmsGu", "담당 행정기관");
                columnMap.put("rmrk", "비고");
                break;
            case "ORTHO":
                columnMap.put("pltafsVideoTtl", "제목");
                columnMap.put("pltafsVideoId", "식별 번호");
                columnMap.put("lyrSrvcNm", "레이어 서비스명");
                columnMap.put("infoRlsAuthrt", "정보 공개 등급");
                columnMap.put("tkcgPbadmsGu", "담당 행정기관");
                columnMap.put("rmrk", "비고");
                break;
        }
        return columnMap;
    }

    /**
     * 편집 레이어 속성조회
     * @param layerName 조회할 레이어명
     */
    @PostMapping("/getEditLayerProp.do")
    @RequirePermission(system = "DRM", permissions = {"PERM_MENU_ACCESS", "PERM_FUNC_DRMC"})
    public ResponseEntity<Map<String, Object>> getEditLayerProp(@RequestParam(value = "layerName") String layerName) {
        Map<String, Object> response = new HashMap<>();
        try {
            List<Map<String, Object>> layerProperties = agsDrmService.selectColumnsExcludeGeom(layerName);
            List<Map<String, Object>> layerPropertiesNm = agsDrmService.selectColumnsCommentExcludeGeom(layerName);

            response.put("success", true);
            response.put("layerName", layerName);
            response.put("layerProperties", layerProperties);
            response.put("layerPropertiesNm", layerPropertiesNm);
            response.put("status", 200);
            response.put("message", "POI 레이어 속성 조회 성공");
            return ResponseEntity.ok(response);

        } catch (DataIntegrityViolationException e) {
            logger.warn("시스템 오류가 발생했습니다: {}", "POI 레이어 속성 조회", e);
            response.put("status", 500);
            response.put("message", "서버 오류가 발생했습니다");
            return ResponseEntity.status(HttpStatus.CONFLICT).body(response);

        } catch (NullPointerException e) {
            logger.error("시스템 오류가 발생했습니다: {}", "POI 레이어 속성 조회", e);
            response.put("status", 500);
            response.put("message", "서버 오류가 발생했습니다");
            return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).build();

        } catch (DataAccessException e) {
            logger.error("시스템 오류가 발생했습니다: {}", "POI 레이어 속성 조회", e);
            response.put("status", 500);
            response.put("message", "서버 오류가 발생했습니다");
            return ResponseEntity.status(HttpStatus.SERVICE_UNAVAILABLE).build();

        } catch (Exception e) {
            logger.error("시스템 오류가 발생했습니다: {}", "POI 레이어 속성 조회", e);
            response.put("status", 500);
            response.put("message", "서버 오류가 발생했습니다");
            return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).body(response);
        }
    }

    /**
     * 정사영상용 작업공간, 저장소, 발행가능한 레이어 조회 또는 서버 등록
     * @param url API 보낼 URL
     * @param operationDescription 전달받은 작업내용
     * @param method GET or Post
     * @param drmOrthoVO
     */
    private String checkServerApi(String url, String operationDescription, String method, DrmOrthoVO drmOrthoVO) {
        ResponseEntity<String> response = null;

        try {
            switch (method) {
                case "GET" -> response = restTemplate.getForEntity(url, String.class);
                case "POST" -> {
                    HttpHeaders headers = new HttpHeaders();
                    headers.setContentType(MediaType.APPLICATION_JSON);

                    Map<String, Object> jsonMap = new HashMap<>();
                    jsonMap.put("workspace", drmOrthoVO.getLyrSrvcJobSpceNm());
                    jsonMap.put("name", drmOrthoVO.getLyrSrvcStorgeNm());
                    if (drmOrthoVO.getLyrSrvcStorgeExpln() != null || !Objects.equals(drmOrthoVO.getLyrSrvcStorgeExpln(), "")) jsonMap.put("description", drmOrthoVO.getLyrSrvcStorgeExpln());
                    jsonMap.put("type", "GeoTiff");
                    jsonMap.put("path", drmOrthoVO.getLyrFlpth());

                    HttpEntity<Map<String, Object>> request = new HttpEntity<>(jsonMap, headers);
                    response = restTemplate.postForEntity(url, request, String.class);
                }
                default -> throw new UnsupportedOperationException("지원되지 않는 HTTP 메서드: " + method);
            }

            // 응답코드 성공 처리
            if (response.getStatusCode().is2xxSuccessful()) {
                String successMsg = operationDescription + " 성공 (Status: " + response.getStatusCode() + ")";
                String responseBody = response.getBody();
                return responseBody != null ? responseBody : successMsg;

            } else {
                // 예외 상황 처리
                return "error " + operationDescription + " 예상치 않은 상태 코드: " + response.getStatusCode() + " | Body: " + response.getBody();
            }

        } catch (RestClientResponseException e) {
            // HttpClientErrorException (4xx) 또는 HttpServerErrorException (5xx)을 포함하는 상위 예외
            String errorBody = e.getResponseBodyAsString();
            String errorMsg = "error " + operationDescription + " API 에러 발생 (Status: " + e.getRawStatusCode() + "): " + e.getStatusText() + " | Response Body: " + errorBody;
            logger.error("시스템 오류가 발생했습니다: {}", "정사영상 등록 오류", e);

            return errorMsg;

        } catch (Exception e) {
            // 일반 예외 처리
            String errorMsg = "error " + operationDescription + " API 호출 중 예외 발생: ";
            logger.error("시스템 오류가 발생했습니다: {}", "정사영상 등록 오류", e);

            return errorMsg;
        }
    }

    /**
     * GeoTiff 저장소 내 발행가능한 레이어 조회 및 해당 레이어 발행
     * @param drmOrthoVO
     */
    private String checkLayerAndPublish (DrmOrthoVO drmOrthoVO) {
        String retrievalUrl = String.format("%s/workspace/%s/storage/%s/tables", BASE_URL + "/rest", drmOrthoVO.getLyrSrvcJobSpceNm(), drmOrthoVO.getLyrSrvcStorgeNm());
        String layersJsonResult = checkServerApi(retrievalUrl, "발행 가능 레이어 조회", "GET", null);

        if (layersJsonResult.contains("error")) {
            return layersJsonResult;
        }
        try {
            TypeReference<Map<String, Object>> responseType = new TypeReference<Map<String, Object>>() {};
            Map<String, Object> fullResponse = objectMapper.readValue(layersJsonResult, responseType);

            if (!"OK".equals(fullResponse.get("status"))) {
                return "error 발행 가능 레이어 조회 실패: " + layersJsonResult;
            }

            Object resultObject = fullResponse.get("result");

            if (!(resultObject instanceof List)) {
                return "error 발행 가능 레이어 조회 응답 구조 오류: 'result' 필드가 배열(List)이 아닙니다.";
            }

            List<Map<String, Object>> layers = (List<Map<String, Object>>) resultObject;

            if (layers == null || layers.isEmpty()) {
                return "error 저장소에 발행 가능한 레이어 (GeoTiff)가 없습니다.";
            }

            // 레이어 정보를 추출
            Map<String, Object> firstLayer = layers.get(0);
            String tableName = (String) firstLayer.get("tableName");

            // 레이어 발행 (POST /layer)
            String publishUrl = String.format("%s/layer", BASE_URL + "/rest");

            Map<String, Object> publishBody = new HashMap<>();
            publishBody.put("name", drmOrthoVO.getLyrSrvcNm());
            publishBody.put("workspace", drmOrthoVO.getLyrSrvcJobSpceNm());
            publishBody.put("storage", drmOrthoVO.getLyrSrvcStorgeNm());
            publishBody.put("tableName", tableName);
            publishBody.put("serviceSrid", 3857);       // VWORLD 좌표계

            String publishResult = publishLayerApi(publishUrl, publishBody);

            // 발행 실패 오류 메시지 반환
            if (publishResult.contains("error")) {
                return publishResult;
            }

            return "레이어 조회 및 발행 성공: " + tableName;

        } catch (Exception e) {
            String errorMsg = "error 레이어 조회 및 발행 처리 중 JSON 파싱/처리 오류 발생: ";
            logger.error("시스템 오류가 발생했습니다: {}", "레이어 조회 및 발행 처리 중 JSON 파싱/처리 오류 발생", e);
            return errorMsg;
        }
    }

    /**
     * 정사영상 레이어 발행
     * @param url 레이어 발행 API URL
     * @param publishBody 발행할 레이어 정보
     * @return
     */
    private String publishLayerApi(String url, Map<String, Object> publishBody) {
        ResponseEntity<String> response = null;

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

            HttpEntity<Map<String, Object>> request = new HttpEntity<>(publishBody, headers);
            response = restTemplate.postForEntity(url, request, String.class);

            if (response.getStatusCode().is2xxSuccessful()) {
                String successMsg = "레이어 발행" + " 성공 (Status: " + response.getStatusCode() + ")";
                return response.getBody() != null ? response.getBody() : successMsg;

            } else {
                return "error " + "레이어 발행" + " 예상치 않은 상태 코드: " + response.getStatusCode() + " | Body: " + response.getBody();
            }

        } catch (RestClientResponseException e) {
            String errorBody = e.getResponseBodyAsString();
            return "error " + "레이어 발행" + " API 에러 발생 (Status: " + e.getRawStatusCode() + "): " + e.getStatusText() + " | Response Body: " + errorBody;

        } catch (Exception e) {
            return "error " + "레이어 발행" + " API 호출 중 예외 발생: ";
        }
    }
}