package com.nanri.aiimage.modules.dedupe.service;

import cn.hutool.core.io.FileUtil;
import cn.hutool.core.util.IdUtil;
import cn.hutool.json.JSONUtil;
import com.nanri.aiimage.common.exception.BusinessException;
import com.nanri.aiimage.config.StorageProperties;
import com.nanri.aiimage.modules.dedupe.model.dto.DedupeRunRequest;
import com.nanri.aiimage.modules.dedupe.model.dto.DedupeSourceFileDto;
import com.nanri.aiimage.modules.dedupe.model.vo.DedupeResultItemVo;
import com.nanri.aiimage.modules.dedupe.model.vo.DedupeRunVo;
import com.nanri.aiimage.modules.file.service.oss.OssStorageService;
import com.nanri.aiimage.modules.task.mapper.FileResultMapper;
import com.nanri.aiimage.modules.task.mapper.FileTaskMapper;
import com.nanri.aiimage.modules.task.model.entity.FileResultEntity;
import com.nanri.aiimage.modules.task.model.entity.FileTaskEntity;
import lombok.RequiredArgsConstructor;
import org.apache.poi.ss.usermodel.Cell;
import org.apache.poi.ss.usermodel.DataFormatter;
import org.apache.poi.ss.usermodel.Row;
import org.apache.poi.ss.usermodel.Sheet;
import org.apache.poi.ss.usermodel.Workbook;
import org.apache.poi.xssf.usermodel.XSSFWorkbook;
import org.springframework.core.io.FileSystemResource;
import org.springframework.core.io.Resource;
import org.springframework.stereotype.Service;

import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.nio.file.Files;
import java.time.LocalDateTime;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.zip.ZipEntry;
import java.util.zip.ZipOutputStream;

import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;

@Service
@RequiredArgsConstructor
public class DedupeRunService {

    private final FileTaskMapper fileTaskMapper;
    private final FileResultMapper fileResultMapper;
    private final StorageProperties storageProperties;
    private final OssStorageService ossStorageService;
    private final DedupeTotalDataService dedupeTotalDataService;

    public DedupeRunVo run(DedupeRunRequest request) {
        if (!request.isKeepIntegerIds() && !request.isKeepUnderscoreIds() && !request.isKeepIntegerMainIdsWhenNoSubIds()) {
            throw new BusinessException("请至少选择一种 ID 保留规则");
        }

        FileTaskEntity task = new FileTaskEntity();
        task.setTaskNo("DEDUPE-" + IdUtil.getSnowflakeNextIdStr());
        task.setModuleType("DEDUPE");
        task.setTaskMode("IMMEDIATE");
        task.setStatus("SUCCESS");
        task.setSourceFileCount(request.getFiles().size());
        task.setCreatedBy("user:" + request.getUserId());
        task.setUserId(request.getUserId());
        task.setRequestJson(JSONUtil.toJsonStr(request));
        task.setCreatedAt(LocalDateTime.now());
        task.setUpdatedAt(LocalDateTime.now());
        task.setFinishedAt(LocalDateTime.now());
        fileTaskMapper.insert(task);

        boolean folderMode = request.getArchiveName() != null && !request.getArchiveName().isBlank();
        List<DedupeResultItemVo> items = new ArrayList<>();
        int successCount = 0;
        int failedCount = 0;
        List<DedupeArchiveEntry> archiveEntries = new ArrayList<>();

        for (DedupeSourceFileDto sourceFile : request.getFiles()) {
            DedupeResultItemVo item = new DedupeResultItemVo();
            item.setSourceFilename(sourceFile.getOriginalFilename());
            try {
                File inputFile = findLocalSourceFile(sourceFile.getFileKey());
                if (inputFile == null || !inputFile.exists()) {
                    throw new BusinessException("上传文件不存在，请重新上传");
                }

                String inputName = sourceFile.getOriginalFilename() == null ? inputFile.getName() : sourceFile.getOriginalFilename();
                String outputFilename = buildOutputFilename(inputName);
                File outputDir = FileUtil.mkdir(FileUtil.file(storageProperties.getLocalTempDir(), "dedupe-result"));
                File outputFile = buildNamedOutputFile(outputDir, outputFilename);

                cleanExcelByLegacyRules(
                        inputFile,
                        outputFile,
                        request.getSelectedColumns(),
                        request.isKeepIntegerIds(),
                        request.isKeepUnderscoreIds(),
                        request.isKeepIntegerMainIdsWhenNoSubIds()
                );

                if (folderMode) {
                    archiveEntries.add(new DedupeArchiveEntry(sourceFile.getRelativePath(), inputName, outputFile));
                    successCount++;
                    continue;
                }

                String ossObjectKey = ossStorageService.uploadResultFile(outputFile, "DEDUPE");
                // 只存 objectKey，不存预签名 URL
                String downloadFilename = request.getFiles().size() == 1 ? inputName : outputFile.getName();

                item.setSuccess(true);
                item.setOutputFilename(downloadFilename);
                item.setDownloadUrl(ossStorageService.generateFreshDownloadUrl(ossObjectKey));
                successCount++;

                FileResultEntity resultEntity = new FileResultEntity();
                resultEntity.setTaskId(task.getId());
                resultEntity.setModuleType("DEDUPE");
                resultEntity.setSourceFilename(inputName);
                resultEntity.setResultFilename(downloadFilename);
                resultEntity.setResultFileUrl(ossObjectKey);  // 存 objectKey
                resultEntity.setResultFileSize(outputFile.length());
                resultEntity.setResultContentType("application/vnd.openxmlformats-officedocument.spreadsheetml.sheet");
                resultEntity.setSuccess(1);
                resultEntity.setUserId(request.getUserId());
                resultEntity.setCreatedAt(LocalDateTime.now());
                fileResultMapper.insert(resultEntity);
            } catch (Exception ex) {
                item.setSuccess(false);
                item.setError(ex.getMessage());
                failedCount++;

                FileResultEntity resultEntity = new FileResultEntity();
                resultEntity.setTaskId(task.getId());
                resultEntity.setModuleType("DEDUPE");
                resultEntity.setSourceFilename(sourceFile.getOriginalFilename());
                resultEntity.setSuccess(0);
                resultEntity.setErrorMessage(ex.getMessage());
                resultEntity.setUserId(request.getUserId());
                resultEntity.setCreatedAt(LocalDateTime.now());
                fileResultMapper.insert(resultEntity);
            }
            items.add(item);
        }

        if (folderMode && !archiveEntries.isEmpty()) {
            File zipFile = packageFolderDedupeResultsAsZip(request.getArchiveName(), archiveEntries);
            String ossObjectKey = ossStorageService.uploadResultFile(zipFile, "DEDUPE");
            // 只存 objectKey
            DedupeResultItemVo item = new DedupeResultItemVo();
            item.setSourceFilename(request.getArchiveName());
            item.setOutputFilename(zipFile.getName());
            item.setSuccess(true);
            item.setDownloadUrl(ossStorageService.generateFreshDownloadUrl(ossObjectKey));

            FileResultEntity resultEntity = new FileResultEntity();
            resultEntity.setTaskId(task.getId());
            resultEntity.setModuleType("DEDUPE");
            resultEntity.setSourceFilename(request.getArchiveName());
            resultEntity.setResultFilename(zipFile.getName());
            resultEntity.setResultFileUrl(ossObjectKey);  // 存 objectKey
            resultEntity.setResultFileSize(zipFile.length());
            resultEntity.setResultContentType("application/zip");
            resultEntity.setSuccess(1);
            resultEntity.setUserId(request.getUserId());
            resultEntity.setCreatedAt(LocalDateTime.now());
            fileResultMapper.insert(resultEntity);
            item.setResultId(resultEntity.getId());
            items.add(0, item);
        }

        task.setSuccessFileCount(successCount);
        task.setFailedFileCount(failedCount);
        task.setResultJson(JSONUtil.toJsonStr(items));
        task.setUpdatedAt(LocalDateTime.now());
        fileTaskMapper.updateById(task);

        DedupeRunVo vo = new DedupeRunVo();
        vo.setTotal(request.getFiles().size());
        vo.setSuccessCount(successCount);
        vo.setFailedCount(failedCount);
        vo.setItems(items);
        return vo;
    }

    public List<DedupeResultItemVo> listHistory(Long userId) {
        return fileResultMapper.selectList(new LambdaQueryWrapper<FileResultEntity>()
                        .eq(FileResultEntity::getModuleType, "DEDUPE")
                        .eq(FileResultEntity::getUserId, userId)
                        .orderByDesc(FileResultEntity::getCreatedAt)
                        .last("limit 50"))
                .stream()
                .map(entity -> {
                    DedupeResultItemVo vo = new DedupeResultItemVo();
                    vo.setResultId(entity.getId());
                    vo.setSourceFilename(entity.getSourceFilename());
                    vo.setOutputFilename(entity.getResultFilename());
                    vo.setDownloadUrl(entity.getSuccess() != null && entity.getSuccess() == 1
                            ? ossStorageService.generateFreshDownloadUrl(entity.getResultFileUrl())
                            : null);
                    vo.setSuccess(entity.getSuccess() != null && entity.getSuccess() == 1);
                    vo.setError(entity.getErrorMessage());
                    return vo;
                })
                .toList();
    }

    public void deleteHistory(Long resultId, Long userId) {
        FileResultEntity entity = fileResultMapper.selectById(resultId);
        if (entity == null || !"DEDUPE".equals(entity.getModuleType()) || !userId.equals(entity.getUserId())) {
            throw new BusinessException("记录不存在");
        }
        fileResultMapper.deleteById(resultId);
    }

    public Resource getResultFile(Long resultId, Long userId) {
        FileResultEntity entity = fileResultMapper.selectById(resultId);
        if (entity == null || entity.getResultFileUrl() == null || !"DEDUPE".equals(entity.getModuleType()) || !userId.equals(entity.getUserId())) {
            throw new BusinessException("结果文件不存在");
        }
        File file = new File(entity.getResultFileUrl());
        if (!file.exists()) {
            throw new BusinessException("结果文件不存在");
        }
        return new FileSystemResource(file);
    }

    private void cleanExcelByLegacyRules(File inputFile, File outputFile, List<String> selectedColumns,
                                          boolean keepIntegerIds, boolean keepUnderscoreIds,
                                          boolean keepIntegerMainIdsWhenNoSubIds) throws Exception {
        DataFormatter formatter = new DataFormatter();
        try (FileInputStream fis = new FileInputStream(inputFile);
             Workbook workbook = org.apache.poi.ss.usermodel.WorkbookFactory.create(fis);
             XSSFWorkbook outputWorkbook = new XSSFWorkbook()) {
            Sheet sheet = workbook.getSheetAt(0);
            Row headerRow = sheet.getRow(0);
            if (headerRow == null) {
                throw new BusinessException("Excel 表头为空");
            }

            Map<String, Integer> headerMap = new HashMap<>();
            for (int i = 0; i < headerRow.getLastCellNum(); i++) {
                Cell cell = headerRow.getCell(i);
                String value = normalizeCellText(cell == null ? null : formatter.formatCellValue(cell));
                if (value.isBlank()) {
                    continue;
                }
                value = value.split("idASIN国家状态价格变体数量", 2)[0].trim().isEmpty()
                        ? value
                        : value.split("idASIN国家状态价格变体数量", 2)[0].trim();
                if (headerMap.containsKey(value)) {
                    continue;
                }
                headerMap.put(value, i);
                if ("缩略图地址8".equals(value)) {
                    break;
                }
            }

            List<String> missing = selectedColumns.stream().filter(column -> !headerMap.containsKey(column)).toList();
            if (!missing.isEmpty()) {
                throw new BusinessException("缺少列：" + String.join("、", missing));
            }

            Sheet outputSheet = outputWorkbook.createSheet(sheet.getSheetName());
            Row outputHeaderRow = outputSheet.createRow(0);
            for (int i = 0; i < selectedColumns.size(); i++) {
                outputHeaderRow.createCell(i).setCellValue(selectedColumns.get(i));
            }

            Integer idColumnIndex = headerMap.get("id");
            Integer asinColumnIndex = headerMap.get("ASIN");
            if (idColumnIndex != null) {
                // The preliminary global scan for mainIdHasSubIdMap was removed.
                // We now determine subset existence contextually (per group) during the main loop.
            }
            List<Row> candidateRows = new ArrayList<>();
            Set<String> candidateAsinValues = new HashSet<>();
            for (int rowNum = 1; rowNum <= sheet.getLastRowNum(); rowNum++) {
                Row row = sheet.getRow(rowNum);
                if (row == null) {
                    continue;
                }
                if (idColumnIndex != null) {
                    String idValue = normalizeCellText(formatter.formatCellValue(row.getCell(idColumnIndex)));
                    if (!shouldKeepId(idValue, keepIntegerIds, keepUnderscoreIds, keepIntegerMainIdsWhenNoSubIds, sheet, rowNum, idColumnIndex, formatter)) {
                        continue;
                    }
                }
                candidateRows.add(row);
                if (asinColumnIndex != null) {
                    String asinValue = dedupeTotalDataService.normalizeComparableValueOrBlank(formatter.formatCellValue(row.getCell(asinColumnIndex)));
                    if (!asinValue.isBlank()) {
                        candidateAsinValues.add(asinValue);
                    }
                }
            }

            Set<String> matchedAsinValues = dedupeTotalDataService.findExistingComparableValues(candidateAsinValues);
            Set<String> writtenAsinValues = new HashSet<>();
            int outputRowIndex = 1;
            for (Row row : candidateRows) {
                if (asinColumnIndex != null) {
                    String asinValue = dedupeTotalDataService.normalizeComparableValueOrBlank(formatter.formatCellValue(row.getCell(asinColumnIndex)));
                    if (!asinValue.isBlank()) {
                        if (!matchedAsinValues.isEmpty() && matchedAsinValues.contains(asinValue)) {
                            continue;
                        }
                        if (!writtenAsinValues.add(asinValue)) {
                            continue;
                        }
                    }
                }

                Row outputRow = outputSheet.createRow(outputRowIndex++);
                for (int i = 0; i < selectedColumns.size(); i++) {
                    Integer sourceIndex = headerMap.get(selectedColumns.get(i));
                    String value = sourceIndex == null ? "" : normalizeCellText(formatter.formatCellValue(row.getCell(sourceIndex)));
                    outputRow.createCell(i).setCellValue(value);
                }
            }

            try (FileOutputStream fos = new FileOutputStream(outputFile)) {
                outputWorkbook.write(fos);
            }
        }
    }

    private File packageFolderDedupeResultsAsZip(String archiveName, List<DedupeArchiveEntry> archiveEntries) {
        File outputDir = FileUtil.mkdir(FileUtil.file(storageProperties.getLocalTempDir(), "dedupe-result"));
        File zipFile = buildNamedOutputFile(outputDir, archiveName + ".zip");
        try (ZipOutputStream zos = new ZipOutputStream(new FileOutputStream(zipFile))) {
            for (DedupeArchiveEntry entry : archiveEntries) {
                String zipEntryName = buildFolderZipEntry(entry.relativePath(), entry.inputName(), entry.inputName(), false);
                zos.putNextEntry(new ZipEntry(zipEntryName));
                Files.copy(entry.outputFile().toPath(), zos);
                zos.closeEntry();
            }
        } catch (Exception ex) {
            throw new BusinessException("去重结果打包失败：" + ex.getMessage());
        }
        return zipFile;
    }

    private String buildFolderZipEntry(String relativePath, String inputName, String outputFilename, boolean includeSourceStemFolder) {
        String normalizedRelativePath = relativePath == null ? "" : relativePath.replace('\\', '/');
        String relativeDir = normalizedRelativePath.contains("/")
                ? normalizedRelativePath.substring(0, normalizedRelativePath.lastIndexOf('/'))
                : "";
        List<String> parts = new ArrayList<>();
        if (!relativeDir.isBlank()) {
            parts.add(relativeDir);
        }
        if (includeSourceStemFolder) {
            parts.add(FileUtil.mainName(inputName));
        }
        parts.add(outputFilename);
        return String.join("/", parts);
    }

    private boolean shouldKeepId(String text, boolean keepIntegerIds, boolean keepUnderscoreIds,
                                 boolean keepIntegerMainIdsWhenNoSubIds, Sheet sheet, int currentRowNum, int idColumnIndex, DataFormatter formatter) {
        if (text == null || text.isBlank()) {
            return false;
        }
        if (keepUnderscoreIds && text.matches("\\d+_\\d+")) {
            return true;
        }
        if (keepIntegerIds && text.matches("\\d+")) {
            return true;
        }
        if (keepIntegerMainIdsWhenNoSubIds && text.matches("\\d+")) {
            String mainId = extractMainId(text);
            if (mainId.isEmpty()) return false;
            
            boolean hasSubIdInGroup = false;
            for (int r = currentRowNum + 1; r <= sheet.getLastRowNum(); r++) {
                Row nextRow = sheet.getRow(r);
                if (nextRow == null) continue;
                Cell cell = nextRow.getCell(idColumnIndex);
                if (cell == null) continue;
                String nextIdValue = normalizeCellText(formatter.formatCellValue(cell));
                if (nextIdValue.isBlank()) continue;
                
                String nextMainId = extractMainId(nextIdValue);
                if (nextMainId.equals(mainId)) {
                    if (nextIdValue.matches("\\d+_\\d+")) {
                        hasSubIdInGroup = true;
                        break;
                    }
                } else {
                    // Encountered a different main ID. The group for 'mainId' has ended.
                    break;
                }
            }
            return !hasSubIdInGroup;
        }
        return false;
    }

    private String extractMainId(String text) {
        if (text == null || text.isBlank()) {
            return "";
        }
        if (text.matches("\\d+")) {
            return text;
        }
        if (text.matches("\\d+_\\d+")) {
            int idx = text.indexOf('_');
            return idx > 0 ? text.substring(0, idx) : "";
        }
        return "";
    }

    private File findLocalSourceFile(String fileKey) {
        File baseDir = FileUtil.file(storageProperties.getLocalTempDir());
        if (!baseDir.exists()) {
            return null;
        }
        List<File> matchedFiles = FileUtil.loopFiles(baseDir, pathname -> pathname.isFile() && pathname.getName().startsWith(fileKey));
        return matchedFiles.isEmpty() ? null : matchedFiles.getFirst();
    }

    private File buildNamedOutputFile(File outputDir, String filename) {
        File candidate = FileUtil.file(outputDir, filename);
        if (!candidate.exists()) {
            return candidate;
        }
        String mainName = FileUtil.mainName(filename);
        String extName = FileUtil.extName(filename);
        int index = 2;
        while (true) {
            String nextFilename = extName == null || extName.isBlank()
                    ? mainName + "_" + index
                    : mainName + "_" + index + "." + extName;
            File nextCandidate = FileUtil.file(outputDir, nextFilename);
            if (!nextCandidate.exists()) {
                return nextCandidate;
            }
            index++;
        }
    }

    private String buildOutputFilename(String originalFilename) {
        String mainName = originalFilename == null || originalFilename.isBlank() ? "source" : FileUtil.mainName(originalFilename);
        String extName = originalFilename == null || originalFilename.isBlank() ? "xlsx" : FileUtil.extName(originalFilename);
        return mainName + "_cleaned." + (extName == null || extName.isBlank() ? "xlsx" : extName);
    }

    private String normalizeCellText(String value) {
        if (value == null) {
            return "";
        }
        return value.replace("\ufeff", "")
                .replace("\u3000", " ")
                .replace("\r\n", " ")
                .replace("\r", " ")
                .replace("\n", " ")
                .replace("\t", " ")
                .trim()
                .replaceAll("\\s+", " ");
    }

    private record DedupeArchiveEntry(String relativePath, String inputName, File outputFile) {
    }
}
