package tech.easyflow.ai.document.support;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Component;
import org.springframework.util.StringUtils;
import tech.easyflow.ai.document.exception.DocumentParseBridgeException;
import tech.easyflow.ai.document.model.DocumentSourceRef;
import tech.easyflow.ai.easyagentsflow.upload.WorkflowApiUploadedFileReader;
import tech.easyflow.common.filestorage.FileStorageService;
import tech.easyflow.common.filestorage.utils.PathGeneratorUtil;

import java.io.IOException;
import java.io.InputStream;
import java.net.URLConnection;
import java.util.Optional;

/**
 * 文档源加载器。
 *
 * <p>负责把不同来源的文件引用统一转换为内存字节和标准文件元数据。</p>
 *
 * @author Codex
 * @since 2026-04-14
 */
@Component
public class DocumentSourceLoader {

    private static final Logger LOG =
            LoggerFactory.getLogger(DocumentSourceLoader.class);

    private final FileStorageService fileStorageService;
    private final WorkflowApiUploadedFileReader uploadedFileReader;

    /**
     * 创建文档源加载器。
     *
     * @param fileStorageService 默认文件存储服务
     * @param uploadedFileReader 已验证的工作流 API 上传文件读取器
     */
    @Autowired
    public DocumentSourceLoader(
            @Qualifier("default") FileStorageService fileStorageService,
            WorkflowApiUploadedFileReader uploadedFileReader) {
        this.fileStorageService = fileStorageService;
        this.uploadedFileReader = uploadedFileReader;
    }

    /**
     * 创建不启用工作流 API 上传识别的加载器，供同包隔离测试使用。
     *
     * @param fileStorageService 文件存储服务
     */
    DocumentSourceLoader(FileStorageService fileStorageService) {
        this(fileStorageService, null);
    }

    /**
     * 加载文档源。
     *
     * @param sourceRef easyflow 文档源
     * @return 内部已加载文档对象
     */
    public LoadedDocumentSource load(DocumentSourceRef sourceRef) {
        return load(sourceRef, 0L);
    }

    /**
     * 在实际字节数保护下加载文档源。
     *
     * @param sourceRef easyflow 文档源
     * @param maxBytes 最大允许字节数；小于等于 0 时保持原有不限大小语义
     * @return 内部已加载文档对象
     */
    public LoadedDocumentSource load(DocumentSourceRef sourceRef, long maxBytes) {
        if (sourceRef == null) {
            throw DocumentParseBridgeException.unsupportedSource("文档源不能为空");
        }
        if (hasContentBytes(sourceRef)) {
            long actualBytes = sourceRef.getContentBytes().length;
            assertWithinLimit(actualBytes, maxBytes);
            logSizeMismatch(sourceRef, actualBytes);
            return buildLoadedSource(
                resolveFileName(sourceRef),
                resolveContentType(sourceRef, resolveFileName(sourceRef)),
                actualBytes,
                sourceRef.getContentBytes()
            );
        }
        if (StringUtils.hasText(sourceRef.getFilePath())) {
            if (isRemoteUrl(sourceRef.getFilePath())) {
                return loadFromRemoteValue(sourceRef, sourceRef.getFilePath(), maxBytes);
            }
            return loadFromFilePath(sourceRef, maxBytes);
        }
        if (StringUtils.hasText(sourceRef.getUrl())) {
            return loadFromUrl(sourceRef, maxBytes);
        }
        throw DocumentParseBridgeException.unsupportedSource("文档源缺少 filePath、url 或 contentBytes");
    }

    private LoadedDocumentSource loadFromFilePath(DocumentSourceRef sourceRef, long maxBytes) {
        String fileName = resolveFileName(sourceRef);
        try (InputStream inputStream = openStoredValue(sourceRef.getFilePath(), maxBytes)) {
            byte[] contentBytes = DocumentInputStreamSupport.readBytes(inputStream, maxBytes);
            logSizeMismatch(sourceRef, contentBytes.length);
            return buildLoadedSource(
                fileName,
                resolveContentType(sourceRef, fileName),
                (long) contentBytes.length,
                contentBytes
            );
        } catch (IOException e) {
            throw DocumentParseBridgeException.sourceLoadFailed(
                "读取文档存储文件失败: " + sourceRef.getFilePath(),
                e
            );
        }
    }

    private LoadedDocumentSource loadFromUrl(DocumentSourceRef sourceRef, long maxBytes) {
        return loadFromRemoteValue(sourceRef, sourceRef.getUrl(), maxBytes);
    }

    private LoadedDocumentSource loadFromRemoteValue(DocumentSourceRef sourceRef,
                                                     String remoteUrl,
                                                     long maxBytes) {
        String fileName = resolveFileName(sourceRef);
        try (InputStream inputStream = openRemoteValue(remoteUrl, maxBytes)) {
            byte[] contentBytes = DocumentInputStreamSupport.readBytes(inputStream, maxBytes);
            logSizeMismatch(sourceRef, contentBytes.length);
            return buildLoadedSource(
                fileName,
                resolveContentType(sourceRef, fileName),
                (long) contentBytes.length,
                contentBytes
            );
        } catch (Exception e) {
            throw DocumentParseBridgeException.sourceLoadFailed(
                "下载文档 URL 失败: " + remoteUrl,
                e
            );
        }
    }

    /**
     * 优先打开经过上传记录验证的受管文件，再回退普通存储路径。
     *
     * @param filePath 文件路径
     * @param maxBytes 最大允许读取字节数
     * @return 受限输入流
     * @throws IOException 文件无法读取时抛出
     */
    private InputStream openStoredValue(String filePath, long maxBytes) throws IOException {
        Optional<InputStream> managed = openManagedValue(filePath);
        if (managed.isPresent()) {
            return DocumentInputStreamSupport.limit(managed.get(), maxBytes);
        }
        return DocumentInputStreamSupport.limit(
                fileStorageService.readStream(filePath),
                maxBytes);
    }

    /**
     * 优先打开经过上传记录验证的受管 URL，再执行普通公网 URL 校验与下载。
     *
     * @param remoteUrl 远端 URL
     * @param maxBytes 最大允许读取字节数
     * @return 受限输入流
     * @throws IOException 文件无法读取时抛出
     */
    private InputStream openRemoteValue(String remoteUrl, long maxBytes) throws IOException {
        Optional<InputStream> managed = openManagedValue(remoteUrl);
        if (managed.isPresent()) {
            return DocumentInputStreamSupport.limit(managed.get(), maxBytes);
        }
        return DocumentInputStreamSupport.openRemote(remoteUrl, maxBytes);
    }

    /**
     * 尝试打开受管工作流上传文件。
     *
     * @param filePath 文件路径
     * @return 受管输入流；测试未配置读取器或普通路径时为空
     * @throws IOException 受管文件校验或读取失败时抛出
     */
    private Optional<InputStream> openManagedValue(String filePath) throws IOException {
        if (uploadedFileReader == null) {
            return Optional.empty();
        }
        return uploadedFileReader.openVerified(filePath);
    }

    private LoadedDocumentSource buildLoadedSource(String fileName, String contentType, Long size, byte[] contentBytes) {
        LoadedDocumentSource loadedSource = new LoadedDocumentSource();
        loadedSource.setFileName(fileName);
        loadedSource.setContentType(contentType);
        loadedSource.setSize(size);
        loadedSource.setContentBytes(contentBytes);
        return loadedSource;
    }

    private String resolveFileName(DocumentSourceRef sourceRef) {
        if (StringUtils.hasText(sourceRef.getFileName())) {
            return PathGeneratorUtil.getPureFileName(sourceRef.getFileName());
        }
        if (StringUtils.hasText(sourceRef.getFilePath())) {
            return PathGeneratorUtil.getPureFileName(sourceRef.getFilePath());
        }
        if (StringUtils.hasText(sourceRef.getUrl())) {
            String pureName = PathGeneratorUtil.getPureFileName(sourceRef.getUrl());
            int queryIndex = pureName.indexOf('?');
            return queryIndex >= 0 ? pureName.substring(0, queryIndex) : pureName;
        }
        throw DocumentParseBridgeException.unsupportedSource("文档源缺少可用文件名");
    }

    private String resolveContentType(DocumentSourceRef sourceRef, String fileName) {
        if (StringUtils.hasText(sourceRef.getContentType())) {
            return sourceRef.getContentType();
        }
        return URLConnection.guessContentTypeFromName(fileName);
    }

    private boolean hasContentBytes(DocumentSourceRef sourceRef) {
        return sourceRef.getContentBytes() != null && sourceRef.getContentBytes().length > 0;
    }

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

    /**
     * 记录上传声明大小与实际读取量不一致的情况。
     *
     * @param sourceRef 文档源
     * @param actualBytes 实际读取字节数
     */
    private void logSizeMismatch(DocumentSourceRef sourceRef, long actualBytes) {
        Long declaredBytes = sourceRef.getSize();
        if (declaredBytes == null || declaredBytes < 0L
                || declaredBytes == actualBytes) {
            return;
        }
        LOG.warn(
                "文档声明大小与实际读取量不一致: fileName={}, "
                        + "declaredBytes={}, actualBytes={}",
                resolveFileName(sourceRef),
                declaredBytes,
                actualBytes);
    }

    /**
     * 校验已确认的实际字节数。
     *
     * @param actualBytes 实际字节数
     * @param maxBytes 最大允许字节数
     */
    private void assertWithinLimit(long actualBytes, long maxBytes) {
        if (maxBytes > 0L && actualBytes > maxBytes) {
            throw DocumentParseBridgeException.sourceLoadFailed(
                    "文档实际大小超过限制: " + maxBytes + " bytes",
                    new DocumentInputStreamSupport.SizeLimitExceededException(
                            maxBytes,
                            actualBytes));
        }
    }
}
