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.KnowledgeContentType;
import com.datamate.datamanagement.common.enums.KnowledgeItemPreviewStatus;
import com.datamate.datamanagement.common.enums.KnowledgeSourceType;
import com.datamate.datamanagement.domain.model.knowledge.KnowledgeItem;
import com.datamate.datamanagement.infrastructure.config.DataManagementProperties;
import com.datamate.datamanagement.infrastructure.persistence.repository.KnowledgeItemRepository;
import com.datamate.datamanagement.interfaces.dto.KnowledgeItemPreviewStatusResponse;
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 KnowledgeItemPreviewService {
    private static final Set<String> OFFICE_EXTENSIONS = Set.of("doc", "docx");
    private static final String KNOWLEDGE_ITEM_UPLOAD_DIR = "knowledge-items";
    private static final String PREVIEW_SUB_DIR = "preview";
    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 KnowledgeItemRepository knowledgeItemRepository;
    private final DataManagementProperties dataManagementProperties;
    private final KnowledgeItemPreviewAsyncService knowledgeItemPreviewAsyncService;
    private final ObjectMapper objectMapper = new ObjectMapper();

    public KnowledgeItemPreviewStatusResponse getPreviewStatus(String setId, String itemId) {
        KnowledgeItem item = requireKnowledgeItem(setId, itemId);
        assertOfficeDocument(item);
        KnowledgeItemPreviewMetadataHelper.PreviewInfo previewInfo = KnowledgeItemPreviewMetadataHelper
            .readPreviewInfo(item.getMetadata(), objectMapper);

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

        return buildResponse(previewInfo);
    }

    public KnowledgeItemPreviewStatusResponse ensurePreview(String setId, String itemId) {
        KnowledgeItem item = requireKnowledgeItem(setId, itemId);
        assertOfficeDocument(item);
        KnowledgeItemPreviewMetadataHelper.PreviewInfo previewInfo = KnowledgeItemPreviewMetadataHelper
            .readPreviewInfo(item.getMetadata(), objectMapper);

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

        String previewRelativePath = resolvePreviewRelativePath(item.getSetId(), item.getId());
        String updatedMetadata = KnowledgeItemPreviewMetadataHelper.applyPreviewInfo(
            item.getMetadata(),
            objectMapper,
            KnowledgeItemPreviewStatus.PROCESSING,
            previewRelativePath,
            null,
            nowText()
        );
        item.setMetadata(updatedMetadata);
        knowledgeItemRepository.updateById(item);
        knowledgeItemPreviewAsyncService.convertPreviewAsync(item.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 setId, KnowledgeItem item) {
        if (item == null) {
            return null;
        }
        KnowledgeItemPreviewMetadataHelper.PreviewInfo previewInfo = KnowledgeItemPreviewMetadataHelper
            .readPreviewInfo(item.getMetadata(), objectMapper);
        if (previewInfo.status() != KnowledgeItemPreviewStatus.READY) {
            return null;
        }
        String relativePath = StringUtils.defaultIfBlank(previewInfo.pdfPath(), resolvePreviewRelativePath(setId, item.getId()));
        Path filePath = resolvePreviewStoragePath(relativePath);
        if (!Files.exists(filePath) || !Files.isRegularFile(filePath)) {
            markPreviewFailed(item, previewInfo, "预览文件不存在");
            return null;
        }
        String previewName = resolvePreviewPdfName(item);
        return new PreviewFile(filePath, previewName);
    }

    public String clearPreviewMetadata(String metadata) {
        return KnowledgeItemPreviewMetadataHelper.clearPreviewInfo(metadata, objectMapper);
    }

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

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

    private KnowledgeItem requireKnowledgeItem(String setId, String itemId) {
        BusinessAssert.isTrue(StringUtils.isNotBlank(setId), CommonErrorCode.PARAM_ERROR);
        BusinessAssert.isTrue(StringUtils.isNotBlank(itemId), CommonErrorCode.PARAM_ERROR);
        KnowledgeItem knowledgeItem = knowledgeItemRepository.getById(itemId);
        BusinessAssert.notNull(knowledgeItem, CommonErrorCode.PARAM_ERROR);
        BusinessAssert.isTrue(Objects.equals(knowledgeItem.getSetId(), setId), CommonErrorCode.PARAM_ERROR);
        return knowledgeItem;
    }

    private void assertOfficeDocument(KnowledgeItem item) {
        BusinessAssert.notNull(item, CommonErrorCode.PARAM_ERROR);
        BusinessAssert.isTrue(
            item.getContentType() == KnowledgeContentType.FILE || item.getSourceType() == KnowledgeSourceType.FILE_UPLOAD,
            CommonErrorCode.PARAM_ERROR
        );
        String extension = resolveFileExtension(resolveOriginalName(item));
        BusinessAssert.isTrue(OFFICE_EXTENSIONS.contains(extension), CommonErrorCode.PARAM_ERROR);
    }

    private String resolveOriginalName(KnowledgeItem item) {
        if (item == null) {
            return "";
        }
        if (StringUtils.isNotBlank(item.getSourceFileId())) {
            return item.getSourceFileId();
        }
        if (StringUtils.isNotBlank(item.getContent())) {
            return Paths.get(item.getContent()).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(KnowledgeItem item) {
        String originalName = resolveOriginalName(item);
        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(KnowledgeItem item, KnowledgeItemPreviewMetadataHelper.PreviewInfo previewInfo) {
        String relativePath = StringUtils.defaultIfBlank(previewInfo.pdfPath(), resolvePreviewRelativePath(item.getSetId(), item.getId()));
        Path filePath = resolvePreviewStoragePath(relativePath);
        return Files.exists(filePath) && Files.isRegularFile(filePath);
    }

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

    private String resolvePreviewRelativePath(String setId, String itemId) {
        String relativePath = Paths.get(KNOWLEDGE_ITEM_UPLOAD_DIR, setId, PREVIEW_SUB_DIR, itemId + 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();
        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) {
    }
}
