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

import incheon.ags.mrb.analysis.service.ExcelDownloadService;
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.slf4j.Logger;
import org.slf4j.LoggerFactory;
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 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.util.*;
import java.util.stream.Collectors;

@Service("mrbAnalysisExcelDownloadService")
@RequiredArgsConstructor
public class ExcelDownloadServiceImpl implements ExcelDownloadService {

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

    @Override
    public ResponseEntity<Resource> downloadExcel(String fileName, List<Map<String, Object>> data, Map<String, String> columnMap) throws IOException {

        Path tempDir = null;
        try {
            tempDir = createDownloadTempDirectory();
            Path filePath = createExcelFile(tempDir, fileName, data, columnMap);
            String downloadFileName = fileName + ".xlsx";
            ResponseEntity<Resource> response = createDownloadResponse(filePath, downloadFileName);
            scheduleDelayedCleanup(tempDir, 10000);
            return response;
        } catch (IOException e) {
            logger.error("[분석 이력] " + fileName + " 엑셀 다운로드 실패: " + e.getMessage());
            if (tempDir != null) {
                try {
                    cleanupDownloadFiles(tempDir);
                } catch (IOException ex) {
                    throw new IOException("정리 실패: " + ex.getMessage(), ex);
                }
            }
            throw new IOException("다운로드 실패: " + e.getMessage(), e);
        }
    }

    private Path createDownloadTempDirectory() throws IOException {
        return Files.createTempDirectory("analysis_excel_");
    }

    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) throws IOException {
        Files.walk(tempDir)
                .sorted(Comparator.reverseOrder())
                .map(Path::toFile)
                .forEach(File::delete);
    }

    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 void scheduleDelayedCleanup(Path tempDir, long delayMillis) {
        if (tempDir == null) return;

        final Path finalTempDir = tempDir;
        new Thread(() -> {
            try {
                Thread.sleep(delayMillis);
                cleanupDownloadFiles(finalTempDir);
            } catch (InterruptedException | IOException e) {
                logger.error("지연 삭제 실패: " + e.getMessage());
            }
        }, "AnalysisDelayedCleanup-" + System.currentTimeMillis()).start();
    }
}

