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

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 org.apache.pdfbox.pdmodel.PDDocument;
import org.apache.pdfbox.text.PDFTextStripper;
import org.junit.jupiter.api.Test;

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.assertThatThrownBy;

class OpenHtmlToPdfExamSprintReportPdfGeneratorTest {

    private static final ObjectMapper OBJECT_MAPPER = new ObjectMapper();

    @Test
    void generateCreatesPdfSmokeWithExtractableOutlookKeyText() throws Exception {
        ClasspathOutlookExamSprintReportRenderer renderer = new ClasspathOutlookExamSprintReportRenderer(OBJECT_MAPPER);
        OpenHtmlToPdfExamSprintReportPdfGenerator pdfGenerator = new OpenHtmlToPdfExamSprintReportPdfGenerator();

        String html = renderer.render(samplePayload(), Instant.parse("2026-01-03T08:00:00Z"));
        byte[] pdfBytes = pdfGenerator.generate(html);

        assertThat(pdfBytes).isNotEmpty();
        assertThat(new String(pdfBytes, 0, 4, StandardCharsets.ISO_8859_1)).isEqualTo("%PDF");

        Path previewPdfPath = Path.of(System.getProperty("user.dir"), "target", "outlook-report-demo.pdf");
        Files.createDirectories(previewPdfPath.getParent());
        Files.write(previewPdfPath, pdfBytes);
        assertThat(previewPdfPath).exists().isRegularFile();

        try (PDDocument document = PDDocument.load(pdfBytes)) {
            assertThat(document.getNumberOfPages()).isGreaterThanOrEqualTo(1);
            String normalizedText = new PDFTextStripper().getText(document).replaceAll("\\s+", "");
            assertThat(normalizedText)
                    .contains("高考英语临考词汇突击潜力展望报告")
                    .contains("模块一：个人学情分析")
                    .contains("模块二：科学备考建议")
                    .contains("模块三：上届学员提分案例")
                    .contains("词频区间掌握度")
                    .containsAnyOf("练习学案频率与提分规划", "建议策略")
                    .contains("王雷宇")
                    .contains("705词")
                    .contains("237词")
                    .contains("+19分")
                    .containsAnyOf("高频词：188.6（优先学习）", "高频词188.6优先学习")
                    .containsAnyOf("中频词：154.5（重点突破）", "中频词154.5重点突破")
                    .containsAnyOf("低频词：70.4（酌情学习）", "低频词70.4酌情学习");
        }
    }

    @Test
    void generateCreatesPdfSmokeWithExtractableAchievementKeyText() throws Exception {
        ClasspathAchievementExamSprintReportRenderer renderer = new ClasspathAchievementExamSprintReportRenderer(OBJECT_MAPPER);
        OpenHtmlToPdfExamSprintReportPdfGenerator pdfGenerator = new OpenHtmlToPdfExamSprintReportPdfGenerator();

        String html = renderer.render(sampleAchievementPayload(), Instant.parse("2026-04-25T08:00:00Z"));
        byte[] pdfBytes = pdfGenerator.generate(html);

        assertThat(pdfBytes).isNotEmpty();
        assertThat(new String(pdfBytes, 0, 4, StandardCharsets.ISO_8859_1)).isEqualTo("%PDF");

        Path previewPdfPath = Path.of(System.getProperty("user.dir"), "target", "achievement-report-demo.pdf");
        Files.createDirectories(previewPdfPath.getParent());
        Files.write(previewPdfPath, pdfBytes);
        assertThat(previewPdfPath).exists().isRegularFile();

        try (PDDocument document = PDDocument.load(pdfBytes)) {
            assertThat(document.getNumberOfPages()).isGreaterThanOrEqualTo(1);
            String normalizedText = normalizePdfText(new PDFTextStripper().getText(document));
            assertThat(normalizedText)
                    .contains("高考英语临考突击学习成果报告")
                    .contains("模块一:词汇量对比")
                    .contains("模块二:试卷熟词量对比")
                    .contains("模块三:实考生词命中状况")
                    .contains("词汇量提升(个)")
                    .contains("真题生词命中率")
                    .containsAnyOf("number", "bear", "popular", "importance");
        }
    }

    @Test
    void generateUsesBundledMiSansWhenSystemFontCandidatesAreEmpty() throws Exception {
        OpenHtmlToPdfExamSprintReportPdfGenerator pdfGenerator = new OpenHtmlToPdfExamSprintReportPdfGenerator(
                BundledOutlookReportFonts::load,
                List.of());

        byte[] pdfBytes = pdfGenerator.generate("""
                <html>
                <head>
                    <meta charset=\"UTF-8\"/>
                    <style>
                        body { font-family: MiSans; }
                    </style>
                </head>
                <body>
                    <p>MiSans 测试：临考冲刺 +19分</p>
                </body>
                </html>
                """);

        assertThat(pdfBytes).isNotEmpty();
        assertThat(new String(pdfBytes, 0, 4, StandardCharsets.ISO_8859_1)).isEqualTo("%PDF");

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

    @Test
    void generateDoesNotHideUnexpectedBundledFontSupplierFailures() {
        OpenHtmlToPdfExamSprintReportPdfGenerator pdfGenerator = new OpenHtmlToPdfExamSprintReportPdfGenerator(
                () -> { throw new IllegalArgumentException("broken supplier contract"); },
                List.of("/System/Library/Fonts/Supplemental/Arial Unicode.ttf"));

        assertThatThrownBy(() -> pdfGenerator.generate("<html><body>测试</body></html>"))
                .isInstanceOf(IllegalStateException.class)
                .hasMessageContaining("Failed to generate PDF")
                .hasRootCauseInstanceOf(IllegalArgumentException.class)
                .hasRootCauseMessage("broken supplier contract");
    }

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

    private JsonNode samplePayload() throws Exception {
        return OBJECT_MAPPER.readTree("""
                {
                  "reportMetadata": {
                    "reportVersionLabel": "2026 词汇展望报告",
                    "learnerName": "李同学",
                    "targetExamName": "春季高考英语",
                    "sprintPeriodLabel": "30 天考前冲刺",
                    "authorName": "Ability Bot"
                  },
                  "readinessOverview": {
                    "summary": "基础较稳，具备短期冲刺提分空间。",
                    "currentStage": "冲刺提升期",
                    "keyInsight": "核心观察：高频与常考词群是提分关键。",
                    "readinessScore": 72
                  },
                  "syllabusMasteryChart": {
                    "totalWordCount": 4200,
                    "masteredWordCount": 2701,
                    "unmasteredWordCount": 1499,
                    "masteryPercent": 64,
                    "summaryLabel": "考纲词汇掌握概览",
                    "recommendation": "优先补齐高考核心场景词。"
                  },
                  "pastPaperVocabularyChart": {
                    "totalWordCount": 961,
                    "unknownWordCountBeforeSprint": 847,
                    "unknownWordCountAfterSprint": 716,
                    "projectedScoreGainLabel": "预计提分5-15分",
                    "recommendation": "先压降真题生词占比。"
                  },
                  "highFrequencyVocabularyChart": {
                    "basicCorePercent": 62,
                    "highScorePercent": 41,
                    "highlightLabel": "拉分词是提分核心突破项"
                  },
                  "vocabularyFrequencyBandChart": {
                    "bars": [
                      {"bandLabel": "高频词", "currentValue": 188.6, "priorityLabel": "优先学习", "themeColor": "#448aff"},
                      {"bandLabel": "中频词", "currentValue": 154.5, "priorityLabel": "重点突破", "themeColor": "#4caf50"},
                      {"bandLabel": "低频词", "currentValue": 70.4, "priorityLabel": "酌情学习", "themeColor": "#ff9800"}
                    ]
                  },
                  "frequencyPlan": {
                    "cards": [
                      {
                        "cadencePerWeek": 1,
                        "scoreGainLabel": "+5分",
                        "winRatePercent": 38,
                        "recommended": false,
                        "badgeLabel": "稳健",
                        "emphasisIcon": "①"
                      },
                      {
                        "cadencePerWeek": 2,
                        "scoreGainLabel": "+10分",
                        "winRatePercent": 55,
                        "recommended": false,
                        "badgeLabel": "均衡",
                        "emphasisIcon": "②"
                      },
                      {
                        "cadencePerWeek": 3,
                        "scoreGainLabel": "+15分",
                        "winRatePercent": 72,
                        "recommended": true,
                        "badgeLabel": "推荐",
                        "emphasisIcon": "③"
                      },
                      {
                        "cadencePerWeek": 5,
                        "scoreGainLabel": "+20分",
                        "winRatePercent": 88,
                        "recommended": false,
                        "badgeLabel": "冲刺",
                        "emphasisIcon": "④"
                      }
                    ],
                    "recommendationTitle": "💡建议策略",
                    "recommendationSummary": "7 天提分冲刺是首选节奏，按词频优先级记忆，不浪费时间；只攻克高频/中频核心词，2周15小时速记500-800必考词，快速缩小生词缺口。",
                    "phaseSuggestions": [
                      {"title": "考前半个月·核心突击期", "description": "围绕高频词建立记忆闭环。"},
                      {"title": "考前半小时·临阵巩固期", "description": "结合真题词做循环巩固。"}
                    ]
                  },
                  "scoreImprovementCaseStudy": {
                    "headline": "真实提分 · 效果可复制",
                    "learnerName": "王雷宇",
                    "studyPeriodLabel": "考前半个月·核心突击期",
                    "memorizedWordCount": 705,
                    "examHitWordCount": 237,
                    "hitRatePercent": 33.8,
                    "baselineScoreLabel": "70分以下",
                    "finalScore": 89,
                    "scoreGain": 19
                  }
                }
                """);
    }

    private JsonNode sampleAchievementPayload() throws Exception {
        return OBJECT_MAPPER.readTree("""
                {
                  "reportTitle": "高考英语临考突击学习成果报告",
                  "reportSubtitle": "2024真题 · 两周专项训练 · 真实提分效果",
                  "completionTitle": "恭喜完成两周考前突击专项训练",
                  "completionSubtitle": "基于2024英语真题试卷 · 真实学习效果分析",
                  "summaryMetrics": {
                    "vocabularyGrowthText": "+19",
                    "paperKnownWordsGrowthText": "+4",
                    "unknownWordHitRateText": "1.93%",
                    "learningEfficiencyText": "0.48倍"
                  },
                  "vocabularyComparison": {
                    "beforeValue": 2328,
                    "afterValue": 2347,
                    "beforeText": "2328 词",
                    "afterText": "2347 词",
                    "growthText": "+19 词"
                  },
                  "paperKnownWordsComparison": {
                    "beforeValue": 650,
                    "afterValue": 654,
                    "beforeText": "650 个",
                    "afterText": "654 个",
                    "growthText": "+4 个"
                  },
                  "examUnknownWordsHitStatus": {
                    "unknownWordHitRateText": "1.93%",
                    "learningEfficiencyText": "0.48倍",
                    "unknownWordsBeforeText": "207 个",
                    "unknownWordsAfterText": "203 个",
                    "reducedUnknownWordsText": "4 个",
                    "hitWords": ["number", "bear", "popular", "importance"]
                  }
                }
                """);
    }
}
