package cn.yunzhixue.ability.center.examsprint.infrastructure.report.rendering.achievement;

import cn.yunzhixue.ability.center.examsprint.contracts.report.ExamSprintReportType;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.node.ObjectNode;
import org.junit.jupiter.api.Test;

import java.time.Instant;

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

class ClasspathAchievementExamSprintReportRendererTest {

    private static final ObjectMapper OBJECT_MAPPER = new ObjectMapper();

    @Test
    void renderBuildsAchievementHtmlAlignedWithDesignDraft() throws Exception {
        ClasspathAchievementExamSprintReportRenderer renderer = new ClasspathAchievementExamSprintReportRenderer(OBJECT_MAPPER);

        String html = renderer.render(samplePayload(), Instant.parse("2026-04-25T08:00:00Z"));

        assertThat(html)
                .contains("高考英语临考突击学习成果报告")
                .contains("2024真题 · 两周专项训练 · 真实提分效果")
                .contains("恭喜完成两周考前突击专项训练")
                .contains("class=\"report-container\"")
                .contains("class=\"result-header\"")
                .contains("class=\"result-grid\"")
                .contains("词汇量提升（个）")
                .contains("试卷熟词提升（个）")
                .contains("真题生词命中率")
                .contains("学习效率提升")
                .contains("模块一：词汇量对比")
                .contains("模块二：试卷熟词量对比")
                .contains("模块三：实考生词命中状况")
                .contains("class='achievement-bar-chart vocabulary-growth-chart'")
                .contains("class='achievement-bar-chart paper-known-words-chart'")
                .contains("chart-y-axis")
                .contains("chart-x-axis")
                .contains("chart-grid-line")
                .contains("chart-tick-label")
                .contains(".chart-tick-label {")
                .contains("font-size: 11px;")
                .contains("fill: #68768a;")
                .contains(">0</text>")
                .contains(">2347</text>")
                .contains(">654</text>")
                .contains("<rect class='chart-bar chart-bar-after' x='222' y='36' width='58' height='144' rx='10' ry='10' fill='#ff7d00'/>")
                .contains("<rect class='chart-bar chart-bar-after' x='222' y='36' width='58' height='144' rx='10' ry='10' fill='#3f8cff'/>")
                .contains("训练前词汇量：<span class=\"highlight\">2328 词</span>")
                .contains("训练后词汇量：<span class=\"highlight\">2347 词</span>")
                .contains("本次提升：<span class=\"highlight\">+19 词</span>")
                .contains("训练前熟词量：<span class=\"highlight\">650 个</span>")
                .contains("训练后熟词量：<span class=\"highlight\">654 个</span>")
                .contains("成功减少生词：<span class=\"highlight\">4 个</span>")
                .contains("class=\"word-list\"")
                .contains("class=\"word-item\">number</div>")
                .doesNotContain("cdn.jsdelivr.net")
                .doesNotContain("echarts")
                .doesNotContain("<script");

        assertThat(countOccurrences(html, "class='chart-grid-line'")).isEqualTo(6);
        assertThat(countOccurrences(html, "class='chart-tick-label'")).isEqualTo(6);
        assertThat(countOccurrences(html, "class='chart-y-axis'")).isEqualTo(2);
        assertThat(countOccurrences(html, "class='chart-x-axis'")).isEqualTo(2);
    }

    @Test
    void supportsOnlyAchievementReportType() {
        ClasspathAchievementExamSprintReportRenderer renderer = new ClasspathAchievementExamSprintReportRenderer(OBJECT_MAPPER);

        assertThat(renderer.supports(ExamSprintReportType.ACHIEVEMENT)).isTrue();
        assertThat(renderer.supports(ExamSprintReportType.OUTLOOK)).isFalse();
    }

    @Test
    void renderEscapesPayloadTextAndHitWords() throws Exception {
        ClasspathAchievementExamSprintReportRenderer renderer = new ClasspathAchievementExamSprintReportRenderer(OBJECT_MAPPER);
        ObjectNode payload = samplePayload().deepCopy();
        payload.put("reportTitle", "成果<script>alert(1)</script>");
        payload.with("examUnknownWordsHitStatus").withArray("hitWords").add("bear<script>");

        String html = renderer.render(payload, Instant.parse("2026-04-25T08:00:00Z"));

        assertThat(html)
                .contains("成果&lt;script&gt;alert(1)&lt;/script&gt;")
                .contains("bear&lt;script&gt;")
                .doesNotContain("<script>alert(1)</script>")
                .doesNotContain("bear<script>");
    }

    @Test
    void renderShowsEmptyStateWhenHitWordsAreEmpty() throws Exception {
        ClasspathAchievementExamSprintReportRenderer renderer = new ClasspathAchievementExamSprintReportRenderer(OBJECT_MAPPER);
        ObjectNode payload = samplePayload().deepCopy();
        payload.with("examUnknownWordsHitStatus").putArray("hitWords");

        String html = renderer.render(payload, Instant.parse("2026-04-25T08:00:00Z"));

        assertThat(html)
                .contains("class=\"word-empty\"")
                .contains("暂无命中单词")
                .doesNotContain("class=\"word-item\"");
    }

    @Test
    void renderShowsEmptyStateWhenHitWordsAreNull() throws Exception {
        ClasspathAchievementExamSprintReportRenderer renderer = new ClasspathAchievementExamSprintReportRenderer(OBJECT_MAPPER);
        ObjectNode payload = samplePayload().deepCopy();
        payload.with("examUnknownWordsHitStatus").putNull("hitWords");

        String html = renderer.render(payload, Instant.parse("2026-04-25T08:00:00Z"));

        assertThat(html)
                .contains("class=\"word-empty\"")
                .contains("暂无命中单词")
                .doesNotContain("class=\"word-item\"");
    }

    @Test
    void renderUsesSafeChartValuesWhenComparisonNumbersAreMissingOrNonFinite() throws Exception {
        ClasspathAchievementExamSprintReportRenderer renderer = new ClasspathAchievementExamSprintReportRenderer(OBJECT_MAPPER);
        ObjectNode payload = samplePayload().deepCopy();
        payload.with("vocabularyComparison").putNull("beforeValue");
        payload.with("vocabularyComparison").put("afterValue", Double.NaN);

        String html = renderer.render(payload, Instant.parse("2026-04-25T08:00:00Z"));

        assertThat(html)
                .contains("class='achievement-bar-chart vocabulary-growth-chart'")
                .doesNotContain("NaN")
                .doesNotContain("Infinity");
    }

    @Test
    void renderDoesNotExpandPlaceholdersInjectedByPayloadText() throws Exception {
        ClasspathAchievementExamSprintReportRenderer renderer = new ClasspathAchievementExamSprintReportRenderer(OBJECT_MAPPER);
        ObjectNode payload = samplePayload().deepCopy();
        payload.put("reportTitle", "{{hitWords}}");
        payload.with("vocabularyComparison").put("beforeText", "{{hitWords}}");

        String html = renderer.render(payload, Instant.parse("2026-04-25T08:00:00Z"));

        assertThat(html)
                .contains("<h1 class=\"report-title\">{{hitWords}}</h1>")
                .contains(">{{hitWords}}</text>")
                .contains("class=\"word-item\">number</div>");
    }

    private JsonNode samplePayload() 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"]
                  }
                }
                """);
    }

    private int countOccurrences(String value, String substring) {
        int count = 0;
        int index = 0;
        while ((index = value.indexOf(substring, index)) >= 0) {
            count++;
            index += substring.length();
        }
        return count;
    }
}
