package incheon.ags.drm.service.impl;

import incheon.ags.drm.mapper.agsDrmMapper;
import incheon.ags.drm.service.agsDrmService;
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 lombok.RequiredArgsConstructor;
import org.apache.poi.ss.usermodel.CellStyle;
import org.apache.poi.ss.usermodel.DataFormat;
import org.apache.poi.ss.usermodel.HorizontalAlignment;
import org.apache.poi.xssf.usermodel.XSSFCell;
import org.apache.poi.xssf.usermodel.XSSFRow;
import org.apache.poi.xssf.usermodel.XSSFSheet;
import org.apache.poi.xssf.usermodel.XSSFWorkbook;
import org.egovframe.rte.fdl.cmmn.EgovAbstractServiceImpl;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.core.io.FileSystemResource;
import org.springframework.core.io.Resource;
import org.springframework.http.HttpHeaders;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
import org.springframework.stereotype.Service;
import org.springframework.web.multipart.MultipartFile;

import java.io.File;
import java.io.IOException;
import java.io.OutputStream;
import java.net.URLEncoder;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.text.SimpleDateFormat;
import java.util.*;
import java.util.stream.Collectors;

@Service("agsDrmService")
@RequiredArgsConstructor
public class agsDrmServiceImpl extends EgovAbstractServiceImpl implements agsDrmService {

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

    // OS별 절대 경로 주입
    @Value("${drm.upload.base-path.win}")
    private String winFilePath;
    @Value("${drm.upload.base-path.linux}")
    private String linuxFilePath;

    @Override
    public List<Map<String, Object>> selectColumnsExcludeGeom(String layerName) throws Exception {
        return agsDrmMapper.selectColumnsExcludeGeom(layerName);
    }
    @Override
    public List<Map<String, Object>> selectColumnsCommentExcludeGeom(String layerName) throws Exception {
        return agsDrmMapper.selectColumnsCommentExcludeGeom(layerName);
    }
    @Override
    public List<TaskLayerVO> getLayerList(TaskLayerSearchRequestDTO searchVO) throws Exception {
        return agsDrmMapper.getLayerList(searchVO);
    }
    @Override
    public List<Map<String, Object>> getAddressByPoint(String pnu) throws Exception {
        return agsDrmMapper.getAddressByPoint(pnu);
    }

    /* DRM_POI */
    @Override
    public Map<String, Integer> countByRelPoiId(Long relPoiId) throws Exception {
        int contentCount = agsDrmMapper.countContentByRelPoi(relPoiId);
        int resultCount = agsDrmMapper.countResultByRelPoi(relPoiId);
        Map<String, Integer> result = new HashMap<>();
        result.put("contentCount", contentCount);
        result.put("resultCount", resultCount);
        return result;
    }
    @Override
    public void deleteByRelPoiId(Long relPoiId) throws Exception {
        DrmContentVO drmContentVO = new DrmContentVO();
        drmContentVO.setRelPoiId(relPoiId);;
        drmContentVO.setRecordCountPerPage(0);
        List<DrmContentVO> result = agsDrmMapper.selectContentList(drmContentVO);
        if (result != null && !result.isEmpty()) {
            for (DrmContentVO item : result) {
                Long contsId = item.getContsId();
                deleteContent(contsId);
            }
        }

        agsDrmMapper.deleteContentByRelPoi(relPoiId);
        agsDrmMapper.deleteResultByRelPoi(relPoiId);
    }
    @Override
    public List<DrmPoiVO> selectPOIList(DrmPoiVO drmPoiVO) throws Exception {
        return agsDrmMapper.selectPOIList(drmPoiVO);
    }
    @Override
    public int selectPOIListCnt(DrmPoiVO drmPoiVO) throws Exception {
        return agsDrmMapper.selectPOIListCnt(drmPoiVO);
    }
    @Override
    public DrmPoiVO searchPoiDetail(DrmPoiVO drmPoiVO) throws Exception {
        return agsDrmMapper.searchPoiDetail(drmPoiVO);
    }
    public void updatePoi(DrmPoiVO drmPoiVO) throws Exception {
        agsDrmMapper.updatePoi(drmPoiVO);
    }
    public void deletePOI(DrmPoiVO drmPoiVO) throws Exception {
        agsDrmMapper.deletePOI(drmPoiVO);
    }
    @Override
    public void registerContent(DrmContentVO drmContentVO, MultipartFile file) throws Exception {
        // 파일명 생성
        Long nextContentId = agsDrmMapper.getContentIdSeq();
        String fileName = nextContentId + "_" + file.getOriginalFilename();

        // 파일 저장
        Path filePath = getFilePath(drmContentVO.getContsClsf(), fileName);
        file.transferTo(filePath.toFile());

        drmContentVO.setContsFileNm(fileName);

        agsDrmMapper.registerContent(drmContentVO);
    }
    @Override
    public void registerResult(DrmResultVO drmResultVO) throws Exception {
        agsDrmMapper.registerResult(drmResultVO);
    }

    /* CONTENT */
    @Override
    public List<DrmContentVO> selectContentList(DrmContentVO drmContentVO) throws Exception {
        return agsDrmMapper.selectContentList(drmContentVO);
    }
    public int selectContentListCnt(DrmContentVO drmContentVO) throws Exception {
        return agsDrmMapper.selectContentListCnt(drmContentVO);
    }
    @Override
    public DrmContentVO searchContentDetail(Long contsId) throws Exception {
        return agsDrmMapper.searchContentDetail(contsId);
    }
    @Override
    public void updateContent(DrmContentVO drmContentVO) throws Exception {
        agsDrmMapper.updateContent(drmContentVO);
    }
    @Override
    public Map<String, Object> deleteContent(Long contsId) throws Exception {
        Map<String, Object> response = new HashMap<>();
        // 기존 파일 경로 조회
        DrmContentVO resultVo = agsDrmMapper.searchContentDetail(contsId);
        Path filePath = getFilePath( resultVo.getContsClsf(), resultVo.getContsFileNm() );

        // 파일 삭제 및 데이터 delete
        if (!Files.exists(filePath)) {
            agsDrmMapper.deleteContent(contsId);
            response.put("status", "error");
            response.put("message", "기존 파일 조회에 실패하여 데이터만 삭제되었습니다");

        } else {
            Files.delete(filePath);
            agsDrmMapper.deleteContent(contsId);
            response.put("status", true);
        }

        return response;
    }

    /**
     * OS에 따라 저장 경로 결정
     */
    private String getOsDependentBasePath() {
        String rawOsName = System.getProperty("os.name");
        String osName = (rawOsName != null) ? rawOsName.toLowerCase() : "linux";
        if (osName.contains("win")) {
            return this.winFilePath;
        } else {
            return this.linuxFilePath;
        }
    }

    /**
     * 관리시스템 - 콘텐츠 분류에따라 읽어오거나 저장할 첨부파일 경로 결정
     * @param clsf 콘텐츠 분류
     * @param fileNm 파일명
     */
    public Path getFilePath (String clsf, String fileNm) throws Exception {

        // OS에 따라 baseDir 문자열을 획득
        String baseDirString = getOsDependentBasePath();
        Path baseDir = Paths.get(baseDirString);

        // 분류에 따른 하위 폴더 경로 설정
        String subPath = switch (clsf) {
            case "CNT000" -> "ags/drm/content/cnt000";
            case "CNT001" -> "ags/drm/content/cnt001";
            case "CNT002" -> "ags/drm/content/cnt002";
            case "CNT003" -> "ags/drm/content/cnt003";
            case "CNT004" -> "ags/drm/content/cnt004";
            default -> "ags/drm/content/etc";
        };

        /* 저장 디렉토리 경로 생성 */
        // baseDir (절대 경로)와 subPath를 결합합니다. (OS 독립적)
        Path dirPath = baseDir.resolve(subPath);

        // 디렉토리 생성
        if (!Files.exists(dirPath)) {
            Files.createDirectories(dirPath);
        }

        // 최종 파일 경로 반환
        return dirPath.resolve(fileNm);
    }
    /**
     * 스카이 드론 - 콘텐츠 분류에따라 읽어올 첨부파일 저장경로 결정
     * @param clsf 콘텐츠 분류
     * @param fileNm 파일명
     */
    public Path getSgpFilePath (String clsf, String fileNm) throws Exception {

        String baseDirString = getOsDependentBasePath();
        Path baseDir = Paths.get(baseDirString);

        String subPath = switch (clsf) {
            case "CNT000" -> "sgp/drm/content/cnt000";
            case "CNT001" -> "sgp/drm/content/cnt001";
            case "CNT002" -> "sgp/drm/content/cnt002";
            case "CNT003" -> "sgp/drm/content/cnt003";
            case "CNT004" -> "sgp/drm/content/cnt004";
            default -> "sgp/drm/content/etc";
        };

        Path dirPath = baseDir.resolve(subPath);

        if (!Files.exists(dirPath)) {
            Files.createDirectories(dirPath);
        }

        return dirPath.resolve(fileNm);
    }

    /* RESULT */
    @Override
    public List<DrmResultVO> selectResultList(DrmResultVO drmResultVO) throws Exception {
        return agsDrmMapper.selectResultList(drmResultVO);
    }
    @Override
    public int selectResultListCnt(DrmResultVO drmResultVO) throws Exception {
        return agsDrmMapper.selectResultListCnt(drmResultVO);
    }
    @Override
    public DrmResultVO searchResultDetail(Long rsulId) throws Exception {
        return agsDrmMapper.searchResultDetail(rsulId);
    }
    @Override
    public void updateResult(DrmResultVO drmResultVO) throws Exception {
        agsDrmMapper.updateResult(drmResultVO);
    }
    @Override
    public void deleteResult(Long rsulId) throws Exception {
        agsDrmMapper.deleteResult(rsulId);
    }

    /* ORTHO */
    @Override
    public List<DrmOrthoVO> selectOrthoList(DrmOrthoVO drmOrthoVO) throws Exception {
        return agsDrmMapper.selectOrthoList(drmOrthoVO);
    }
    @Override
    public int selectOrthoListCnt(DrmOrthoVO drmOrthoVO) throws Exception {
        return agsDrmMapper.selectOrthoListCnt(drmOrthoVO);
    }
    @Override
    public void registerOrtho(DrmOrthoVO drmOrthoVO) throws Exception {
        agsDrmMapper.registerOrtho(drmOrthoVO);
    }
    @Override
    public DrmOrthoVO searchOrthoDetail(Long orthoId) throws Exception {
        return agsDrmMapper.searchOrthoDetail(orthoId);
    }
    @Override
    public void updateOrtho(DrmOrthoVO drmOrthoVO) throws Exception {
        agsDrmMapper.updateOrtho(drmOrthoVO);
    }
    @Override
    public void deleteOrtho(Long orthoId) throws Exception {
        agsDrmMapper.deleteOrtho(orthoId);
    }

    private Path createExcelFile(Path tempDir, String fileName, List<Map<String, Object>> data, Map<String, String> columnMap) throws IOException {
        if (data == null || data.isEmpty()) {
            throw new RuntimeException("Excel로 변환할 데이터가 없습니다.");
        }

        Path excelFile = tempDir.resolve(fileName + ".xlsx");

        try (XSSFWorkbook workbook = new XSSFWorkbook()) {
            XSSFSheet sheet = workbook.createSheet();

            // 0. 스타일
            DataFormat format = workbook.createDataFormat();

            CellStyle floatStyle = workbook.createCellStyle();
            floatStyle.setDataFormat(format.getFormat("#,##0.#"));

            CellStyle intStyle = workbook.createCellStyle();
            intStyle.setDataFormat(format.getFormat("#,##0"));

            CellStyle headerStyle = workbook.createCellStyle();
            headerStyle.setAlignment(HorizontalAlignment.CENTER);

            // 1. 컬럼 정리
            Set<String> dataKey = data.get(0).keySet();
            List<String> columnKeyList = new ArrayList<>(dataKey);  // 컬럼맵이 없는 경우에 데이터의 모든 키를 헤더로 사용

            if (columnMap != null) {
                // 컬럼 키
                columnKeyList = new ArrayList<>(columnMap.keySet());

                // 존재하지 않는 컬럼 체크
                Set<String> availableColumns = dataKey;
                List<String> invalidColumns = columnKeyList.stream()
                        .filter(col -> !availableColumns.contains(col))
                        .collect(Collectors.toList());

                if (!invalidColumns.isEmpty()) {
                    throw new RuntimeException("존재하지 않는 컬럼: " + String.join(", ", invalidColumns));
                }
            }

            // 2. 헤더 생성
            XSSFRow headerRow = sheet.createRow(0);
            for (int i = 0; i < columnKeyList.size(); i++) {
                XSSFCell cell = headerRow.createCell(i);
                String columnName = columnMap != null ? columnMap.get(columnKeyList.get(i)) : columnKeyList.get(i);
                cell.setCellValue(columnName);
                cell.setCellStyle(headerStyle);
            }

            // 3. 데이터 작성
            for (int i = 0; i < data.size(); i++) {
                Map<String, Object> rowData = data.get(i);
                XSSFRow row = sheet.createRow(i + 1);
                for (int j = 0; j < columnKeyList.size(); j++) {
                    XSSFCell cell = row.createCell(j);
                    Object value = rowData.get(columnKeyList.get(j));

                    if (value instanceof Number) {

                        double num = ((Number) value).doubleValue();
                        cell.setCellValue(num);

                        if (num == Math.floor(num)) {
                            cell.setCellStyle(intStyle);
                        } else {
                            cell.setCellStyle(floatStyle);
                        }
                    } else {
                        cell.setCellValue(value != null ? value.toString() : "");
                    }
                }
            }

            // 4. 열 너비 조정
            int maxWidth = 255 * 256;   // 최대 폭
            for (int i = 0; i < columnKeyList.size(); i++) {
                String key = columnKeyList.get(i);

                int maxLength = columnMap != null ? columnMap.get(key).length() : key.length();   // 헤더 포함
                for (Map<String, Object> row : data) {
                    Object val = row.get(key);
                    if (val != null) {
                        int len = val.toString().length();
                        if (maxLength < len) {
                            maxLength = len;
                        }
                    }
                }

                int width = (int) ((maxLength + 4) * 256 * 1.3);
                sheet.setColumnWidth(i, Math.min(width, maxWidth));
            }

            // 5. 파일 쓰기
            try (OutputStream os = Files.newOutputStream(excelFile)) {
                workbook.write(os);
            }
        }

        return excelFile;
    }

    private ResponseEntity<Resource> createDownloadResponse(Path filePath, String fileName) throws IOException {
        if (!Files.exists(filePath)) {
            throw new RuntimeException("다운로드할 파일이 존재하지 않습니다: " + filePath);
        }

        // 파일 크기 로그
        long fileSize = Files.size(filePath);
        Resource resource = new FileSystemResource(filePath);

        // 파일명 인코딩
        String encodedFileName = URLEncoder.encode(fileName, StandardCharsets.UTF_8)
                .replaceAll("\\+", "%20");  // 공백 처리

        HttpHeaders headers = new HttpHeaders();
        headers.add(HttpHeaders.CONTENT_DISPOSITION, "attachment; filename*=UTF-8''" + encodedFileName);
        headers.add(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_OCTET_STREAM_VALUE);
        headers.add(HttpHeaders.CACHE_CONTROL, "no-cache");

        return ResponseEntity.ok()
                .headers(headers)
                .contentLength(fileSize)
                .body(resource);
    }
    private void cleanupDownloadFiles(Path tempDir) {
        try {
            Files.walk(tempDir)
                    .sorted(Comparator.reverseOrder())
                    .map(Path::toFile)
                    .forEach(File::delete);
        } catch (IOException e) {
            logger.error("시스템 오류가 발생했습니다: {}", "임시 파일 정리", e);
        }
    }
    @Override
    public ResponseEntity<Resource> downLoadExcel(List<Map<String, Object>> data, Map<String, String> columnMap, String fileName) {
        Path tempDir = null;
        try {
            tempDir = Files.createTempDirectory("excel_");
            Path excelFilePath = createExcelFile(tempDir, fileName, data, columnMap);
            String downloadFileName = fileName + "_" + new SimpleDateFormat("yyyyMMdd_HHmmss").format(new Date()) + ".xlsx";
            ResponseEntity<Resource> response = createDownloadResponse(excelFilePath, downloadFileName);

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

            if (tempDir != null) {
                try {
                    cleanupDownloadFiles(tempDir);
                } catch (Exception cleanupEx) {
                    logger.error("시스템 오류가 발생했습니다: {}", "엑셀 정리 실패", cleanupEx);
                }
            }

            throw new RuntimeException("다운로드 실패: " + e.getMessage(), e);
        }

    }
}
