package com.easyagents.core.file2text;

import com.easyagents.core.file2text.extractor.impl.DocExtractor;
import com.easyagents.core.file2text.extractor.impl.PdfTextExtractor;
import com.easyagents.core.file2text.extractor.impl.XlsExtractor;
import com.easyagents.core.file2text.extractor.impl.XlsxExtractor;
import com.easyagents.core.file2text.source.ByteArrayDocumentSource;
import org.apache.poi.hslf.usermodel.HSLFSlide;
import org.apache.poi.hslf.usermodel.HSLFSlideShow;
import org.apache.poi.hslf.usermodel.HSLFTextBox;
import org.apache.poi.hssf.usermodel.HSSFWorkbook;
import org.apache.poi.xslf.usermodel.XMLSlideShow;
import org.apache.poi.xwpf.usermodel.XWPFDocument;
import org.apache.poi.xssf.usermodel.XSSFWorkbook;
import org.junit.Assert;
import org.junit.Test;

import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.nio.charset.StandardCharsets;

/**
 * 常规文档格式轻量读取回归测试。
 */
public class File2TextServiceLightweightReadTest {

    private final File2TextService service = new File2TextService();

    /**
     * 验证 TXT 与 Markdown 会保留行区间和标题结构。
     */
    @Test
    public void shouldReadTextAndMarkdownWithStableSegments() {
        LightweightDocumentReadResult text = read(
                "sample.txt", "text/plain", "first line\nsecond line".getBytes(StandardCharsets.UTF_8));
        LightweightDocumentReadResult markdown = read(
                "sample.md", "text/markdown", "# Chapter\nbody".getBytes(StandardCharsets.UTF_8));

        Assert.assertTrue(text.getText().contains("second line"));
        Assert.assertEquals("LINE_RANGE", text.getSegments().get(0).getLocatorType());
        Assert.assertTrue(markdown.getText().contains("Chapter"));
        Assert.assertEquals("Chapter", markdown.getSegments().get(0).getHeadingPath().get(0));
    }

    /**
     * 验证 DOCX、PPTX、PPT、XLSX 与 XLS 均可直接读取正文。
     *
     * @throws Exception 测试文档生成失败时抛出
     */
    @Test
    public void shouldReadGeneratedOfficeAndPdfDocuments() throws Exception {
        assertReadable("sample.docx",
                "application/vnd.openxmlformats-officedocument.wordprocessingml.document",
                docxBytes(), "DOCX sample");
        assertReadable("sample.pptx",
                "application/vnd.openxmlformats-officedocument.presentationml.presentation",
                pptxBytes(), "PPTX sample");
        assertReadable("sample.ppt", "application/vnd.ms-powerpoint", pptBytes(), "PPT sample");
        assertReadable("sample.xlsx",
                "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet",
                xlsxBytes(), "XLSX sample");
        assertReadable("sample.xls", "application/vnd.ms-excel", xlsBytes(), "XLS sample");
    }

    /**
     * 验证 PDF 与旧版 DOC 扩展名仍路由到专用读取器。
     */
    @Test
    public void shouldRegisterPdfAndLegacyDocReaders() {
        DocExtractor docExtractor = new DocExtractor();
        PdfTextExtractor pdfExtractor = new PdfTextExtractor();

        Assert.assertTrue(docExtractor.supports(new ByteArrayDocumentSource(
                new byte[0], "legacy.doc", "application/msword")));
        Assert.assertTrue(pdfExtractor.supports(new ByteArrayDocumentSource(
                new byte[0], "sample.pdf", "application/pdf")));
    }

    /**
     * 验证表格读取取消会保留明确错误码。
     *
     * @throws Exception 测试文档生成失败时抛出
     */
    @Test
    public void shouldPreserveCancellationErrorForSpreadsheets() throws Exception {
        assertCancelled(new XlsxExtractor(), "sample.xlsx",
                "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet", xlsxBytes());
        assertCancelled(new XlsExtractor(), "sample.xls",
                "application/vnd.ms-excel", xlsBytes());
    }

    private void assertCancelled(com.easyagents.core.file2text.extractor.FileExtractor extractor,
                                 String fileName,
                                 String mimeType,
                                 byte[] bytes) throws Exception {
        LightweightDocumentReadRequest request = new LightweightDocumentReadRequest(
                new ByteArrayDocumentSource(bytes, fileName, mimeType));
        request.setCancelled(() -> true);
        try {
            extractor.read(request);
            Assert.fail("Expected document read cancellation");
        } catch (DocumentReadException error) {
            Assert.assertEquals(DocumentReadErrorCode.DOCUMENT_READ_CANCELLED, error.getErrorCode());
        }
    }

    private void assertReadable(String fileName, String mimeType, byte[] bytes, String expected) {
        Assert.assertTrue(read(fileName, mimeType, bytes).getText().contains(expected));
    }

    private LightweightDocumentReadResult read(String fileName, String mimeType, byte[] bytes) {
        return service.readFromStream(new ByteArrayInputStream(bytes), fileName, mimeType);
    }

    private byte[] docxBytes() throws Exception {
        try (XWPFDocument document = new XWPFDocument();
             ByteArrayOutputStream output = new ByteArrayOutputStream()) {
            document.createParagraph().createRun().setText("DOCX sample");
            document.write(output);
            return output.toByteArray();
        }
    }

    private byte[] pptxBytes() throws Exception {
        try (XMLSlideShow presentation = new XMLSlideShow();
             ByteArrayOutputStream output = new ByteArrayOutputStream()) {
            presentation.createSlide().createTextBox().setText("PPTX sample");
            presentation.write(output);
            return output.toByteArray();
        }
    }

    private byte[] pptBytes() throws Exception {
        try (HSLFSlideShow presentation = new HSLFSlideShow();
             ByteArrayOutputStream output = new ByteArrayOutputStream()) {
            HSLFSlide slide = presentation.createSlide();
            HSLFTextBox textBox = new HSLFTextBox();
            textBox.setText("PPT sample");
            slide.addShape(textBox);
            presentation.write(output);
            return output.toByteArray();
        }
    }

    private byte[] xlsxBytes() throws Exception {
        try (XSSFWorkbook workbook = new XSSFWorkbook();
             ByteArrayOutputStream output = new ByteArrayOutputStream()) {
            workbook.createSheet("Sheet1").createRow(0).createCell(0).setCellValue("XLSX sample");
            workbook.write(output);
            return output.toByteArray();
        }
    }

    private byte[] xlsBytes() throws Exception {
        try (HSSFWorkbook workbook = new HSSFWorkbook();
             ByteArrayOutputStream output = new ByteArrayOutputStream()) {
            workbook.createSheet("Sheet1").createRow(0).createCell(0).setCellValue("XLS sample");
            workbook.write(output);
            return output.toByteArray();
        }
    }
}
