package cn.yunzhixue.ability.center.examsprint.infrastructure.report.pdf;

import cn.yunzhixue.ability.center.examsprint.domain.report.AchievementReportContent;
import cn.yunzhixue.ability.center.examsprint.domain.report.ReportType;
import cn.yunzhixue.ability.center.examsprint.domain.report.UnmodeledReportContent;
import cn.yunzhixue.ability.center.examsprint.infrastructure.report.rendering.achievement.ClasspathAchievementExamSprintReportRenderer;
import cn.yunzhixue.ability.center.examsprint.infrastructure.report.rendering.outlook.ClasspathOutlookExamSprintReportRenderer;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.node.ObjectNode;
import com.microsoft.playwright.BrowserType;
import org.apache.pdfbox.pdmodel.PDDocument;
import org.apache.pdfbox.text.PDFTextStripper;
import org.junit.jupiter.api.AfterAll;
import org.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.TestInstance;

import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Path;
import java.text.Normalizer;
import java.time.Instant;
import java.util.List;

import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.assertThatCode;

@TestInstance(TestInstance.Lifecycle.PER_CLASS)
class PlaywrightExamSprintReportPdfGeneratorTest {

    private static final ObjectMapper OBJECT_MAPPER = new ObjectMapper();

    private PlaywrightExamSprintReportPdfGenerator pdfGenerator;

    @BeforeAll
    void createPdfGenerator() {
        pdfGenerator = new PlaywrightExamSprintReportPdfGenerator();
    }

    @AfterAll
    void closePdfGenerator() {
        if (pdfGenerator != null) {
            pdfGenerator.close();
        }
    }

    @Test
    void constructorDoesNotLaunchChromiumBeforeFirstGenerate() {
        assertThatCode(() -> {
            try (PlaywrightExamSprintReportPdfGenerator generator = new PlaywrightExamSprintReportPdfGenerator(1, 1)) {
                // Constructor should not require a browser launch; Chromium is initialized lazily by generate().
            }
        }).doesNotThrowAnyException();
    }

    @Test
    void chromiumLaunchUsesDefaultSandboxOptions() {
        BrowserType.LaunchOptions launchOptions = PlaywrightExamSprintReportPdfGenerator.createLaunchOptions(123);

        assertThat(launchOptions.args).isNull();
    }

    @Test
    void generateCreatesPdfWithExtractableChineseText() throws Exception {
        byte[] pdfBytes = pdfGenerator.generate("""
                <html>
                <head>
                    <meta charset=\"UTF-8\"/>
                    <style>
                        body { font-family: MiSans, ReportFont, sans-serif; }
                    </style>
                </head>
                <body>
                    <p>Playwright 测试：临考冲刺 +19分</p>
                </body>
                </html>
                """);

        assertPdfHeader(pdfBytes);
        try (PDDocument document = PDDocument.load(pdfBytes)) {
            assertThat(document.getNumberOfPages()).isGreaterThanOrEqualTo(1);
            assertThat(normalizePdfText(new PDFTextStripper().getText(document)))
                    .contains("Playwright测试:临考冲刺+19分");
        }
    }

    @Test
    void generateRendersInlineSvgChineseText() throws Exception {
        byte[] pdfBytes = pdfGenerator.generate("""
                <html>
                <head>
                    <meta charset=\"UTF-8\"/>
                </head>
                <body>
                    <svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 220 80' width='220' height='80'
                         font-family=\"'MiSans VF', MiSans, ReportFont, sans-serif\">
                        <text x='20' y='48' font-size='24'>掌握率</text>
                    </svg>
                </body>
                </html>
                """);

        assertPdfHeader(pdfBytes);
        try (PDDocument document = PDDocument.load(pdfBytes)) {
            assertThat(document.getNumberOfPages()).isEqualTo(1);
        }
    }

    @Test
    void generateCreatesReadablePdfForOutlookReportTemplate() throws Exception {
        ClasspathOutlookExamSprintReportRenderer renderer = new ClasspathOutlookExamSprintReportRenderer(OBJECT_MAPPER);
        String html = renderer.render(
                new UnmodeledReportContent(ReportType.OUTLOOK, samplePayloadWithComplex(false)),
                Instant.parse("2026-01-03T08:00:00Z"));

        byte[] pdfBytes = pdfGenerator.generate(html);

        assertPdfHeader(pdfBytes);
        Path previewPdfPath = Path.of(System.getProperty("user.dir"), "target", "playwright-outlook-report-demo.pdf");
        Files.deleteIfExists(previewPdfPath);
        writePreviewPdf(previewPdfPath, pdfBytes);
        assertThat(previewPdfPath).exists().isRegularFile();
        try (PDDocument document = PDDocument.load(pdfBytes)) {
            assertThat(document.getNumberOfPages()).isGreaterThanOrEqualTo(1);
            assertThat(normalizePdfText(new PDFTextStripper().getText(document)))
                    .contains("高考英语临考词汇突击潜力展望报告")
                    .contains("模块一:个人学情分析")
                    .contains("模块二:科学备考建议");
        }
    }

    @Test
    void generateCreatesReadablePdfForAchievementReportTemplate() throws Exception {
        ClasspathAchievementExamSprintReportRenderer renderer = new ClasspathAchievementExamSprintReportRenderer();
        String html = renderer.render(sampleAchievementContent(), Instant.parse("2026-04-25T08:00:00Z"));

        byte[] pdfBytes = pdfGenerator.generate(html);

        assertPdfHeader(pdfBytes);
        Path previewPdfPath = Path.of(System.getProperty("user.dir"), "target", "playwright-achievement-report-demo.pdf");
        Files.deleteIfExists(previewPdfPath);
        writePreviewPdf(previewPdfPath, pdfBytes);
        assertThat(previewPdfPath).exists().isRegularFile();
        try (PDDocument document = PDDocument.load(pdfBytes)) {
            assertThat(document.getNumberOfPages()).isGreaterThanOrEqualTo(1);
            assertThat(normalizePdfText(new PDFTextStripper().getText(document)))
                    .contains("高考英语临考突击学习成果报告")
                    .contains("模块一:词汇量对比")
                    .contains("模块二:试卷熟词量对比")
                    .contains("模块三:实考生词命中状况");
        }
    }

    private void assertPdfHeader(byte[] pdfBytes) {
        assertThat(pdfBytes).isNotEmpty();
        assertThat(new String(pdfBytes, 0, 4, StandardCharsets.ISO_8859_1)).isEqualTo("%PDF");
    }

    private void writePreviewPdf(Path previewPdfPath, byte[] pdfBytes) throws Exception {
        Files.createDirectories(previewPdfPath.getParent());
        Files.write(previewPdfPath, pdfBytes);
    }

    private String normalizePdfText(String text) {
        return Normalizer.normalize(text, Normalizer.Form.NFKC).replaceAll("\\s+", "");
    }

    private AchievementReportContent sampleAchievementContent() {
        return new AchievementReportContent(
                "吴泓妤",
                "高考英语临考突击学习成果报告",
                "2024真题 · 两周专项训练 · 真实提分效果",
                "恭喜完成两周考前突击专项训练",
                "基于2024英语真题试卷 · 真实学习效果分析",
                new AchievementReportContent.SummaryMetrics(
                        "+19",
                        "+4",
                        "21.1%",
                        "0.48"),
                new AchievementReportContent.Comparison(2328.0, 2347.0, "2328", "2347", "+19"),
                new AchievementReportContent.Comparison(650.0, 654.0, "650", "654", "+4"),
                new AchievementReportContent.StageVocabularySummary("高考", "3500", "66.51", "67.06", "+0.55"),
                new AchievementReportContent.TestPaperVocabularySummary("2024真题", "861", "207", "203", "75.49", "75.96", "+0.62"),
                new AchievementReportContent.ExamUnknownWordsHitStatus(
                        "21.1%",
                        "0.48",
                        "207",
                        "203",
                        "4",
                        List.of("number", "bear", "popular", "importance")));
    }

    private JsonNode samplePayload() throws Exception {
        return OBJECT_MAPPER.readTree("""
                {
                  "StudentName": "20260318测试",
                  "StudentStage": 2,
                  "StageName": "初中",
                  "StageVocabulary": 10,
                  "StudentVocabulary": 4,
                  "StageExaminName": "中考",
                  "StageImportant": 3,
                  "StudentWordsLatest": [
                    {"WordId": 1, "MeanId": 1, "WordSpell": "w1", "WordFrequency": 1, "Mastery": 1.0, "ReviewTimes": 1, "Reliability": 2, "CreateTime": "2026-03-18T15:28:25.5813874+08:00"},
                    {"WordId": 2, "MeanId": 1, "WordSpell": "w2", "WordFrequency": 2, "Mastery": 0.5, "ReviewTimes": 1, "Reliability": 2, "CreateTime": "2026-03-18T15:28:25.5813874+08:00"},
                    {"WordId": 3, "MeanId": 1, "WordSpell": "w3", "WordFrequency": 3, "Mastery": 0.2, "ReviewTimes": 1, "Reliability": 2, "CreateTime": "2026-03-18T15:28:25.5813874+08:00"},
                    {"WordId": 4, "MeanId": 1, "WordSpell": "w4", "WordFrequency": 4, "Mastery": 0.4, "ReviewTimes": 1, "Reliability": 2, "CreateTime": "2026-03-18T15:28:25.5813874+08:00"},
                    {"WordId": 5, "MeanId": 1, "WordSpell": "w5", "WordFrequency": 5, "Mastery": 0.6, "ReviewTimes": 1, "Reliability": 2, "CreateTime": "2026-03-18T15:28:25.5813874+08:00"},
                    {"WordId": 6, "MeanId": 1, "WordSpell": "w6", "WordFrequency": 6, "Mastery": 0.8, "ReviewTimes": 1, "Reliability": 2, "CreateTime": "2026-03-18T15:28:25.5813874+08:00"},
                    {"WordId": 7, "MeanId": 1, "WordSpell": "w7", "WordFrequency": 7, "Mastery": 0.1, "ReviewTimes": 1, "Reliability": 2, "CreateTime": "2026-03-18T15:28:25.5813874+08:00"},
                    {"WordId": 8, "MeanId": 1, "WordSpell": "w8", "WordFrequency": 8, "Mastery": 0.2, "ReviewTimes": 1, "Reliability": 2, "CreateTime": "2026-03-18T15:28:25.5813874+08:00"},
                    {"WordId": 9, "MeanId": 1, "WordSpell": "w9", "WordFrequency": 9, "Mastery": 0.3, "ReviewTimes": 1, "Reliability": 2, "CreateTime": "2026-03-18T15:28:25.5813874+08:00"},
                    {"WordId": 10, "MeanId": 1, "WordSpell": "w10", "WordFrequency": 10, "Mastery": 0.4, "ReviewTimes": 1, "Reliability": 2, "CreateTime": "2026-03-18T15:28:25.5813874+08:00"}
                  ],
                  "MastedWordCount": 4,
                  "UnMastedWordCount": 6,
                  "ExamineStrangeWordCount": 3,
                  "TestPaperWordIdArray": [1, 2, 3, 4, 5],
                  "TestPaperTitle": "文章2.jpg",
                  "TestPaperUnMasterWordCount": 3,
                  "TestPaperMastedWordCount": 2,
                  "TestPaperWordCount": 5,
                  "Complex": false
                }
                """);
    }

    private JsonNode samplePayloadWithComplex(boolean complex) throws Exception {
        ObjectNode payload = (ObjectNode) samplePayload();
        payload.put("Complex", complex);
        return payload;
    }
}
