package com.easyagents.document.xlsx.mineru;

import com.alibaba.fastjson2.JSONObject;
import com.easyagents.document.core.async.DocumentAsyncTaskManager;
import com.easyagents.document.core.async.InMemoryDocumentAsyncTaskRepository;
import com.easyagents.document.core.mineru.MineruClient;
import com.easyagents.document.core.mineru.MineruMapper;
import com.easyagents.document.core.mineru.MineruProperties;
import com.easyagents.document.core.mineru.MineruResultPayload;
import com.easyagents.document.core.entity.ParseArtifacts;
import com.easyagents.document.core.entity.ParseFile;
import com.easyagents.document.core.entity.ParseResponse;
import com.easyagents.document.core.entity.ParseResult;
import com.easyagents.document.core.entity.ParseTaskInfo;
import com.easyagents.document.core.entity.ParseTaskStatus;
import com.easyagents.document.core.entity.XlsxParseRequest;
import com.easyagents.document.core.exception.DocumentParseException;
import com.easyagents.document.xlsx.model.XlsxParseArtifact;
import org.apache.poi.ss.usermodel.ClientAnchor;
import org.apache.poi.xssf.usermodel.XSSFDrawing;
import org.apache.poi.xssf.usermodel.XSSFSheet;
import org.apache.poi.xssf.usermodel.XSSFWorkbook;
import org.junit.Assert;
import org.junit.Test;

import javax.imageio.ImageIO;
import java.awt.image.BufferedImage;
import java.io.ByteArrayOutputStream;
import java.util.ArrayDeque;
import java.util.Queue;
import java.util.concurrent.Executor;

/**
 * XLSX MinerU 服务测试。
 *
 * @author Codex
 * @since 2026-04-16
 */
public class MineruXlsxDocumentParseServiceTest {

    @Test
    public void shouldBuildMarkdownAndImageArtifacts() throws Exception {
        RecordingClient client = new RecordingClient(defaultProperties());
        MineruMapper mapper = new MineruMapper(defaultProperties());
        MineruXlsxDocumentParseService service = new MineruXlsxDocumentParseService(
            defaultProperties(),
            client,
            mapper,
            new DocumentAsyncTaskManager(new InMemoryDocumentAsyncTaskRepository(), directExecutor())
        );

        XlsxParseRequest request = new XlsxParseRequest();
        request.addFile(ParseFile.of("demo.xlsx", buildWorkbookBytes()));

        ParseResponse response = service.parse(request);

        Assert.assertEquals(1, response.getResults().size());
        ParseResult result = response.getResults().get(0);
        Assert.assertTrue(result.getMarkdown().contains("# Sheet1"));
        Assert.assertTrue(result.getMarkdown().contains("[IMG:sheet1-r2c2-001]"));
        Assert.assertTrue(result.getMarkdown().contains("images/sheet1/r2c2-001.png"));
        Assert.assertTrue(result.getMarkdown().contains("图片文字描述"));
        Assert.assertEquals(1, result.getImages().size());
        Assert.assertNotNull(result.getImages().get(0).getContent());

        XlsxParseArtifact artifact = extractXlsxArtifact(result);
        Assert.assertEquals("demo.xlsx", artifact.getWorkbookName());
        Assert.assertEquals(1, artifact.getSheets().size());
        Assert.assertEquals(1, artifact.getSheetImages().size());
        Assert.assertEquals(1, artifact.getCellImages().size());
        Assert.assertEquals("sheet1-r2c2-001", artifact.getCellImages().get(0).getReferenceKey());
        Assert.assertEquals("images/sheet1/r2c2-001.png", artifact.getCellImages().get(0).getSourcePath());
        Assert.assertEquals("sheet1-r2c2-001", artifact.getSheetImages().get(0).getReferenceKeys().get(0));
        Assert.assertEquals("images/sheet1/r2c2-001.png", artifact.getSheetImages().get(0).getSourcePaths().get(0));
    }

    @Test
    public void shouldKeepImageKeysUniqueForNonAsciiSheetNames() throws Exception {
        RecordingClient client = new RecordingClient(defaultProperties());
        MineruMapper mapper = new MineruMapper(defaultProperties());
        MineruXlsxDocumentParseService service = new MineruXlsxDocumentParseService(
            defaultProperties(),
            client,
            mapper,
            new DocumentAsyncTaskManager(new InMemoryDocumentAsyncTaskRepository(), directExecutor())
        );

        XlsxParseRequest request = new XlsxParseRequest();
        request.addFile(ParseFile.of("unicode-sheets.xlsx", buildWorkbookBytesWithUnicodeSheetNames()));

        ParseResponse response = service.parse(request);
        ParseResult result = response.getResults().get(0);

        Assert.assertEquals(2, result.getImages().size());
        Assert.assertNotEquals(result.getImages().get(0).getName(), result.getImages().get(1).getName());
        Assert.assertNotEquals(result.getImages().get(0).getSourcePath(), result.getImages().get(1).getSourcePath());
        Assert.assertTrue(result.getMarkdown().contains("[IMG:" + result.getImages().get(0).getName() + "]"));
        Assert.assertTrue(result.getMarkdown().contains("[IMG:" + result.getImages().get(1).getName() + "]"));
    }

    @Test
    public void shouldDetectJpegMimeType() throws Exception {
        RecordingClient client = new RecordingClient(defaultProperties());
        MineruMapper mapper = new MineruMapper(defaultProperties());
        MineruXlsxDocumentParseService service = new MineruXlsxDocumentParseService(
            defaultProperties(),
            client,
            mapper,
            new DocumentAsyncTaskManager(new InMemoryDocumentAsyncTaskRepository(), directExecutor())
        );

        XlsxParseRequest request = new XlsxParseRequest();
        request.addFile(ParseFile.of("jpeg.xlsx", buildWorkbookBytesWithJpegImage()));

        ParseResponse response = service.parse(request);
        ParseResult result = response.getResults().get(0);

        Assert.assertEquals(1, result.getImages().size());
        Assert.assertEquals("image/jpeg", result.getImages().get(0).getMimeType());
    }

    @Test
    public void shouldAppendImageReferenceForImageOnlySheet() throws Exception {
        RecordingClient client = new RecordingClient(defaultProperties());
        MineruMapper mapper = new MineruMapper(defaultProperties());
        MineruXlsxDocumentParseService service = new MineruXlsxDocumentParseService(
            defaultProperties(),
            client,
            mapper,
            new DocumentAsyncTaskManager(new InMemoryDocumentAsyncTaskRepository(), directExecutor())
        );

        XlsxParseRequest request = new XlsxParseRequest();
        request.addFile(ParseFile.of("image-only.xlsx", buildWorkbookBytesWithImageOnlySheet()));

        ParseResponse response = service.parse(request);
        ParseResult result = response.getResults().get(0);
        XlsxParseArtifact artifact = extractXlsxArtifact(result);

        Assert.assertTrue(result.getMarkdown().contains("# Sheet1"));
        Assert.assertFalse(result.getMarkdown().contains("_empty sheet_"));
        Assert.assertTrue(result.getMarkdown().contains("## Sheet1 图片内容"));
        Assert.assertTrue(result.getMarkdown().contains("[IMG:sheet1-r2c2-001]"));
        Assert.assertTrue(result.getMarkdown().contains("## Sheet1 图片说明"));
        Assert.assertTrue(result.getMarkdown().contains("![sheet1-r2c2-001](images/sheet1/r2c2-001.png)"));
        Assert.assertTrue(result.getMarkdown().contains("- 占位符：[IMG:sheet1-r2c2-001]"));
        Assert.assertEquals(1, result.getImages().size());
        Assert.assertEquals(1, artifact.getSheetImages().size());
        Assert.assertEquals("sheet1-r2c2-001", artifact.getSheetImages().get(0).getReferenceKeys().get(0));
    }

    @Test
    public void shouldKeepMarkdownImageReferenceWhenImageAppendixDisabled() throws Exception {
        RecordingClient client = new RecordingClient(defaultProperties());
        MineruMapper mapper = new MineruMapper(defaultProperties());
        MineruXlsxDocumentParseService service = new MineruXlsxDocumentParseService(
            defaultProperties(),
            client,
            mapper,
            new DocumentAsyncTaskManager(new InMemoryDocumentAsyncTaskRepository(), directExecutor())
        );

        XlsxParseRequest request = new XlsxParseRequest();
        request.setIncludeImageAppendix(Boolean.FALSE);
        request.addFile(ParseFile.of("image-only.xlsx", buildWorkbookBytesWithImageOnlySheet()));

        ParseResponse response = service.parse(request);
        ParseResult result = response.getResults().get(0);

        Assert.assertTrue(result.getMarkdown().contains("## Sheet1 图片内容"));
        Assert.assertTrue(result.getMarkdown().contains("[IMG:sheet1-r2c2-001]"));
        Assert.assertTrue(result.getMarkdown().contains("![sheet1-r2c2-001](images/sheet1/r2c2-001.png)"));
        Assert.assertFalse(result.getMarkdown().contains("## Sheet1 图片说明"));
    }

    @Test
    public void shouldTrackAsyncLifecycleAndExposeResult() throws Exception {
        RecordingClient client = new RecordingClient(defaultProperties());
        MineruMapper mapper = new MineruMapper(defaultProperties());
        ManualExecutor executor = new ManualExecutor();
        MineruXlsxDocumentParseService service = new MineruXlsxDocumentParseService(
            defaultProperties(),
            client,
            mapper,
            new DocumentAsyncTaskManager(new InMemoryDocumentAsyncTaskRepository(), executor)
        );

        XlsxParseRequest request = new XlsxParseRequest();
        request.addFile(ParseFile.of("async.xlsx", buildWorkbookBytes()));

        ParseTaskStatus submitted = service.submit(request);
        Assert.assertEquals("queued", submitted.getStatus());
        Assert.assertEquals("queued", submitted.getCurrentStage());
        Assert.assertEquals(Integer.valueOf(0), submitted.getProgressPercent());

        ParseTaskInfo queuedInfo = service.queryTaskInfo(submitted.getTaskId());
        Assert.assertNull(queuedInfo.getResult());
        try {
            service.queryResult(submitted.getTaskId());
            Assert.fail("任务未完成时应抛出异常");
        } catch (DocumentParseException expected) {
            Assert.assertTrue(expected.getMessage().contains(submitted.getTaskId()));
        }

        executor.runNext();

        ParseTaskStatus completed = service.queryTask(submitted.getTaskId());
        Assert.assertEquals("completed", completed.getStatus());
        Assert.assertEquals("completed", completed.getCurrentStage());
        Assert.assertEquals(Integer.valueOf(100), completed.getProgressPercent());
        Assert.assertEquals("任务执行完成", completed.getStatusMessage());

        ParseTaskInfo completedInfo = service.queryTaskInfo(submitted.getTaskId());
        Assert.assertNotNull(completedInfo.getResult());
        Assert.assertTrue(completedInfo.getResult().getResults().get(0).getMarkdown().contains("[IMG:sheet1-r2c2-001]"));
        Assert.assertEquals(completedInfo.getResult(), service.queryResult(submitted.getTaskId()));
    }

    private byte[] buildWorkbookBytes() throws Exception {
        XSSFWorkbook workbook = new XSSFWorkbook();
        XSSFSheet sheet = workbook.createSheet("Sheet1");
        sheet.createRow(0).createCell(0).setCellValue("商品");
        sheet.getRow(0).createCell(1).setCellValue("图片");
        sheet.createRow(1).createCell(0).setCellValue("手机");
        addPicture(workbook, sheet, 1, 1, createImageBytes("png"), XSSFWorkbook.PICTURE_TYPE_PNG);
        return writeWorkbook(workbook);
    }

    private byte[] buildWorkbookBytesWithUnicodeSheetNames() throws Exception {
        XSSFWorkbook workbook = new XSSFWorkbook();

        XSSFSheet detailSheet = workbook.createSheet("明细");
        detailSheet.createRow(0).createCell(0).setCellValue("图片");
        addPicture(workbook, detailSheet, 1, 1, createImageBytes("png"), XSSFWorkbook.PICTURE_TYPE_PNG);

        XSSFSheet summarySheet = workbook.createSheet("汇总");
        summarySheet.createRow(0).createCell(0).setCellValue("图片");
        addPicture(workbook, summarySheet, 1, 1, createImageBytes("png"), XSSFWorkbook.PICTURE_TYPE_PNG);

        return writeWorkbook(workbook);
    }

    private byte[] buildWorkbookBytesWithJpegImage() throws Exception {
        XSSFWorkbook workbook = new XSSFWorkbook();
        XSSFSheet sheet = workbook.createSheet("Sheet1");
        sheet.createRow(0).createCell(0).setCellValue("图片");
        addPicture(workbook, sheet, 1, 1, createImageBytes("jpg"), XSSFWorkbook.PICTURE_TYPE_JPEG);
        return writeWorkbook(workbook);
    }

    private byte[] buildWorkbookBytesWithImageOnlySheet() throws Exception {
        XSSFWorkbook workbook = new XSSFWorkbook();
        XSSFSheet sheet = workbook.createSheet("Sheet1");
        addPicture(workbook, sheet, 1, 1, createImageBytes("png"), XSSFWorkbook.PICTURE_TYPE_PNG);
        return writeWorkbook(workbook);
    }

    private void addPicture(XSSFWorkbook workbook,
                            XSSFSheet sheet,
                            int rowIndex,
                            int colIndex,
                            byte[] imageBytes,
                            int pictureType) {
        int pictureIndex = workbook.addPicture(imageBytes, pictureType);
        XSSFDrawing drawing = sheet.createDrawingPatriarch();
        ClientAnchor anchor = workbook.getCreationHelper().createClientAnchor();
        anchor.setRow1(rowIndex);
        anchor.setCol1(colIndex);
        anchor.setRow2(rowIndex + 1);
        anchor.setCol2(colIndex + 1);
        drawing.createPicture(anchor, pictureIndex);
    }

    private byte[] writeWorkbook(XSSFWorkbook workbook) throws Exception {
        ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
        workbook.write(outputStream);
        workbook.close();
        return outputStream.toByteArray();
    }

    private byte[] createImageBytes(String format) throws Exception {
        BufferedImage image = new BufferedImage(2, 2, BufferedImage.TYPE_INT_RGB);
        ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
        ImageIO.write(image, format, outputStream);
        return outputStream.toByteArray();
    }

    private MineruProperties defaultProperties() {
        MineruProperties properties = new MineruProperties();
        properties.setBaseUrl("http://127.0.0.1:8000");
        return properties;
    }

    private Executor directExecutor() {
        return new Executor() {
            @Override
            public void execute(Runnable command) {
                command.run();
            }
        };
    }

    private XlsxParseArtifact extractXlsxArtifact(ParseResult result) {
        ParseArtifacts artifacts = result.getArtifacts();
        Assert.assertNotNull(artifacts);
        Object artifact = artifacts.getExtraJsonArtifacts().get("xlsx");
        Assert.assertTrue(artifact instanceof XlsxParseArtifact);
        return (XlsxParseArtifact) artifact;
    }

    /**
     * 手动执行的测试执行器，用于验证异步任务状态流转。
     */
    private static class ManualExecutor implements Executor {

        private final Queue<Runnable> tasks = new ArrayDeque<Runnable>();

        @Override
        public void execute(Runnable command) {
            tasks.offer(command);
        }

        private void runNext() {
            Runnable task = tasks.poll();
            Assert.assertNotNull("应当存在待执行任务", task);
            task.run();
        }
    }

    private static class RecordingClient extends MineruClient {

        private RecordingClient(MineruProperties properties) {
            super(properties, new MineruMapper(properties));
        }

        @Override
        public MineruResultPayload parse(com.easyagents.document.core.entity.ParseRequest request) {
            return new MineruMapper(testProperties()).toResultPayload(syncPayload());
        }

        private JSONObject syncPayload() {
            JSONObject payload = new JSONObject();
            payload.put("backend", "vlm-http-client");
            payload.put("version", "3.0.9");
            JSONObject result = new JSONObject();
            result.put("md_content", "图片文字描述");
            JSONObject results = new JSONObject();
            results.put("image", result);
            payload.put("results", results);
            return payload;
        }

        private static MineruProperties testProperties() {
            MineruProperties properties = new MineruProperties();
            properties.setBaseUrl("http://127.0.0.1:8000");
            return properties;
        }
    }
}
