package tech.easyflow.ai.node;

import cn.hutool.http.HttpUtil;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.stereotype.Component;
import tech.easyflow.ai.document.model.DocumentParseScenario;
import tech.easyflow.ai.document.model.DocumentParsedResult;
import tech.easyflow.ai.document.model.DocumentSourceRef;
import tech.easyflow.ai.document.service.DocumentParseBridgeService;
import tech.easyflow.ai.document.support.DocumentParseSourceType;
import tech.easyflow.common.filestorage.FileStorageService;
import tech.easyflow.common.util.StringUtil;
import tech.easyflow.common.web.exceptions.BusinessException;

import java.io.IOException;
import java.io.InputStream;
import java.util.ArrayList;
import java.util.Collection;
import java.util.LinkedHashMap;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;

/**
 * {@link DocNode} 文件内容提取器。
 *
 * <p>负责把工作流运行态中的文件对象转换为统一文档源，并根据文件类型优先选择
 * 统一文档解析桥接服务；仅在桥接不支持时回退到默认读取器。</p>
 *
 * @author Codex
 * @since 2026-04-14
 */
@Component
public class DocNodeFileContentExtractor {
    private static final int FILE_MAX_COUNT = 10;
    private static final long FILE_MAX_SINGLE_SIZE = 5L * 1024 * 1024;
    private static final long FILE_MAX_TOTAL_SIZE = 50L * 1024 * 1024;

    private final DocumentParseBridgeService documentParseBridgeService;
    private final FileStorageService fileStorageService;
    private final ReaderManager readerManager;

    /**
     * 创建文件内容提取器。
     *
     * @param documentParseBridgeService 统一文档解析桥接服务
     * @param fileStorageService 文件存储服务
     * @param readerManager 默认读取器管理器
     */
    public DocNodeFileContentExtractor(DocumentParseBridgeService documentParseBridgeService,
                                       @Qualifier("default") FileStorageService fileStorageService,
                                       ReaderManager readerManager) {
        this.documentParseBridgeService = documentParseBridgeService;
        this.fileStorageService = fileStorageService;
        this.readerManager = readerManager;
    }

    /**
     * 提取文件文本内容。
     *
     * @param fileValue 工作流运行态中的文件对象
     * @return 可供下游节点直接消费的文本
     */
    public String extract(Object fileValue) {
        DocumentSourceRef sourceRef = toDocumentSourceRef(fileValue);
        validateSourceRef(sourceRef);
        if (shouldUseDocumentBridge(sourceRef)) {
            return extractBridgeContent(sourceRef);
        }
        return extractDefaultContent(sourceRef);
    }

    /**
     * 批量提取文件文本内容。
     *
     * @param fileValue 工作流运行态中的单文件或多文件值
     * @return 逐文件解析结果
     */
    public List<DocExtractedDocument> extractDocuments(Object fileValue) {
        List<DocumentSourceRef> sourceRefs = toDocumentSourceRefs(fileValue);
        List<DocExtractedDocument> results = new ArrayList<>();
        for (int index = 0; index < sourceRefs.size(); index++) {
            DocumentSourceRef sourceRef = sourceRefs.get(index);
            try {
                String content = shouldUseDocumentBridge(sourceRef)
                        ? extractBridgeContent(sourceRef)
                        : extractDefaultContent(sourceRef);
                results.add(new DocExtractedDocument(sourceRef.getFileName(), content));
            } catch (Exception e) {
                String fileName = StringUtil.hasText(sourceRef.getFileName()) ? sourceRef.getFileName() : ("#" + (index + 1));
                if (e instanceof BusinessException businessException) {
                    throw new BusinessException("文件解析失败(" + fileName + "): " + businessException.getMessage());
                }
                throw new RuntimeException("文件解析失败(" + fileName + ")", e);
            }
        }
        return results;
    }

    /**
     * 将运行时文件值转换为统一文档源。
     *
     * @param fileValue 运行时文件值
     * @return 文档源
     */
    DocumentSourceRef toDocumentSourceRef(Object fileValue) {
        if (fileValue instanceof DocumentSourceRef sourceRef) {
            return sourceRef;
        }
        if (!(fileValue instanceof Map<?, ?> fileMap)) {
            throw new BusinessException("文件输入格式不正确，必须为文件对象");
        }
        DocumentSourceRef sourceRef = new DocumentSourceRef();
        sourceRef.setFileName(asText(fileMap.get("fileName")));
        sourceRef.setFilePath(asText(fileMap.get("filePath")));
        sourceRef.setContentType(asText(fileMap.get("contentType")));
        sourceRef.setUrl(asText(fileMap.get("url")));
        sourceRef.setSize(asLong(fileMap.get("size")));
        return sourceRef;
    }

    /**
     * 将单文件或多文件运行值归一化为统一文档源列表。
     *
     * @param fileValue 运行态文件值
     * @return 文档源列表
     */
    List<DocumentSourceRef> toDocumentSourceRefs(Object fileValue) {
        List<Object> candidates = new ArrayList<>();
        collectFileValues(fileValue, candidates);
        if (candidates.isEmpty()) {
            throw new BusinessException("文件输入不能为空");
        }

        List<DocumentSourceRef> sourceRefs = new ArrayList<>();
        Set<String> seenFilePaths = new LinkedHashSet<>();
        long totalSize = 0L;
        for (Object candidate : candidates) {
            DocumentSourceRef sourceRef = toDocumentSourceRef(candidate);
            validateSourceRef(sourceRef);
            String filePath = sourceRef.getFilePath().trim();
            if (!seenFilePaths.add(filePath)) {
                continue;
            }
            Long size = sourceRef.getSize();
            if (size != null && size > FILE_MAX_SINGLE_SIZE) {
                throw new BusinessException("单个文件不能超过 5MB: " + sourceRef.getFileName());
            }
            if (size != null && size > 0) {
                totalSize += size;
            }
            sourceRefs.add(sourceRef);
        }
        if (sourceRefs.size() > FILE_MAX_COUNT) {
            throw new BusinessException("最多上传 10 个文件");
        }
        if (totalSize > FILE_MAX_TOTAL_SIZE) {
            throw new BusinessException("文件总大小不能超过 50MB");
        }
        if (sourceRefs.isEmpty()) {
            throw new BusinessException("文件输入不能为空");
        }
        return sourceRefs;
    }

    private void validateSourceRef(DocumentSourceRef sourceRef) {
        if (sourceRef == null) {
            throw new BusinessException("文件输入不能为空");
        }
        if (!StringUtil.hasText(sourceRef.getFileName())) {
            throw new BusinessException("文件输入缺少 fileName");
        }
        if (!StringUtil.hasText(sourceRef.getFilePath())) {
            throw new BusinessException("文件输入缺少 filePath");
        }
    }

    private void collectFileValues(Object value, List<Object> result) {
        if (value == null) {
            return;
        }
        if (value instanceof Collection<?> collection) {
            for (Object item : collection) {
                collectFileValues(item, result);
            }
            return;
        }
        result.add(value);
    }

    /**
     * 判断当前文件类型是否应优先走统一文档解析桥接。
     *
     * @param sourceRef 文档源
     * @return 是否走桥接
     */
    private boolean shouldUseDocumentBridge(DocumentSourceRef sourceRef) {
        return DocumentParseSourceType.resolve(sourceRef.getFileName(), sourceRef.getContentType())
            != DocumentParseSourceType.UNSUPPORTED;
    }

    /**
     * 通过统一文档解析桥接提取主文本结果。
     *
     * @param sourceRef 文档源
     * @return 桥接提取出的主文本
     */
    private String extractBridgeContent(DocumentSourceRef sourceRef) {
        DocumentParsedResult parsedResult = documentParseBridgeService.parse(sourceRef, DocumentParseScenario.WORKFLOW_TEXT);
        String preferredText = parsedResult == null ? null : parsedResult.getPreferredText();
        if (StringUtil.hasText(preferredText)) {
            return preferredText;
        }
        if (parsedResult != null && StringUtil.hasText(parsedResult.getMarkdown())) {
            return parsedResult.getMarkdown();
        }
        if (parsedResult != null && StringUtil.hasText(parsedResult.getPlainText())) {
            return parsedResult.getPlainText();
        }
        throw new BusinessException("文档解析结果为空");
    }

    private String extractDefaultContent(DocumentSourceRef sourceRef) {
        try (InputStream inputStream = openInputStream(sourceRef)) {
            return readerManager.getReader().read(sourceRef.getFileName(), inputStream);
        } catch (IOException e) {
            throw new RuntimeException("读取文件内容失败: " + sourceRef.getFilePath(), e);
        }
    }

    private InputStream openInputStream(DocumentSourceRef sourceRef) throws IOException {
        String filePath = sourceRef.getFilePath();
        if (StringUtil.hasText(filePath) && isRemoteUrl(filePath)) {
            byte[] bytes = HttpUtil.downloadBytes(filePath);
            return new java.io.ByteArrayInputStream(bytes);
        }
        if (StringUtil.hasText(filePath)) {
            return fileStorageService.readStream(filePath);
        }
        if (StringUtil.hasText(sourceRef.getUrl())) {
            byte[] bytes = HttpUtil.downloadBytes(sourceRef.getUrl());
            return new java.io.ByteArrayInputStream(bytes);
        }
        throw new IOException("文件输入缺少可读取路径");
    }

    private boolean isRemoteUrl(String value) {
        return value.startsWith("http://") || value.startsWith("https://");
    }

    private String asText(Object value) {
        return value == null ? null : String.valueOf(value).trim();
    }

    private Long asLong(Object value) {
        if (value == null) {
            return null;
        }
        if (value instanceof Number number) {
            return number.longValue();
        }
        if (value instanceof String text && StringUtil.hasText(text)) {
            return Long.parseLong(text.trim());
        }
        return null;
    }

    /**
     * 逐文件解析结果。
     */
    public static final class DocExtractedDocument {
        private final String fileName;
        private final String content;

        /**
         * 创建逐文件解析结果。
         *
         * @param fileName 文件名
         * @param content 解析文本
         */
        public DocExtractedDocument(String fileName, String content) {
            this.fileName = fileName;
            this.content = content;
        }

        /**
         * @return 文件名
         */
        public String getFileName() {
            return fileName;
        }

        /**
         * @return 文本内容
         */
        public String getContent() {
            return content;
        }

        /**
         * 转为轻量 Map，供工作流结果与引用树消费。
         *
         * @return 轻量结果对象
         */
        public Map<String, Object> toMap() {
            Map<String, Object> result = new LinkedHashMap<>();
            result.put("fileName", fileName);
            result.put("content", content);
            return result;
        }
    }
}
