package tech.easyflow.ai.easyagentsflow.upload;

import org.junit.Assert;
import org.junit.Test;
import org.mockito.Mockito;
import tech.easyflow.common.filestorage.FileStorageService;
import tech.easyflow.common.filestorage.FileStorageWriteHandle;

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

/**
 * {@link WorkflowApiUploadedFileReader} 上传记录授权边界测试。
 */
public class WorkflowApiUploadedFileReaderTest {

    private static final String UPLOAD_ID =
            "0123456789abcdef0123456789abcdef";
    private static final String FILENAME =
            "000-abcdefabcdefabcdefabcdefabcdefab.docx";
    private static final String FILE_URL =
            "http://127.0.0.1:39000/easyflow/attachment/"
                    + "workflow-api-upload/" + UPLOAD_ID + "/" + FILENAME;

    /**
     * 验证 URL、上传记录与恢复句柄完全匹配后按固定后端读取。
     *
     * @throws Exception 测试流读取失败时抛出
     */
    @Test
    public void shouldReadExactRecordedUploadByRecoverableHandle() throws Exception {
        WorkflowApiUploadStore uploadStore = Mockito.mock(WorkflowApiUploadStore.class);
        FileStorageService fileStorageService = Mockito.mock(FileStorageService.class);
        WorkflowApiUploadedFileReader reader =
                new WorkflowApiUploadedFileReader(uploadStore, fileStorageService);
        FileStorageWriteHandle handle = handle();
        WorkflowApiUploadRecord record = record(FILE_URL, handle);
        byte[] content = "document-content".getBytes(StandardCharsets.UTF_8);
        Mockito.when(uploadStore.find(UPLOAD_ID)).thenReturn(Optional.of(record));
        Mockito.when(fileStorageService.readRecoverable(handle))
                .thenReturn(new ByteArrayInputStream(content));

        Optional<InputStream> opened = reader.openVerified(FILE_URL);

        Assert.assertTrue(opened.isPresent());
        try (InputStream inputStream = opened.orElseThrow()) {
            Assert.assertArrayEquals(content, inputStream.readAllBytes());
        }
        Mockito.verify(fileStorageService).readRecoverable(handle);
    }

    /**
     * 验证普通远端 URL 不访问上传记录，也不获得内部读取权限。
     *
     * @throws IOException 路径解析失败时抛出
     */
    @Test
    public void shouldIgnoreOrdinaryRemoteUrl() throws IOException {
        WorkflowApiUploadStore uploadStore = Mockito.mock(WorkflowApiUploadStore.class);
        FileStorageService fileStorageService = Mockito.mock(FileStorageService.class);
        WorkflowApiUploadedFileReader reader =
                new WorkflowApiUploadedFileReader(uploadStore, fileStorageService);

        Optional<InputStream> opened = reader.openVerified(
                "http://127.0.0.1:39000/easyflow/attachment/ordinary.docx");

        Assert.assertTrue(opened.isEmpty());
        Mockito.verifyNoInteractions(uploadStore, fileStorageService);
    }

    /**
     * 验证看似系统目录的 URL 在 Redis 记录不存在时明确判定为失效。
     */
    @Test
    public void shouldRejectManagedPathWhenUploadRecordExpired() {
        WorkflowApiUploadStore uploadStore = Mockito.mock(WorkflowApiUploadStore.class);
        FileStorageService fileStorageService = Mockito.mock(FileStorageService.class);
        WorkflowApiUploadedFileReader reader =
                new WorkflowApiUploadedFileReader(uploadStore, fileStorageService);
        Mockito.when(uploadStore.find(UPLOAD_ID)).thenReturn(Optional.empty());

        IOException exception = Assert.assertThrows(
                IOException.class,
                () -> reader.openVerified(FILE_URL));

        Assert.assertTrue(exception.getMessage().contains("已失效"));
        Mockito.verifyNoInteractions(fileStorageService);
    }

    /**
     * 验证同一请求 ID 下未记录的 URL 不能复用其他文件的存储 locator。
     */
    @Test
    public void shouldRejectUrlThatDoesNotExactlyMatchStoredFile() {
        WorkflowApiUploadStore uploadStore = Mockito.mock(WorkflowApiUploadStore.class);
        FileStorageService fileStorageService = Mockito.mock(FileStorageService.class);
        WorkflowApiUploadedFileReader reader =
                new WorkflowApiUploadedFileReader(uploadStore, fileStorageService);
        Mockito.when(uploadStore.find(UPLOAD_ID)).thenReturn(Optional.of(
                record(FILE_URL + "?different=true", handle())));

        IOException exception = Assert.assertThrows(
                IOException.class,
                () -> reader.openVerified(FILE_URL));

        Assert.assertTrue(exception.getMessage().contains("不匹配"));
        Mockito.verifyNoInteractions(fileStorageService);
    }

    /**
     * 创建与系统上传目录一致的恢复句柄。
     *
     * @return 测试句柄
     */
    private FileStorageWriteHandle handle() {
        return new FileStorageWriteHandle(
                "local",
                "",
                "/tmp/easyflow-test",
                "workflow-api-upload/" + UPLOAD_ID,
                FILENAME);
    }

    /**
     * 创建包含单个受管文件的上传记录。
     *
     * @param fileUrl 记录中的完整文件 URL
     * @param handle 文件存储句柄
     * @return 上传记录
     */
    private WorkflowApiUploadRecord record(
            String fileUrl,
            FileStorageWriteHandle handle) {
        WorkflowApiUploadRecord record = new WorkflowApiUploadRecord();
        record.setUploadId(UPLOAD_ID);
        record.setRequestId("http-request-1");
        record.setStoredFiles(List.of(new WorkflowApiStoredFile(
                fileUrl,
                handle.encodeLocator())));
        return record;
    }
}
