001package com.studentgui.test;
002
003import java.lang.reflect.Field;
004import java.nio.file.Path;
005import java.time.LocalDate;
006import java.util.Map;
007
008import static org.junit.jupiter.api.Assertions.assertTrue;
009import org.junit.jupiter.api.Test;
010
011import com.studentgui.apphelpers.Database;
012import com.studentgui.apphelpers.Helpers;
013import com.studentgui.apppages.Braille;
014import com.studentgui.apppages.JLineGraph;
015
016/**
017 * Test that generates example Braille exports (per-phase PNGs + MD/HTML)
018 * for the student "Test Student". This mirrors the export logic used in
019 * the Braille page submit handler but runs headlessly as a test so the
020 * agent can produce example files for review.
021 */
022public class ExportBrailleReportsTest {
023
024    @Test
025    /**
026     * Generate example Braille export files (PNG per phase, markdown and HTML
027     * report) for a test student in headless mode. Useful for validating the
028     * export rendering logic in CI environments.
029     */
030
031    public void generateBrailleExport() throws Exception {
032        // Force headless mode for chart rendering in CI-like environments
033        System.setProperty("java.awt.headless", "true");
034
035        Helpers.createFolderHierarchy();
036        // Ensure DB exists and schema is initialized (idempotent)
037        com.studentgui.apphelpers.SqlGenerate.initializeDatabase();
038
039        String student = "Test Student";
040        String progressType = "Braille";
041
042        // Instantiate the Braille page to ensure canonical parts are created
043        JLineGraph graph = new JLineGraph();
044        Braille braille = new Braille(student, LocalDate.now(), graph);
045
046        // Fetch historical rows + dates
047        Database.ResultsWithDates rwd = Database.fetchLatestAssessmentResultsWithDates(student, progressType, Integer.MAX_VALUE);
048
049        // Reflectively obtain the partCodes and human labels from Braille instance
050        Field pcField = Braille.class.getDeclaredField("partCodes");
051        pcField.setAccessible(true);
052        String[] partCodes = (String[]) pcField.get(braille);
053
054        Field partsField = Braille.class.getDeclaredField("parts");
055        partsField.setAccessible(true);
056        String[][] parts = (String[][]) partsField.get(braille);
057        String[] labels = new String[parts.length];
058        for (int i = 0; i < parts.length; i++) labels[i] = parts[i][1];
059
060        java.nio.file.Path out = Helpers.APP_HOME.resolve("StudentDataFiles").resolve(Helpers.safeName(student)).resolve("plots");
061        java.nio.file.Files.createDirectories(out);
062        String baseName = "Braille-example-" + java.time.LocalDate.now().toString();
063
064        if (rwd != null && rwd.rows != null && !rwd.rows.isEmpty()) {
065            graph.updateWithGroupedDataByDate(rwd.dates, rwd.rows, partCodes, labels);
066        } else {
067            // No historical data; create a single-row from zeros by reflection of Braille's fields
068            java.util.List<java.util.List<Integer>> rowsList = new java.util.ArrayList<>();
069            java.util.List<Integer> latest = new java.util.ArrayList<>();
070            for (int i = 0; i < partCodes.length; i++) latest.add(0);
071            rowsList.add(latest);
072            graph.updateWithGroupedData(rowsList, partCodes);
073        }
074
075        Map<String, Path> groups = graph.saveGroupedCharts(out, baseName, 1000, 240);
076
077        // Build simple markdown and html reports (reuse palette from JLineGraph)
078        StringBuilder md = new StringBuilder();
079        md.append("# ").append(student).append(" - ").append(java.time.LocalDate.now().toString()).append("\n\n");
080        for (Map.Entry<String, Path> e : groups.entrySet()) {
081            md.append("## ").append(e.getKey()).append("\n\n");
082            md.append("![](./").append(e.getValue().getFileName().toString()).append(")\n\n");
083        }
084        java.nio.file.Path mdFile = out.resolve(baseName + ".md");
085        java.nio.file.Files.writeString(mdFile, md.toString(), java.nio.charset.StandardCharsets.UTF_8);
086
087        String[] palette = JLineGraph.PALETTE_HEX;
088        java.util.LinkedHashMap<String, java.util.List<Integer>> groupsIdx = new java.util.LinkedHashMap<>();
089        for (int i = 0; i < partCodes.length; i++) {
090            String code = partCodes[i];
091            String grp = code != null && code.contains("_") ? code.split("_")[0] : code;
092            groupsIdx.computeIfAbsent(grp, k -> new java.util.ArrayList<>()).add(i);
093        }
094
095        StringBuilder html = new StringBuilder();
096        html.append("<!doctype html><html><head><meta charset=\"utf-8\"><title>");
097        html.append(student).append(" - ").append(java.time.LocalDate.now().toString()).append("</title>");
098        html.append("<style>body{font-family:sans-serif;margin:20px;} img{max-width:100%;height:auto;border:1px solid #ccc;margin-bottom:8px;} .legend{max-height:160px;overflow:auto;border:1px solid #ddd;padding:8px;margin-bottom:24px;} .legend-item{display:flex;align-items:center;gap:8px;padding:4px 0;} .swatch{width:18px;height:12px;border:1px solid #333;display:inline-block}</style>");
099        html.append("</head><body>");
100        html.append("<h1>").append(student).append(" - ").append(java.time.LocalDate.now().toString()).append("</h1>");
101        for (Map.Entry<String, Path> e2 : groups.entrySet()) {
102            String grp = e2.getKey();
103            String imgName = e2.getValue().getFileName().toString();
104            html.append("<h2>").append(grp).append("</h2>");
105            html.append("<div class=\"plot\"><img src=\"./").append(imgName).append("\" alt=\"").append(grp).append("\"></div>");
106            java.util.List<Integer> idxs = groupsIdx.getOrDefault(grp, new java.util.ArrayList<>());
107            html.append("<div class=\"legend\">");
108            for (int s = 0; s < idxs.size(); s++) {
109                int idx = idxs.get(s);
110                String code = partCodes[idx];
111                String human = labels[idx];
112                String seriesName = code + " - " + human;
113                String color = palette[s % palette.length];
114                html.append("<div class=\"legend-item\">");
115                html.append("<span class=\"swatch\" style=\"background:");
116                html.append(color).append(";\"></span>");
117                html.append("<div>").append(seriesName).append("</div></div>");
118            }
119            html.append("</div>");
120        }
121        html.append("</body></html>");
122        java.nio.file.Path htmlFile = out.resolve(baseName + ".html");
123        java.nio.file.Files.writeString(htmlFile, html.toString(), java.nio.charset.StandardCharsets.UTF_8);
124
125        System.out.println("Exported Braille report to: " + out.toAbsolutePath().toString());
126        for (Map.Entry<String, Path> e : groups.entrySet()) System.out.println(" - " + e.getKey() + " -> " + e.getValue().getFileName());
127        System.out.println("MD: " + mdFile.getFileName() + " HTML: " + htmlFile.getFileName());
128
129        // Quick assertion to ensure at least one image or the md file exists
130        assertTrue(java.nio.file.Files.exists(mdFile));
131    }
132}