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

import cn.yunzhixue.ability.center.examsprint.contracts.report.AchievementExamSprintReportPayload;
import cn.yunzhixue.ability.center.examsprint.contracts.report.ExamSprintReportType;
import cn.yunzhixue.ability.center.examsprint.domain.report.ExamSprintReportRenderer;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.core.io.ClassPathResource;
import org.springframework.stereotype.Component;

import java.io.IOException;
import java.io.InputStream;
import java.io.UncheckedIOException;
import java.nio.charset.StandardCharsets;
import java.time.Instant;
import java.util.LinkedHashMap;
import java.util.Map;
import java.util.Objects;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

@Component
public class ClasspathAchievementExamSprintReportRenderer implements ExamSprintReportRenderer {

    private static final String TEMPLATE_RESOURCE = "templates/achievement-exam-sprint-report-template.html";
    private static final Pattern TEMPLATE_PLACEHOLDER_PATTERN = Pattern.compile("\\{\\{([A-Za-z0-9]+)}}");

    private final ObjectMapper objectMapper;
    private final AchievementExamSprintReportSvgChartBuilder chartBuilder;

    @Autowired
    public ClasspathAchievementExamSprintReportRenderer(ObjectMapper objectMapper) {
        this(objectMapper, new AchievementExamSprintReportSvgChartBuilder());
    }

    ClasspathAchievementExamSprintReportRenderer(ObjectMapper objectMapper,
                                                 AchievementExamSprintReportSvgChartBuilder chartBuilder) {
        this.objectMapper = Objects.requireNonNull(objectMapper, "objectMapper");
        this.chartBuilder = Objects.requireNonNull(chartBuilder, "chartBuilder");
    }

    @Override
    public boolean supports(ExamSprintReportType reportType) {
        return reportType == ExamSprintReportType.ACHIEVEMENT;
    }

    @Override
    public String render(JsonNode payload, Instant generatedAt) {
        try {
            AchievementExamSprintReportPayload reportPayload = objectMapper.treeToValue(
                    payload,
                    AchievementExamSprintReportPayload.class
            );
            AchievementExamSprintReportPayload.SummaryMetrics summary = reportPayload.summaryMetrics();
            AchievementExamSprintReportPayload.Comparison vocabulary = reportPayload.vocabularyComparison();
            AchievementExamSprintReportPayload.Comparison paperKnownWords = reportPayload.paperKnownWordsComparison();
            AchievementExamSprintReportPayload.ExamUnknownWordsHitStatus hitStatus = reportPayload.examUnknownWordsHitStatus();

            return renderTemplate(loadTemplate(), placeholders(reportPayload, summary, vocabulary, paperKnownWords, hitStatus));
        } catch (IOException exception) {
            throw new UncheckedIOException("Failed to load achievement exam sprint report template", exception);
        } catch (Exception exception) {
            throw new IllegalStateException("Failed to render achievement exam sprint report", exception);
        }
    }

    private Map<String, String> placeholders(AchievementExamSprintReportPayload reportPayload,
                                             AchievementExamSprintReportPayload.SummaryMetrics summary,
                                             AchievementExamSprintReportPayload.Comparison vocabulary,
                                             AchievementExamSprintReportPayload.Comparison paperKnownWords,
                                             AchievementExamSprintReportPayload.ExamUnknownWordsHitStatus hitStatus) {
        Map<String, String> placeholders = new LinkedHashMap<>();
        placeholders.put("reportTitle", escape(reportPayload.reportTitle()));
        placeholders.put("reportSubtitle", escape(reportPayload.reportSubtitle()));
        placeholders.put("completionTitle", escape(reportPayload.completionTitle()));
        placeholders.put("completionSubtitle", escape(reportPayload.completionSubtitle()));
        placeholders.put("vocabularyGrowthText", escape(summary.vocabularyGrowthText()));
        placeholders.put("paperKnownWordsGrowthText", escape(summary.paperKnownWordsGrowthText()));
        placeholders.put("unknownWordHitRateText", escape(summary.unknownWordHitRateText()));
        placeholders.put("learningEfficiencyText", escape(summary.learningEfficiencyText()));
        placeholders.put("vocabularyComparisonChart", renderVocabularyComparisonChart(vocabulary));
        placeholders.put("paperKnownWordsComparisonChart", renderPaperKnownWordsComparisonChart(paperKnownWords));
        placeholders.put("vocabularyBeforeText", escape(vocabulary.beforeText()));
        placeholders.put("vocabularyAfterText", escape(vocabulary.afterText()));
        placeholders.put("vocabularyGrowthDetailText", escape(vocabulary.growthText()));
        placeholders.put("paperKnownWordsBeforeText", escape(paperKnownWords.beforeText()));
        placeholders.put("paperKnownWordsAfterText", escape(paperKnownWords.afterText()));
        placeholders.put("paperKnownWordsGrowthDetailText", escape(paperKnownWords.growthText()));
        placeholders.put("unknownWordsBeforeText", escape(hitStatus.unknownWordsBeforeText()));
        placeholders.put("unknownWordsAfterText", escape(hitStatus.unknownWordsAfterText()));
        placeholders.put("reducedUnknownWordsText", escape(hitStatus.reducedUnknownWordsText()));
        placeholders.put("hitStatusUnknownWordHitRateText", escape(hitStatus.unknownWordHitRateText()));
        placeholders.put("hitStatusLearningEfficiencyText", escape(hitStatus.learningEfficiencyText()));
        placeholders.put("hitWords", renderHitWords(hitStatus));
        return placeholders;
    }

    private String renderTemplate(String template, Map<String, String> placeholders) {
        Matcher matcher = TEMPLATE_PLACEHOLDER_PATTERN.matcher(template);
        StringBuffer rendered = new StringBuffer();
        while (matcher.find()) {
            String placeholderName = matcher.group(1);
            String replacement = placeholders.getOrDefault(placeholderName, matcher.group(0));
            matcher.appendReplacement(rendered, Matcher.quoteReplacement(replacement));
        }
        matcher.appendTail(rendered);
        return rendered.toString();
    }

    private String loadTemplate() throws IOException {
        try (InputStream inputStream = new ClassPathResource(TEMPLATE_RESOURCE).getInputStream()) {
            return new String(inputStream.readAllBytes(), StandardCharsets.UTF_8);
        }
    }

    private String renderVocabularyComparisonChart(AchievementExamSprintReportPayload.Comparison comparison) {
        return chartBuilder.comparisonBarChart(
                "vocabulary-growth-chart",
                "词汇量对比",
                "训练前",
                safeNonNegativeFinite(comparison.beforeValue()),
                comparison.beforeText(),
                "训练后",
                safeNonNegativeFinite(comparison.afterValue()),
                comparison.afterText(),
                "#ff7d00"
        );
    }

    private String renderPaperKnownWordsComparisonChart(AchievementExamSprintReportPayload.Comparison comparison) {
        return chartBuilder.comparisonBarChart(
                "paper-known-words-chart",
                "试卷熟词量对比",
                "训练前",
                safeNonNegativeFinite(comparison.beforeValue()),
                comparison.beforeText(),
                "训练后",
                safeNonNegativeFinite(comparison.afterValue()),
                comparison.afterText(),
                "#3f8cff"
        );
    }

    private String renderHitWords(AchievementExamSprintReportPayload.ExamUnknownWordsHitStatus hitStatus) {
        if (hitStatus == null || hitStatus.hitWords() == null || hitStatus.hitWords().isEmpty()) {
            return "<div class=\"word-empty\">暂无命中单词</div>";
        }

        StringBuilder builder = new StringBuilder();
        for (String hitWord : hitStatus.hitWords()) {
            builder.append("<div class=\"word-item\">")
                    .append(escape(hitWord))
                    .append("</div>");
        }
        return builder.toString();
    }

    private double safeNonNegativeFinite(Double value) {
        if (value == null || !Double.isFinite(value) || value < 0d) {
            return 0d;
        }
        return value;
    }

    private String escape(String value) {
        if (value == null) {
            return "";
        }
        return value.replace("&", "&amp;")
                .replace("<", "&lt;")
                .replace(">", "&gt;")
                .replace("\"", "&quot;")
                .replace("'", "&#39;");
    }
}
