package tech.easyflow.ai.document.support;

import org.junit.Assert;
import org.junit.Test;
import org.mockito.Mockito;
import tech.easyflow.ai.easyagentsflow.upload.WorkflowApiUploadedFileReader;
import tech.easyflow.ai.document.exception.DocumentParseBridgeException;
import tech.easyflow.ai.document.model.DocumentSourceRef;
import tech.easyflow.common.filestorage.FileStorageService;

import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.nio.charset.StandardCharsets;
import java.util.Optional;

/**
 * {@link DocumentSourceLoader} 单元测试。
 *
 * @author Codex
 * @since 2026-04-14
 */
public class DocumentSourceLoaderTest {

    /**
     * 验证可从 filePath 正常读取文件内容。
     *
     * @throws IOException 测试读取异常
     */
    @Test
    public void shouldLoadContentFromFilePath() throws IOException {
        DocumentSourceLoader loader = new DocumentSourceLoader(new FakeFileStorageService("demo-pdf".getBytes(StandardCharsets.UTF_8)));
        DocumentSourceRef sourceRef = DocumentSourceRef.ofPath("/attachment/test/demo.pdf");

        LoadedDocumentSource loadedSource = loader.load(sourceRef);

        Assert.assertEquals("demo.pdf", loadedSource.getFileName());
        Assert.assertEquals(8L, loadedSource.getSize().longValue());
        Assert.assertArrayEquals("demo-pdf".getBytes(StandardCharsets.UTF_8), loadedSource.getContentBytes());
    }

    /**
     * 验证缺少有效来源时抛出明确异常。
     */
    @Test
    public void shouldThrowWhenSourceMissing() {
        DocumentSourceLoader loader = new DocumentSourceLoader(new FakeFileStorageService(new byte[0]));

        try {
            loader.load(new DocumentSourceRef());
            Assert.fail("expected DocumentParseBridgeException");
        } catch (DocumentParseBridgeException e) {
            Assert.assertEquals("unsupported_source", e.getCode());
        }
    }

    /**
     * 验证普通 filePath 远端 URL 仍拒绝访问回环地址。
     */
    @Test
    public void shouldRejectLoopbackWhenRemoteUrlIsNotManagedUpload() {
        DocumentSourceLoader loader = new DocumentSourceLoader(new FailingFileStorageService());
        DocumentSourceRef sourceRef = new DocumentSourceRef();
        sourceRef.setFileName("demo.pdf");
        sourceRef.setFilePath("http://127.0.0.1:39000/demo.pdf");

        try {
            loader.load(sourceRef);
            Assert.fail("expected DocumentParseBridgeException");
        } catch (DocumentParseBridgeException exception) {
            Assert.assertEquals("source_load_failed", exception.getCode());
            Assert.assertTrue(exception.getCause() instanceof java.net.UnknownHostException);
        }
    }

    /**
     * 验证已通过上传记录校验的内网存储 URL 会走恢复句柄读取。
     *
     * @throws IOException 测试流创建失败时抛出
     */
    @Test
    public void shouldLoadVerifiedManagedUploadBeforeRemoteAddressGuard() throws IOException {
        WorkflowApiUploadedFileReader uploadedFileReader =
                Mockito.mock(WorkflowApiUploadedFileReader.class);
        DocumentSourceLoader loader = new DocumentSourceLoader(
                new FailingFileStorageService(),
                uploadedFileReader);
        String fileUrl = "http://127.0.0.1:39000/easyflow/attachment/"
                + "workflow-api-upload/0123456789abcdef0123456789abcdef/file.pdf";
        byte[] body = "demo-pdf".getBytes(StandardCharsets.UTF_8);
        Mockito.when(uploadedFileReader.openVerified(fileUrl))
                .thenReturn(Optional.of(new ByteArrayInputStream(body)));
        DocumentSourceRef sourceRef = new DocumentSourceRef();
        sourceRef.setFileName("demo.pdf");
        sourceRef.setFilePath(fileUrl);

        LoadedDocumentSource loadedSource = loader.load(sourceRef);

        Assert.assertEquals("demo.pdf", loadedSource.getFileName());
        Assert.assertArrayEquals(body, loadedSource.getContentBytes());
    }

    /**
     * 验证受管上传文件仍按实际读取字节数拒绝超限内容。
     */
    @Test
    public void shouldRejectManagedContentThatExceedsActualByteLimit() throws IOException {
        WorkflowApiUploadedFileReader uploadedFileReader =
                Mockito.mock(WorkflowApiUploadedFileReader.class);
        DocumentSourceLoader loader = new DocumentSourceLoader(
                new FailingFileStorageService(),
                uploadedFileReader);
        byte[] body = "123456789".getBytes(StandardCharsets.UTF_8);
        String fileUrl = "http://127.0.0.1:39000/easyflow/attachment/"
                + "workflow-api-upload/0123456789abcdef0123456789abcdef/oversized.pdf";
        Mockito.when(uploadedFileReader.openVerified(fileUrl))
                .thenReturn(Optional.of(new ByteArrayInputStream(body)));
        DocumentSourceRef sourceRef = new DocumentSourceRef();
        sourceRef.setFileName("oversized.pdf");
        sourceRef.setFilePath(fileUrl);
        sourceRef.setSize(1L);

        try {
            loader.load(sourceRef, 8L);
            Assert.fail("expected DocumentParseBridgeException");
        } catch (DocumentParseBridgeException exception) {
            Assert.assertEquals("source_load_failed", exception.getCode());
            Assert.assertTrue(exception.getCause()
                    instanceof DocumentInputStreamSupport.SizeLimitExceededException);
        }
    }

    /**
     * 验证存储元数据虚高时按实际读取量接受小文件。
     */
    @Test
    public void shouldUseActualBytesWhenStorageMetadataIsIncorrect() {
        byte[] content = new byte[58 * 1024];
        DocumentSourceLoader loader = new DocumentSourceLoader(
                new IncorrectSizeFileStorageService(content));
        DocumentSourceRef sourceRef = DocumentSourceRef.ofPath(
                "/attachment/document.docx");
        sourceRef.setSize((long) content.length);

        LoadedDocumentSource loadedSource = loader.load(
                sourceRef,
                20L * 1024L * 1024L);

        Assert.assertEquals(
                content.length,
                loadedSource.getSize().longValue());
        Assert.assertArrayEquals(content, loadedSource.getContentBytes());
    }

    private static class FakeFileStorageService implements FileStorageService {

        private final byte[] content;

        private FakeFileStorageService(byte[] content) {
            this.content = content;
        }

        @Override
        public String save(org.springframework.web.multipart.MultipartFile file) {
            return null;
        }

        @Override
        public void delete(String path) {
        }

        @Override
        public InputStream readStream(String path) {
            return new ByteArrayInputStream(content);
        }

        @Override
        public long getFileSize(String path) {
            return content.length;
        }
    }

    /**
     * 返回错误大小元数据的存储测试替身。
     */
    private static class IncorrectSizeFileStorageService
            extends FakeFileStorageService {

        private IncorrectSizeFileStorageService(byte[] content) {
            super(content);
        }

        @Override
        public long getFileSize(String path) {
            return 20L * 1024L * 1024L + 1L;
        }
    }

    private static class FailingFileStorageService implements FileStorageService {

        @Override
        public String save(org.springframework.web.multipart.MultipartFile file) {
            return null;
        }

        @Override
        public void delete(String path) {
        }

        @Override
        public InputStream readStream(String path) throws IOException {
            throw new IOException("should not read remote url from storage");
        }

        @Override
        public long getFileSize(String path) {
            return 0L;
        }
    }
}
