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

import cn.yunzhixue.ability.center.examsprint.contracts.report.ExamSprintReportType;
import cn.yunzhixue.ability.center.examsprint.contracts.report.OutlookExamSprintReportPayload;
import cn.yunzhixue.ability.center.examsprint.domain.report.ExamSprintReportRenderer;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
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.List;
import java.util.Locale;
import java.util.Objects;
import java.util.regex.Pattern;

@Component
public class ClasspathOutlookExamSprintReportRenderer implements ExamSprintReportRenderer {

    private static final String TEMPLATE_RESOURCE = "templates/outlook-exam-sprint-report-template.html";
    private static final String DEFAULT_THEME_COLOR = "#448aff";
    private static final Pattern HEX_COLOR_PATTERN = Pattern.compile("^#(?:[0-9a-fA-F]{3}|[0-9a-fA-F]{6})$");

    private final ObjectMapper objectMapper;

    public ClasspathOutlookExamSprintReportRenderer(ObjectMapper objectMapper) {
        this.objectMapper = Objects.requireNonNull(objectMapper, "objectMapper");
    }

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

    @Override
    public String render(JsonNode payload, Instant generatedAt) {
        try {
            OutlookExamSprintReportPayload reportPayload = objectMapper.treeToValue(payload, OutlookExamSprintReportPayload.class);
            return loadTemplate()
                    .replace("{{syllabusMasteryChart}}", renderSyllabusMasteryChart(reportPayload.syllabusMasteryChart()))
                    .replace("{{pastPaperVocabularyChart}}", renderPastPaperVocabularyChart(reportPayload.pastPaperVocabularyChart()))
                    .replace("{{highFrequencyVocabularyChart}}", renderHighFrequencyVocabularyChart(reportPayload.highFrequencyVocabularyChart()))
                    .replace("{{vocabularyFrequencyBandChart}}", renderVocabularyFrequencyBandChart(reportPayload.vocabularyFrequencyBandChart()))
                    .replace("{{studySuggestionSection}}", renderStudySuggestionSection(reportPayload.frequencyPlan()))
                    .replace("{{scoreImprovementCaseStudy}}", renderScoreImprovementCaseStudy(reportPayload.scoreImprovementCaseStudy()));
        } catch (IOException exception) {
            throw new UncheckedIOException("Failed to load outlook exam sprint report template", exception);
        } catch (Exception exception) {
            throw new IllegalStateException("Failed to render outlook exam sprint report", exception);
        }
    }

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

    private String renderSyllabusMasteryChart(OutlookExamSprintReportPayload.SyllabusMasteryChart chart) {
        double masteredPercent = percentage(chart.masteredWordCount(), chart.totalWordCount());
        double unmasteredPercent = percentage(chart.unmasteredWordCount(), chart.totalWordCount());
        double endAngle = -90 + (Math.max(0d, Math.min(100d, masteredPercent)) * 3.6);

        return new StringBuilder()
                .append("<div class='card'>")
                .append("<h3 class='card-title'>考纲词汇掌握情况</h3>")
                .append("<div class='chart-box'>")
                .append("<svg class='syllabus-donut-chart' xmlns='http://www.w3.org/2000/svg' viewBox='0 0 220 220' role='img' aria-label='考纲词汇掌握情况'>")
                .append("<circle class='chart-track' cx='110' cy='110' r='76' fill='none' stroke='#e8eef7' stroke-width='18'></circle>")
                .append(renderProgressRing("donut-mastered-arc", "donut-mastered-full-circle", 110, 110, 76, masteredPercent, "#448aff"))
                .append("<path class='donut-unmastered-arc' d='")
                .append(describeArc(110, 110, 76, endAngle, 270))
                .append("' stroke='#e8eef7' stroke-width='18' fill='none' stroke-linecap='round'/>")
                .append("<text class='chart-percent' x='110' y='106' text-anchor='middle' fill='#2b4c8a' font-size='28' font-weight='700'>")
                .append(formatTwoDecimals(masteredPercent)).append("%</text>")
                .append("<text class='chart-caption' x='110' y='131' text-anchor='middle' fill='#5f6b7a' font-size='14'>掌握率</text>")
                .append("</svg>")
                .append("</div>")
                .append("<div class='data-text'>")
                .append("考纲总量：<span class='highlight'>").append(chart.totalWordCount())
                .append("词</span> | 已掌握：<span class='highlight'>").append(chart.masteredWordCount())
                .append("词（").append(formatTwoDecimals(masteredPercent)).append("%）</span><br/>")
                .append("未掌握：<span class='highlight'>").append(chart.unmasteredWordCount())
                .append("词（").append(formatTwoDecimals(unmasteredPercent)).append("%）</span>")
                .append("</div>")
                .append("</div>")
                .toString();
    }

    private String renderPastPaperVocabularyChart(OutlookExamSprintReportPayload.PastPaperVocabularyChart chart) {
        int maxValue = Math.max(chart.totalWordCount(), chart.unknownWordCountBeforeSprint());
        int totalHeight = barHeight(chart.totalWordCount(), maxValue);
        int unknownHeight = barHeight(chart.unknownWordCountBeforeSprint(), maxValue);
        double beforePercent = percentage(chart.unknownWordCountBeforeSprint(), chart.totalWordCount());
        double afterPercent = percentage(chart.unknownWordCountAfterSprint(), chart.totalWordCount());

        return new StringBuilder()
                .append("<div class='card'>")
                .append("<h3 class='card-title'>真题试卷词汇掌握情况</h3>")
                .append("<div class='chart-box'>")
                .append("<svg class='past-paper-column-chart' xmlns='http://www.w3.org/2000/svg' viewBox='0 0 320 220' role='img' aria-label='真题试卷词汇掌握情况'>")
                .append(renderChartAxes(320))
                .append("<rect class='chart-column total-column' x='84' y='").append(180 - totalHeight)
                .append("' width='56' height='").append(totalHeight).append("' rx='8' ry='8' fill='#448aff'/>")
                .append("<rect class='chart-column unknown-column' x='180' y='").append(180 - unknownHeight)
                .append("' width='56' height='").append(unknownHeight).append("' rx='8' ry='8' fill='#ff9800'/>")
                .append("<text class='chart-caption' x='112' y='198' text-anchor='middle'>总词量</text>")
                .append("<text class='chart-caption' x='208' y='198' text-anchor='middle'>生词量</text>")
                .append("</svg>")
                .append("</div>")
                .append("<div class='data-text'>")
                .append("真题总词：").append(chart.totalWordCount()).append("词 | 生词量：")
                .append(chart.unknownWordCountBeforeSprint()).append("词（")
                .append(formatTwoDecimals(beforePercent)).append("%）<br/>")
                .append("冲刺后生词：").append(chart.unknownWordCountAfterSprint())
                .append("词，生词占比降至").append(formatTwoDecimals(afterPercent)).append("%，")
                .append(escape(chart.projectedScoreGainLabel()))
                .append("</div>")
                .append("<p class='data-text'>").append(escape(chart.recommendation())).append("</p>")
                .append("</div>")
                .toString();
    }

    private String renderHighFrequencyVocabularyChart(OutlookExamSprintReportPayload.HighFrequencyVocabularyChart chart) {
        int basicHeight = barHeight(chart.basicCorePercent(), 100);
        int highScoreHeight = barHeight(chart.highScorePercent(), 100);

        return new StringBuilder()
                .append("<div class='card'>")
                .append("<h3 class='card-title'>常考词汇掌握情况</h3>")
                .append("<div class='chart-box'>")
                .append("<svg class='high-frequency-column-chart' xmlns='http://www.w3.org/2000/svg' viewBox='0 0 320 220' role='img' aria-label='常考词汇掌握情况'>")
                .append(renderChartAxes(320))
                .append("<rect class='chart-column basic-core-column' x='84' y='").append(180 - basicHeight)
                .append("' width='56' height='").append(basicHeight).append("' rx='8' ry='8' fill='#42a5f5'/>")
                .append("<rect class='chart-column high-score-column' x='180' y='").append(180 - highScoreHeight)
                .append("' width='56' height='").append(highScoreHeight).append("' rx='8' ry='8' fill='#66bb6a'/>")
                .append("<text class='chart-caption' x='112' y='198' text-anchor='middle'>基础必会词</text>")
                .append("<text class='chart-caption' x='208' y='198' text-anchor='middle'>高分拉分词</text>")
                .append("</svg>")
                .append("</div>")
                .append("<div class='data-text'>基础必会词：掌握率").append(chart.basicCorePercent())
                .append("%<br/>高分拉分词：掌握率").append(chart.highScorePercent()).append("%</div>")
                .append("<p class='data-text'><span class='highlight'>")
                .append(escape(chart.highlightLabel()))
                .append("</span></p>")
                .append("</div>")
                .toString();
    }

    private String renderVocabularyFrequencyBandChart(OutlookExamSprintReportPayload.VocabularyFrequencyBandChart chart) {
        List<OutlookExamSprintReportPayload.VocabularyFrequencyBar> bars = chart.bars();
        double highest = bars.stream()
                .mapToDouble(OutlookExamSprintReportPayload.VocabularyFrequencyBar::currentValue)
                .max()
                .orElse(1d);
        int maxScaled = (int) Math.ceil(highest * 10d);

        StringBuilder builder = new StringBuilder();
        builder.append("<div class='card'>")
                .append("<h3 class='card-title'>词频区间掌握度</h3>")
                .append("<div class='chart-box'>")
                .append("<svg class='frequency-band-column-chart' xmlns='http://www.w3.org/2000/svg' viewBox='0 0 360 220' role='img' aria-label='词频区间掌握度'>")
                .append(renderChartAxes(360));

        int[] xPositions = {70, 160, 250};
        String[] columnClasses = {"high-band-column", "mid-band-column", "low-band-column"};
        for (int index = 0; index < Math.min(3, bars.size()); index++) {
            OutlookExamSprintReportPayload.VocabularyFrequencyBar bar = bars.get(index);
            int height = barHeight((int) Math.round(bar.currentValue() * 10d), maxScaled);
            int x = xPositions[index];

            builder.append("<rect class='chart-column ").append(columnClasses[index]).append("' x='")
                    .append(x).append("' y='").append(180 - height).append("' width='54' height='")
                    .append(height).append("' rx='8' ry='8' fill='").append(safeColor(bar.themeColor())).append("'/>")
                    .append("<text class='donut-label-text' x='").append(x + 27).append("' y='198' text-anchor='middle'>")
                    .append(escape(bar.bandLabel())).append("</text>")
                    .append("<text class='chart-caption' x='").append(x + 27).append("' y='20' text-anchor='middle'>")
                    .append(escape(bar.priorityLabel())).append("</text>");
        }

        builder.append("</svg>")
                .append("</div>")
                .append("<div class='data-text'>");

        for (int index = 0; index < Math.min(3, bars.size()); index++) {
            OutlookExamSprintReportPayload.VocabularyFrequencyBar bar = bars.get(index);
            if (index > 0) {
                builder.append("<br/>");
            }
            builder.append(escape(bar.bandLabel())).append("：")
                    .append(formatOneDecimal(bar.currentValue()))
                    .append("（")
                    .append(escape(bar.priorityLabel()))
                    .append("）");
        }

        builder.append("</div>")
                .append("</div>");
        return builder.toString();
    }

    private String renderStudySuggestionSection(OutlookExamSprintReportPayload.FrequencyPlan frequencyPlan) {
        StringBuilder builder = new StringBuilder();
        builder.append("<div class='suggest-item'>")
                .append("<h4>🎯 练习学案频率与提分规划</h4>")
                .append("<p class='text-desc'>")
                .append(escape(frequencyPlan.recommendationSummary()))
                .append("</p>")
                .append("</div>")
                .append("<table class='frequency-table' role='presentation'><tr class='frequency-row'>");

        for (int index = 0; index < frequencyPlan.cards().size(); index++) {
            OutlookExamSprintReportPayload.FrequencyPlanCard card = frequencyPlan.cards().get(index);
            int columnNumber = index + 1;
            builder.append("<td class='frequency-cell frequency-cell-").append(columnNumber).append("'>")
                    .append("<div class='freq-card card-").append(columnNumber);
            if (card.recommended()) {
                builder.append(" active");
            }
            builder.append("'>")
                    .append("<div class='freq-header'>")
                    .append(card.cadencePerWeek()).append("套/周");

            if (card.recommended()) {
                String badgeLabel = card.badgeLabel() == null || card.badgeLabel().isBlank() ? "推荐" : card.badgeLabel();
                builder.append(" <span class='badge'>").append(escape(badgeLabel)).append("</span>");
            } else if (card.cadencePerWeek() == 5) {
                builder.append(" <span class='crown'>★</span>");
            }

            builder.append("</div>")
                    .append("<div class='freq-progress'><span style='width:")
                    .append(card.winRatePercent()).append("%'></span></div>")
                    .append("<div class='freq-data'>提升 <strong>")
                    .append(escape(card.scoreGainLabel()))
                    .append("</strong> · 胜率 <strong>")
                    .append(card.winRatePercent())
                    .append("%</strong></div>")
                    .append("</div>")
                    .append("</td>");
        }

        builder.append("</tr></table>")
                .append("<div class='suggest-note'><strong>")
                .append(escape(frequencyPlan.recommendationTitle()))
                .append("：</strong>")
                .append(escape(frequencyPlan.recommendationSummary()))
                .append("</div>")
                .append("<div class='suggest-box'>");

        for (OutlookExamSprintReportPayload.PhaseSuggestion suggestion : frequencyPlan.phaseSuggestions()) {
            builder.append("<div class='suggest-item'>")
                    .append("<h4>").append(escape(suggestion.title())).append("</h4>")
                    .append("<p>").append(escape(suggestion.description())).append("</p>")
                    .append("</div>");
        }

        builder.append("</div>");
        return builder.toString();
    }

    private String renderScoreImprovementCaseStudy(OutlookExamSprintReportPayload.ScoreImprovementCaseStudy caseStudy) {
        double progressPercent = Math.max(0d, Math.min(100d, caseStudy.hitRatePercent()));
        double endAngle = -90 + (progressPercent * 3.6);

        return new StringBuilder()
                .append("<div class='student-case'>")
                .append("<table class='student-case-table' role='presentation'>")
                .append("<tr class='student-case-row'>")
                .append("<td class='case-chart-cell'>")
                .append("<div class='case-chart'>")
                .append("<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 260 260' role='img' aria-label='上届学员提分案例图示'>")
                .append("<circle cx='130' cy='130' r='86' fill='none' stroke='#ffe3c7' stroke-width='18'></circle>")
                .append(renderProgressRing("case-progress-arc", "case-progress-full-circle", 130, 130, 86, progressPercent, "#ff7d00"))
                .append("<text x='130' y='124' text-anchor='middle' fill='#8a5d36' font-size='26' font-weight='700'>")
                .append(formatOneDecimal(progressPercent)).append("%</text>")
                .append("<text x='130' y='152' text-anchor='middle' fill='#8a5d36' font-size='12'>命中率</text>")
                .append("</svg>")
                .append("</div>")
                .append("</td>")
                .append("<td class='case-info-cell'>")
                .append("<div class='case-info'>")
                .append("<h3>").append(escape(caseStudy.headline())).append("</h3>")
                .append("<p><strong>学员：</strong>").append(escape(caseStudy.learnerName())).append("</p>")
                .append("<p><strong>阶段：</strong>").append(escape(caseStudy.studyPeriodLabel())).append("</p>")
                .append("<p><strong>记忆词汇：</strong>").append(caseStudy.memorizedWordCount())
                .append("词 | 高考命中：").append(caseStudy.examHitWordCount())
                .append("词 | 命中率：").append(formatOneDecimal(progressPercent)).append("%</p>")
                .append("<p><strong>提升分数：</strong><span class='highlight score-gain'>+")
                .append(caseStudy.scoreGain()).append("分</span></p>")
                .append("<p>").append(escape(caseStudy.baselineScoreLabel())).append(" → ")
                .append(caseStudy.finalScore()).append("分</p>")
                .append("</div>")
                .append("</td>")
                .append("</tr>")
                .append("</table>")
                .append("</div>")
                .toString();
    }

    private String renderChartAxes(int width) {
        return new StringBuilder()
                .append("<line class='chart-axis' x1='34' y1='18' x2='34' y2='180' stroke='#9aa6b2' stroke-width='1'/>")
                .append("<line class='chart-axis' x1='34' y1='180' x2='").append(width - 24)
                .append("' y2='180' stroke='#9aa6b2' stroke-width='1'/>")
                .toString();
    }

    private int barHeight(int value, int maxValue) {
        if (maxValue <= 0) {
            return 0;
        }
        double ratio = Math.max(0d, Math.min(1d, value / (double) maxValue));
        return Math.max(16, (int) Math.round(ratio * 130));
    }

    private double percentage(int numerator, int denominator) {
        if (denominator <= 0) {
            return 0d;
        }
        return (numerator * 100d) / denominator;
    }

    private String describeArc(double centerX, double centerY, double radius, double startAngle, double endAngle) {
        double startRadians = Math.toRadians(startAngle);
        double endRadians = Math.toRadians(endAngle);
        double startX = centerX + (radius * Math.cos(startRadians));
        double startY = centerY + (radius * Math.sin(startRadians));
        double endX = centerX + (radius * Math.cos(endRadians));
        double endY = centerY + (radius * Math.sin(endRadians));
        int largeArcFlag = Math.abs(endAngle - startAngle) <= 180 ? 0 : 1;
        return new StringBuilder()
                .append("M ").append(formatTwoDecimals(startX)).append(' ')
                .append(formatTwoDecimals(startY)).append(' ')
                .append("A ").append(formatTwoDecimals(radius)).append(' ')
                .append(formatTwoDecimals(radius)).append(" 0 ")
                .append(largeArcFlag).append(" 1 ")
                .append(formatTwoDecimals(endX)).append(' ')
                .append(formatTwoDecimals(endY))
                .toString();
    }

    private String renderProgressRing(String arcClass,
                                      String fullCircleClass,
                                      double centerX,
                                      double centerY,
                                      double radius,
                                      double percent,
                                      String strokeColor) {
        double boundedPercent = Math.max(0d, Math.min(100d, percent));
        if (boundedPercent >= 100d) {
            return new StringBuilder()
                    .append("<circle class='").append(fullCircleClass).append("' cx='")
                    .append(formatTwoDecimals(centerX)).append("' cy='")
                    .append(formatTwoDecimals(centerY)).append("' r='")
                    .append(formatTwoDecimals(radius)).append("' fill='none' stroke='")
                    .append(strokeColor).append("' stroke-width='18'></circle>")
                    .toString();
        }
        if (boundedPercent <= 0d) {
            return "";
        }
        double endAngle = -90 + (boundedPercent * 3.6);
        return new StringBuilder()
                .append("<path class='").append(arcClass).append("' d='")
                .append(describeArc(centerX, centerY, radius, -90, endAngle))
                .append("' stroke='").append(strokeColor)
                .append("' stroke-width='18' fill='none' stroke-linecap='round'/>")
                .toString();
    }

    private String formatOneDecimal(double value) {
        return String.format(Locale.ROOT, "%.1f", value);
    }

    private String formatTwoDecimals(double value) {
        return String.format(Locale.ROOT, "%.2f", value);
    }

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

    private String safeColor(String value) {
        if (value == null) {
            return DEFAULT_THEME_COLOR;
        }
        String trimmed = value.trim();
        if (HEX_COLOR_PATTERN.matcher(trimmed).matches()) {
            return trimmed;
        }
        return DEFAULT_THEME_COLOR;
    }
}
