package com.njcn.gather.aireport.testreport.component;

import cn.hutool.core.util.StrUtil;
import com.njcn.common.pojo.enums.response.CommonResponseEnum;
import com.njcn.common.pojo.exception.BusinessException;
import com.njcn.gather.aireport.common.constant.AiReportFileConst;
import com.njcn.gather.aireport.common.constant.AiReportFileExtensionConst;
import com.njcn.gather.aireport.common.storage.AiReportMinioStorageService;
import com.njcn.gather.aireport.testreport.mapper.TestReportMapper;
import com.njcn.gather.aireport.testreport.pojo.constant.TestReportConst;
import lombok.RequiredArgsConstructor;
import org.springframework.http.MediaType;
import org.springframework.stereotype.Component;
import org.springframework.web.multipart.MultipartFile;

import java.io.InputStream;
import java.nio.charset.StandardCharsets;
import java.util.Arrays;
import java.util.Collections;
import java.util.LinkedHashSet;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.Set;

@Component
@RequiredArgsConstructor
public class TestReportStorageService {

    private static final Set<String> POINT_WORD_ATTACHMENT_EXTENSIONS = Collections.unmodifiableSet(
            new LinkedHashSet<String>(Arrays.asList("doc", "docx")));

    private static final Set<String> IMPORT_ALLOWED_EXTENSIONS = Collections.unmodifiableSet(
            new LinkedHashSet<String>(Arrays.asList("doc", "docx", "xls", "xlsx", "pdf")));

    private final AiReportMinioStorageService minioStorageService;
    private final TestReportMapper testReportMapper;

    public String saveSourceExcel(String reportId, MultipartFile file) {
        validateMultipartFile(file, AiReportFileExtensionConst.EXCEL_EXTENSIONS, "原始Excel文件不能为空", "原始Excel仅允许xls/xlsx格式");
        return minioStorageService.saveMultipartFile(buildBusinessDir(reportId, TestReportConst.STORAGE_SOURCE_EXCEL_DIR),
                null, file, false).getObjectName();
    }

    public String saveLedgerSummaryExcel(String reportId, MultipartFile file) {
        validateMultipartFile(file, AiReportFileExtensionConst.EXCEL_EXTENSIONS, "台账汇总文件不能为空", "台账汇总仅允许xls/xlsx格式");
        return minioStorageService.saveMultipartFile(buildBusinessDir(reportId, TestReportConst.STORAGE_LEDGER_SUMMARY_DIR),
                null, file, false).getObjectName();
    }

    public String savePointExcelAttachment(String reportId, MultipartFile file) {
        validateMultipartFile(file, AiReportFileExtensionConst.EXCEL_EXTENSIONS, "监测点Excel附件不能为空", "监测点Excel附件仅允许xls/xlsx格式");
        return minioStorageService.saveMultipartFile(buildBusinessDir(reportId, TestReportConst.STORAGE_POINT_EXCEL_DIR),
                null, file, false).getObjectName();
    }

    public String savePointWordAttachment(String reportId, MultipartFile file) {
        validateMultipartFile(file, POINT_WORD_ATTACHMENT_EXTENSIONS, "监测点Word附件不能为空", "监测点Word附件仅允许doc/docx格式");
        return minioStorageService.saveMultipartFile(buildBusinessDir(reportId, TestReportConst.STORAGE_POINT_WORD_DIR),
                null, file, false).getObjectName();
    }

    public String saveLedgerSummaryBytes(String reportId, String fileName, byte[] content) {
        validateFileName(fileName, AiReportFileExtensionConst.EXCEL_EXTENSIONS, "台账汇总文件不能为空", "台账汇总仅允许xls/xlsx格式");
        return minioStorageService.saveBytes(buildBusinessDir(reportId, TestReportConst.STORAGE_LEDGER_SUMMARY_DIR),
                null, fileName, content, resolveExcelContentType(fileName), false).getObjectName();
    }

    public String saveLedgerSummaryBytes(String reportId, String multiId, String fileName, byte[] content) {
        validateFileName(fileName, AiReportFileExtensionConst.EXCEL_EXTENSIONS, "台账汇总文件不能为空", "台账汇总仅允许xls/xlsx格式");
        return minioStorageService.saveBytes(buildBusinessDir(reportId, multiId, TestReportConst.STORAGE_LEDGER_SUMMARY_DIR),
                null, fileName, content, resolveExcelContentType(fileName), false).getObjectName();
    }

    public String savePointExcelBytes(String reportId, String fileName, byte[] content) {
        validateFileName(fileName, AiReportFileExtensionConst.EXCEL_EXTENSIONS, "监测点Excel附件不能为空", "监测点Excel附件仅允许xls/xlsx格式");
        return minioStorageService.saveBytes(buildBusinessDir(reportId, TestReportConst.STORAGE_POINT_EXCEL_DIR),
                null, fileName, content, resolveExcelContentType(fileName), false).getObjectName();
    }

    public String savePointExcelBytes(String reportId, String multiId, String fileName, byte[] content) {
        validateFileName(fileName, AiReportFileExtensionConst.EXCEL_EXTENSIONS, "监测点Excel附件不能为空", "监测点Excel附件仅允许xls/xlsx格式");
        return minioStorageService.saveBytes(buildBusinessDir(reportId, multiId, TestReportConst.STORAGE_POINT_EXCEL_DIR),
                null, fileName, content, resolveExcelContentType(fileName), false).getObjectName();
    }

    public String savePointWordBytes(String reportId, String fileName, byte[] content) {
        validateFileName(fileName, POINT_WORD_ATTACHMENT_EXTENSIONS, "监测点Word附件不能为空", "监测点Word附件仅允许doc/docx格式");
        return minioStorageService.saveBytes(buildBusinessDir(reportId, TestReportConst.STORAGE_POINT_WORD_DIR),
                null, fileName, content, resolveWordContentType(fileName), false).getObjectName();
    }

    public String savePointWordBytes(String reportId, String multiId, String fileName, byte[] content) {
        validateFileName(fileName, POINT_WORD_ATTACHMENT_EXTENSIONS, "监测点Word附件不能为空", "监测点Word附件仅允许doc/docx格式");
        return minioStorageService.saveBytes(buildBusinessDir(reportId, multiId, TestReportConst.STORAGE_POINT_WORD_DIR),
                null, fileName, content, resolveWordContentType(fileName), false).getObjectName();
    }

    public UploadedBatch saveTempBatchFiles(String reportId, String batchId, Map<String, MultipartFile> fileMap) {
        return saveBatchFiles(buildTempUploadDir(reportId, batchId), buildTempBatchRelativePath(reportId, batchId),
                batchId, fileMap, "文件上传阶段：保存临时文件失败");
    }

    public UploadedBatch saveValidateSessionFiles(String sessionId, Map<String, MultipartFile> fileMap) {
        return saveBatchFiles(buildValidateSessionUploadDir(sessionId), buildValidateSessionRelativePath(sessionId),
                sessionId, fileMap, "校验文件阶段：保存校验会话文件失败");
    }

    public AiReportMinioStorageService.StoredObject getTempBatchObject(String reportId, String batchId, String fileName) {
        return minioStorageService.getObject(buildTempBatchFileObjectName(reportId, batchId, fileName));
    }

    public AiReportMinioStorageService.StoredObject getValidateSessionObject(String sessionId, String fileName) {
        return minioStorageService.getObject(buildValidateSessionFileObjectName(sessionId, fileName));
    }

    public void writeTempBatchMeta(String reportId, String batchId, String content) {
        writeText(buildTempBatchMetaObjectName(reportId, batchId), content, "校验文件阶段：保存批次元数据失败");
    }

    public void writeValidateSessionResult(String sessionId, String content) {
        writeText(buildValidateSessionResultObjectName(sessionId), content, "校验文件阶段：保存校验结果失败");
    }

    public List<String> listValidateSessionFileNames(String sessionId) {
        return minioStorageService.listFileNames(buildValidateSessionUploadDir(sessionId));
    }

    public String readTempBatchMeta(String reportId, String batchId) {
        String objectName = buildTempBatchMetaObjectName(reportId, batchId);
        if (!minioStorageService.exists(objectName)) {
            return null;
        }
        return new String(minioStorageService.readBytes(objectName), StandardCharsets.UTF_8);
    }

    public String saveLedgerSummaryFromTemp(String reportId, String batchId, String fileName) {
        return copyTempBatchFile(reportId, batchId, fileName,
                buildBusinessDir(reportId, TestReportConst.STORAGE_LEDGER_SUMMARY_DIR),
                resolveExcelContentType(fileName), "保存台账汇总excel失败");
    }

    public String savePointExcelFromTemp(String reportId, String batchId, String fileName) {
        return copyTempBatchFile(reportId, batchId, fileName,
                buildBusinessDir(reportId, TestReportConst.STORAGE_POINT_EXCEL_DIR),
                resolveExcelContentType(fileName), "保存监测点excel附件失败");
    }

    public String savePointWordFromTemp(String reportId, String batchId, String fileName) {
        return copyTempBatchFile(reportId, batchId, fileName,
                buildBusinessDir(reportId, TestReportConst.STORAGE_POINT_WORD_DIR),
                resolveWordContentType(fileName), "保存监测点Word附件失败");
    }

    public String saveGeneratedPlaceholder(String reportId, String fileName, byte[] content) {
        validateFileName(fileName, AiReportFileExtensionConst.WORD_EXTENSIONS, "生成报告文件名不能为空", "生成报告仅允许docx格式");
        return minioStorageService.saveBytes(buildBusinessDir(reportId, TestReportConst.STORAGE_GENERATED_REPORT_DIR),
                null, fileName, content, MediaType.APPLICATION_OCTET_STREAM_VALUE, false).getObjectName();
    }

    public String saveGeneratedPlaceholder(String reportId, String multiId, String fileName, byte[] content) {
        validateFileName(fileName, AiReportFileExtensionConst.WORD_EXTENSIONS, "生成报告文件名不能为空", "生成报告仅允许docx格式");
        return minioStorageService.saveBytes(buildBusinessDir(reportId, multiId, TestReportConst.STORAGE_GENERATED_REPORT_DIR),
                null, fileName, content, MediaType.APPLICATION_OCTET_STREAM_VALUE, false).getObjectName();
    }

    public byte[] readBusinessBytes(String storedPath) {
        return minioStorageService.readBytes(storedPath);
    }

    public void deleteQuietly(String storedPath) {
        minioStorageService.deleteQuietly(storedPath);
    }

    public void deleteTempBatchQuietly(String reportId, String batchId) {
        deletePrefixQuietly(buildTempBatchDir(reportId, batchId));
    }

    private UploadedBatch saveBatchFiles(String bizDir, String tempStoragePath, String batchId,
                                         Map<String, MultipartFile> fileMap, String errorMessage) {
        if (fileMap == null || fileMap.isEmpty()) {
            throw new BusinessException(CommonResponseEnum.FAIL, "文件不能为空");
        }
        Map<String, String> storedFileMap = new LinkedHashMap<String, String>();
        try {
            for (Map.Entry<String, MultipartFile> entry : fileMap.entrySet()) {
                String originalFileName = trimFileName(entry.getKey());
                MultipartFile file = entry.getValue();
                if (StrUtil.isBlank(originalFileName) || file == null || file.isEmpty()) {
                    throw new BusinessException(CommonResponseEnum.FAIL, "文件不能为空");
                }
                validateAllowedUploadFile(originalFileName);
                try (InputStream inputStream = file.getInputStream()) {
                    AiReportMinioStorageService.StoredFile storedFile = minioStorageService.saveInputStream(
                            bizDir, null, originalFileName, inputStream, file.getSize(), file.getContentType(), true);
                    storedFileMap.put(originalFileName, storedFile.getObjectName());
                }
            }
        } catch (BusinessException exception) {
            cleanupStoredFiles(storedFileMap);
            throw exception;
        } catch (Exception exception) {
            cleanupStoredFiles(storedFileMap);
            throw new BusinessException(CommonResponseEnum.FAIL, errorMessage);
        }
        UploadedBatch uploadedBatch = new UploadedBatch();
        uploadedBatch.setBatchId(batchId);
        uploadedBatch.setTempStoragePath(tempStoragePath);
        uploadedBatch.setStoredFileMap(storedFileMap);
        return uploadedBatch;
    }

    private void writeText(String objectName, String content, String errorMessage) {
        try {
            minioStorageService.saveBytes(resolveParentDir(objectName), null, resolveFileName(objectName),
                    content == null ? new byte[0] : content.getBytes(StandardCharsets.UTF_8),
                    MediaType.APPLICATION_JSON_VALUE, true);
        } catch (BusinessException exception) {
            throw exception;
        } catch (Exception exception) {
            throw new BusinessException(CommonResponseEnum.FAIL, errorMessage);
        }
    }

    private String copyTempBatchFile(String reportId, String batchId, String fileName, String targetBizDir,
                                     String contentType, String errorMessage) {
        String normalizedFileName = trimFileName(fileName);
        if (StrUtil.isBlank(normalizedFileName)) {
            return null;
        }
        try {
            byte[] content = minioStorageService.readBytes(buildTempBatchFileObjectName(reportId, batchId, normalizedFileName));
            return minioStorageService.saveBytes(targetBizDir, null, normalizedFileName, content, contentType, false)
                    .getObjectName();
        } catch (BusinessException exception) {
            throw exception;
        } catch (Exception exception) {
            throw new BusinessException(CommonResponseEnum.FAIL, errorMessage);
        }
    }

    private void deletePrefixQuietly(String prefix) {
        List<String> objectNames = minioStorageService.listObjectNames(prefix);
        for (String objectName : objectNames) {
            minioStorageService.deleteQuietly(objectName);
        }
    }

    private void cleanupStoredFiles(Map<String, String> storedFileMap) {
        if (storedFileMap == null || storedFileMap.isEmpty()) {
            return;
        }
        for (String objectName : storedFileMap.values()) {
            minioStorageService.deleteQuietly(objectName);
        }
    }

    private void validateMultipartFile(MultipartFile file, Set<String> allowedExtensions,
                                       String emptyMessage, String invalidMessage) {
        if (file == null || file.isEmpty()) {
            throw new BusinessException(CommonResponseEnum.FAIL, emptyMessage);
        }
        validateFileName(file.getOriginalFilename(), allowedExtensions, emptyMessage, invalidMessage);
    }

    private void validateAllowedUploadFile(String fileName) {
        validateFileName(fileName, IMPORT_ALLOWED_EXTENSIONS, "文件不能为空",
                "仅允许上传doc、docx、xls、xlsx、pdf文件");
    }

    private String resolveExcelContentType(String fileName) {
        String extension = resolveExtension(fileName);
        if ("xls".equals(extension)) {
            return "application/vnd.ms-excel";
        }
        return "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet";
    }

    private String resolveWordContentType(String fileName) {
        String extension = resolveExtension(fileName);
        if ("doc".equals(extension)) {
            return "application/msword";
        }
        return "application/vnd.openxmlformats-officedocument.wordprocessingml.document";
    }

    private void validateFileName(String fileName, Set<String> allowedExtensions,
                                  String emptyMessage, String invalidMessage) {
        String normalizedFileName = trimFileName(fileName);
        if (StrUtil.isBlank(normalizedFileName)) {
            throw new BusinessException(CommonResponseEnum.FAIL, emptyMessage);
        }
        String extension = resolveExtension(normalizedFileName);
        if (!allowedExtensions.contains(extension)) {
            throw new BusinessException(CommonResponseEnum.FAIL, invalidMessage);
        }
    }

    private String buildBusinessDir(String reportId, String childDir) {
        return buildBusinessDir(reportId, resolveReportMultiId(reportId), childDir);
    }

    private String buildBusinessDir(String reportId, String multiId, String childDir) {
        if (StrUtil.isNotBlank(multiId)) {
            return AiReportFileConst.BIZ_DIR_TEST_REPORT + "/multi-task/" + multiId + "/child-report/" + reportId + "/" + childDir;
        }
        return AiReportFileConst.BIZ_DIR_TEST_REPORT + "/" + reportId + "/" + childDir;
    }

    private String buildTempBatchDir(String reportId, String batchId) {
        String multiId = resolveReportMultiId(reportId);
        if (StrUtil.isNotBlank(multiId)) {
            return AiReportFileConst.BIZ_DIR_TEST_REPORT + "/multi-task/" + multiId + "/child-report/" + reportId + "/"
                    + TestReportConst.STORAGE_IMPORT_TEMP_DIR + "/" + batchId;
        }
        return AiReportFileConst.BIZ_DIR_TEST_REPORT + "/" + reportId + "/"
                + TestReportConst.STORAGE_IMPORT_TEMP_DIR + "/" + batchId;
    }

    private String buildTempUploadDir(String reportId, String batchId) {
        return buildTempBatchDir(reportId, batchId) + "/" + TestReportConst.STORAGE_IMPORT_TEMP_UPLOAD_DIR;
    }

    private String buildValidateSessionDir(String sessionId) {
        return AiReportFileConst.BIZ_DIR_TEST_REPORT + "/" + TestReportConst.STORAGE_VALIDATE_SESSION_DIR + "/" + sessionId;
    }

    private String buildValidateSessionUploadDir(String sessionId) {
        return buildValidateSessionDir(sessionId) + "/" + TestReportConst.STORAGE_IMPORT_TEMP_UPLOAD_DIR;
    }

    private String buildTempBatchMetaObjectName(String reportId, String batchId) {
        return buildTempBatchDir(reportId, batchId) + "/" + TestReportConst.STORAGE_IMPORT_META_FILE_NAME;
    }

    private String buildValidateSessionResultObjectName(String sessionId) {
        return buildValidateSessionDir(sessionId) + "/" + TestReportConst.STORAGE_VALIDATE_RESULT_FILE_NAME;
    }

    private String buildTempBatchFileObjectName(String reportId, String batchId, String fileName) {
        return buildTempUploadDir(reportId, batchId) + "/" + trimFileName(fileName);
    }

    private String buildValidateSessionFileObjectName(String sessionId, String fileName) {
        return buildValidateSessionUploadDir(sessionId) + "/" + trimFileName(fileName);
    }

    private String buildTempBatchRelativePath(String reportId, String batchId) {
        String multiId = resolveReportMultiId(reportId);
        if (StrUtil.isNotBlank(multiId)) {
            return "multi-task/" + multiId + "/child-report/" + reportId + "/" + TestReportConst.STORAGE_IMPORT_TEMP_DIR + "/" + batchId;
        }
        return reportId + "/" + TestReportConst.STORAGE_IMPORT_TEMP_DIR + "/" + batchId;
    }

    private String resolveReportMultiId(String reportId) {
        String normalizedReportId = trimFileName(reportId);
        if (StrUtil.isBlank(normalizedReportId) || testReportMapper == null) {
            return null;
        }
        return StrUtil.trimToNull(testReportMapper.selectReportMultiId(normalizedReportId));
    }

    private String buildValidateSessionRelativePath(String sessionId) {
        return TestReportConst.STORAGE_VALIDATE_SESSION_DIR + "/" + sessionId;
    }

    private String resolveParentDir(String objectName) {
        int slashIndex = objectName.lastIndexOf('/');
        if (slashIndex <= 0) {
            throw new BusinessException(CommonResponseEnum.FAIL, "对象目录配置不合法");
        }
        return objectName.substring(0, slashIndex);
    }

    private String resolveFileName(String objectName) {
        int slashIndex = objectName.lastIndexOf('/');
        return slashIndex >= 0 ? objectName.substring(slashIndex + 1) : objectName;
    }

    private String trimFileName(String fileName) {
        String normalized = StrUtil.trimToEmpty(fileName);
        if (StrUtil.isBlank(normalized)) {
            return null;
        }
        int slashIndex = Math.max(normalized.lastIndexOf('/'), normalized.lastIndexOf('\\'));
        return slashIndex >= 0 ? normalized.substring(slashIndex + 1) : normalized;
    }

    private String resolveExtension(String fileName) {
        int dotIndex = fileName.lastIndexOf('.');
        if (dotIndex < 0 || dotIndex == fileName.length() - 1) {
            return "";
        }
        return fileName.substring(dotIndex + 1).toLowerCase(Locale.ENGLISH);
    }

    public static class UploadedBatch {
        private String batchId;
        private String tempStoragePath;
        private Map<String, String> storedFileMap = new LinkedHashMap<String, String>();

        public String getBatchId() {
            return batchId;
        }

        public void setBatchId(String batchId) {
            this.batchId = batchId;
        }

        public String getTempStoragePath() {
            return tempStoragePath;
        }

        public void setTempStoragePath(String tempStoragePath) {
            this.tempStoragePath = tempStoragePath;
        }

        public Map<String, String> getStoredFileMap() {
            return storedFileMap;
        }

        public void setStoredFileMap(Map<String, String> storedFileMap) {
            this.storedFileMap = storedFileMap;
        }
    }
}
