package com.datamate.datamanagement.application;

import com.datamate.common.infrastructure.exception.BusinessAssert;
import com.datamate.common.infrastructure.exception.CommonErrorCode;
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.datamate.datamanagement.interfaces.dto.DatasetFilePreviewStatusResponse;
import com.fasterxml.jackson.databind.ObjectMapper;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
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.Objects;
import java.util.Set;

/**
 * 数据集文件预览转换服务
 */
@Service
@RequiredArgsConstructor
@Slf4j
public class DatasetFilePreviewService {
    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 DateTimeFormatter PREVIEW_TIME_FORMATTER = DateTimeFormatter.ISO_LOCAL_DATE_TIME;

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

    public DatasetFilePreviewStatusResponse getPreviewStatus(String datasetId, String fileId) {
        DatasetFile file = requireDatasetFile(datasetId, fileId);
        assertOfficeDocument(file);
        KnowledgeItemPreviewMetadataHelper.PreviewInfo previewInfo = KnowledgeItemPreviewMetadataHelper
            .readPreviewInfo(file.getMetadata(), objectMapper);

        if (previewInfo.status() == KnowledgeItemPreviewStatus.READY && !previewPdfExists(file, previewInfo)) {
            previewInfo = markPreviewFailed(file, previewInfo, "预览文件不存在");
        }

        return buildResponse(previewInfo);
    }

    public DatasetFilePreviewStatusResponse ensurePreview(String datasetId, String fileId) {
        DatasetFile file = requireDatasetFile(datasetId, fileId);
        assertOfficeDocument(file);
        KnowledgeItemPreviewMetadataHelper.PreviewInfo previewInfo = KnowledgeItemPreviewMetadataHelper
            .readPreviewInfo(file.getMetadata(), objectMapper);

        if (previewInfo.status() == KnowledgeItemPreviewStatus.READY && previewPdfExists(file, previewInfo)) {
            return buildResponse(previewInfo);
        }
        if (previewInfo.status() == KnowledgeItemPreviewStatus.PROCESSING) {
            return buildResponse(previewInfo);
        }

        String previewRelativePath = resolvePreviewRelativePath(file.getDatasetId(), file.getId());
        String updatedMetadata = KnowledgeItemPreviewMetadataHelper.applyPreviewInfo(
            file.getMetadata(),
            objectMapper,
            KnowledgeItemPreviewStatus.PROCESSING,
            previewRelativePath,
            null,
            nowText()
        );
        file.setMetadata(updatedMetadata);
        datasetFileRepository.updateById(file);
        datasetFilePreviewAsyncService.convertPreviewAsync(file.getId());

        KnowledgeItemPreviewMetadataHelper.PreviewInfo refreshed = KnowledgeItemPreviewMetadataHelper
            .readPreviewInfo(updatedMetadata, objectMapper);
        return buildResponse(refreshed);
    }

    public boolean isOfficeDocument(String fileName) {
        String extension = resolveFileExtension(fileName);
        return StringUtils.isNotBlank(extension) && OFFICE_EXTENSIONS.contains(extension.toLowerCase());
    }

    public PreviewFile resolveReadyPreviewFile(String datasetId, DatasetFile file) {
        if (file == null) {
            return null;
        }
        KnowledgeItemPreviewMetadataHelper.PreviewInfo previewInfo = KnowledgeItemPreviewMetadataHelper
            .readPreviewInfo(file.getMetadata(), objectMapper);
        if (previewInfo.status() != KnowledgeItemPreviewStatus.READY) {
            return null;
        }
        String relativePath = StringUtils.defaultIfBlank(previewInfo.pdfPath(), resolvePreviewRelativePath(datasetId, file.getId()));
        Path filePath = resolvePreviewStoragePath(relativePath);
        if (!Files.exists(filePath) || !Files.isRegularFile(filePath)) {
            markPreviewFailed(file, previewInfo, "预览文件不存在");
            return null;
        }
        String previewName = resolvePreviewPdfName(file);
        return new PreviewFile(filePath, previewName);
    }

    public void deletePreviewFileQuietly(String datasetId, String fileId) {
        String relativePath = resolvePreviewRelativePath(datasetId, fileId);
        Path filePath = resolvePreviewStoragePath(relativePath);
        try {
            Files.deleteIfExists(filePath);
        } catch (Exception e) {
            log.warn("delete dataset preview pdf error, fileId: {}", fileId, e);
        }
    }

    private DatasetFilePreviewStatusResponse buildResponse(KnowledgeItemPreviewMetadataHelper.PreviewInfo previewInfo) {
        DatasetFilePreviewStatusResponse response = new DatasetFilePreviewStatusResponse();
        KnowledgeItemPreviewStatus status = previewInfo.status() == null
            ? KnowledgeItemPreviewStatus.PENDING
            : previewInfo.status();
        response.setStatus(status);
        response.setPreviewError(previewInfo.error());
        response.setUpdatedAt(previewInfo.updatedAt());
        return response;
    }

    private DatasetFile requireDatasetFile(String datasetId, String fileId) {
        BusinessAssert.isTrue(StringUtils.isNotBlank(datasetId), CommonErrorCode.PARAM_ERROR);
        BusinessAssert.isTrue(StringUtils.isNotBlank(fileId), CommonErrorCode.PARAM_ERROR);
        DatasetFile datasetFile = datasetFileRepository.getById(fileId);
        BusinessAssert.notNull(datasetFile, CommonErrorCode.PARAM_ERROR);
        BusinessAssert.isTrue(Objects.equals(datasetFile.getDatasetId(), datasetId), CommonErrorCode.PARAM_ERROR);
        return datasetFile;
    }

    private void assertOfficeDocument(DatasetFile file) {
        BusinessAssert.notNull(file, CommonErrorCode.PARAM_ERROR);
        String extension = resolveFileExtension(resolveOriginalName(file));
        BusinessAssert.isTrue(OFFICE_EXTENSIONS.contains(extension), CommonErrorCode.PARAM_ERROR);
    }

    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 resolvePreviewPdfName(DatasetFile file) {
        String originalName = resolveOriginalName(file);
        if (StringUtils.isBlank(originalName)) {
            return "预览.pdf";
        }
        int dotIndex = originalName.lastIndexOf('.');
        if (dotIndex <= 0) {
            return originalName + PREVIEW_FILE_SUFFIX;
        }
        return originalName.substring(0, dotIndex) + PREVIEW_FILE_SUFFIX;
    }

    private boolean previewPdfExists(DatasetFile file, KnowledgeItemPreviewMetadataHelper.PreviewInfo previewInfo) {
        String relativePath = StringUtils.defaultIfBlank(previewInfo.pdfPath(), resolvePreviewRelativePath(file.getDatasetId(), file.getId()));
        Path filePath = resolvePreviewStoragePath(relativePath);
        return Files.exists(filePath) && Files.isRegularFile(filePath);
    }

    private KnowledgeItemPreviewMetadataHelper.PreviewInfo markPreviewFailed(
        DatasetFile file,
        KnowledgeItemPreviewMetadataHelper.PreviewInfo previewInfo,
        String error
    ) {
        String relativePath = StringUtils.defaultIfBlank(previewInfo.pdfPath(), resolvePreviewRelativePath(file.getDatasetId(), file.getId()));
        String updatedMetadata = KnowledgeItemPreviewMetadataHelper.applyPreviewInfo(
            file.getMetadata(),
            objectMapper,
            KnowledgeItemPreviewStatus.FAILED,
            relativePath,
            error,
            nowText()
        );
        file.setMetadata(updatedMetadata);
        datasetFileRepository.updateById(file);
        return KnowledgeItemPreviewMetadataHelper.readPreviewInfo(updatedMetadata, objectMapper);
    }

    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);
    }

    Path resolvePreviewStoragePath(String relativePath) {
        String normalizedRelativePath = StringUtils.defaultString(relativePath).replace("/", java.io.File.separator);
        Path root = resolveUploadRootPath();
        Path target = root.resolve(normalizedRelativePath).toAbsolutePath().normalize();
        BusinessAssert.isTrue(target.startsWith(root), CommonErrorCode.PARAM_ERROR);
        return target;
    }

    private Path resolveUploadRootPath() {
        String uploadDir = dataManagementProperties.getFileStorage().getUploadDir();
        BusinessAssert.isTrue(StringUtils.isNotBlank(uploadDir), CommonErrorCode.PARAM_ERROR);
        return Paths.get(uploadDir).toAbsolutePath().normalize();
    }

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

    public record PreviewFile(Path filePath, String fileName) {
    }
}
