package com.datamate.datamanagement.application;

import com.datamate.datamanagement.common.enums.KnowledgeItemPreviewStatus;
import com.datamate.datamanagement.domain.model.dataset.DatasetFile;
import com.datamate.datamanagement.infrastructure.config.DataManagementProperties;
import com.datamate.datamanagement.infrastructure.persistence.repository.DatasetFileRepository;
import com.fasterxml.jackson.databind.ObjectMapper;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.springframework.scheduling.annotation.Async;
import org.springframework.stereotype.Service;

import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.util.Set;

/**
 * 数据集文件预览转换异步任务
 */
@Service
@RequiredArgsConstructor
@Slf4j
public class DatasetFilePreviewAsyncService {
    private static final Set<String> OFFICE_EXTENSIONS = Set.of("doc", "docx");
    private static final String DATASET_PREVIEW_DIR = "dataset-previews";
    private static final String PREVIEW_FILE_SUFFIX = ".pdf";
    private static final String PATH_SEPARATOR = "/";
    private static final int MAX_ERROR_LENGTH = 500;
    private static final DateTimeFormatter PREVIEW_TIME_FORMATTER = DateTimeFormatter.ISO_LOCAL_DATE_TIME;

    private final DatasetFileRepository datasetFileRepository;
    private final DataManagementProperties dataManagementProperties;
    private final ObjectMapper objectMapper = new ObjectMapper();

    @Async
    public void convertPreviewAsync(String fileId) {
        if (StringUtils.isBlank(fileId)) {
            return;
        }
        DatasetFile file = datasetFileRepository.getById(fileId);
        if (file == null) {
            return;
        }
        String extension = resolveFileExtension(resolveOriginalName(file));
        if (!OFFICE_EXTENSIONS.contains(extension)) {
            updatePreviewStatus(file, KnowledgeItemPreviewStatus.FAILED, null, "仅支持 DOC/DOCX 转换");
            return;
        }
        if (StringUtils.isBlank(file.getFilePath())) {
            updatePreviewStatus(file, KnowledgeItemPreviewStatus.FAILED, null, "源文件路径为空");
            return;
        }
        Path sourcePath = Paths.get(file.getFilePath()).toAbsolutePath().normalize();
        if (!Files.exists(sourcePath) || !Files.isRegularFile(sourcePath)) {
            updatePreviewStatus(file, KnowledgeItemPreviewStatus.FAILED, null, "源文件不存在");
            return;
        }

        KnowledgeItemPreviewMetadataHelper.PreviewInfo previewInfo = KnowledgeItemPreviewMetadataHelper
            .readPreviewInfo(file.getMetadata(), objectMapper);
        String previewRelativePath = StringUtils.defaultIfBlank(
            previewInfo.pdfPath(),
            resolvePreviewRelativePath(file.getDatasetId(), file.getId())
        );
        Path targetPath = resolvePreviewStoragePath(previewRelativePath);

        try {
            ensureParentDirectory(targetPath);
            LibreOfficeConverter.convertToPdf(sourcePath, targetPath);
            updatePreviewStatus(file, KnowledgeItemPreviewStatus.READY, previewRelativePath, null);
        } catch (Exception e) {
            log.error("dataset preview convert failed, fileId: {}", file.getId(), e);
            updatePreviewStatus(file, KnowledgeItemPreviewStatus.FAILED, previewRelativePath, trimError(e.getMessage()));
        }
    }

    private void updatePreviewStatus(
        DatasetFile file,
        KnowledgeItemPreviewStatus status,
        String previewRelativePath,
        String error
    ) {
        if (file == null) {
            return;
        }
        String updatedMetadata = KnowledgeItemPreviewMetadataHelper.applyPreviewInfo(
            file.getMetadata(),
            objectMapper,
            status,
            previewRelativePath,
            error,
            nowText()
        );
        file.setMetadata(updatedMetadata);
        datasetFileRepository.updateById(file);
    }

    private String resolveOriginalName(DatasetFile file) {
        if (file == null) {
            return "";
        }
        if (StringUtils.isNotBlank(file.getFileName())) {
            return file.getFileName();
        }
        if (StringUtils.isNotBlank(file.getFilePath())) {
            return Paths.get(file.getFilePath()).getFileName().toString();
        }
        return "";
    }

    private String resolveFileExtension(String fileName) {
        if (StringUtils.isBlank(fileName)) {
            return "";
        }
        int dotIndex = fileName.lastIndexOf('.');
        if (dotIndex <= 0 || dotIndex >= fileName.length() - 1) {
            return "";
        }
        return fileName.substring(dotIndex + 1).toLowerCase();
    }

    private String resolvePreviewRelativePath(String datasetId, String fileId) {
        String relativePath = Paths.get(DATASET_PREVIEW_DIR, datasetId, fileId + PREVIEW_FILE_SUFFIX)
            .toString();
        return relativePath.replace("\\", PATH_SEPARATOR);
    }

    private Path resolvePreviewStoragePath(String relativePath) {
        String normalizedRelativePath = StringUtils.defaultString(relativePath).replace("/", java.io.File.separator);
        Path root = resolveUploadRootPath();
        Path target = root.resolve(normalizedRelativePath).toAbsolutePath().normalize();
        if (!target.startsWith(root)) {
            throw new IllegalArgumentException("invalid preview path");
        }
        return target;
    }

    private Path resolveUploadRootPath() {
        String uploadDir = dataManagementProperties.getFileStorage().getUploadDir();
        return Paths.get(uploadDir).toAbsolutePath().normalize();
    }

    private void ensureParentDirectory(Path targetPath) {
        try {
            Path parent = targetPath.getParent();
            if (parent != null) {
                Files.createDirectories(parent);
            }
        } catch (Exception e) {
            throw new IllegalStateException("创建预览目录失败", e);
        }
    }

    private String trimError(String error) {
        if (StringUtils.isBlank(error)) {
            return "";
        }
        if (error.length() <= MAX_ERROR_LENGTH) {
            return error;
        }
        return error.substring(0, MAX_ERROR_LENGTH);
    }

    private String nowText() {
        return LocalDateTime.now().format(PREVIEW_TIME_FORMATTER);
    }
}
