001/*
002 * Copyright 2025 devteam@scivicslab.com
003 *
004 * Licensed under the Apache License, Version 2.0 (the "License");
005 * you may not use this file except in compliance with the License.
006 * You may obtain a copy of the License at
007 *
008 *     http://www.apache.org/licenses/LICENSE-2.0
009 *
010 * Unless required by applicable law or agreed to in writing,
011 * software distributed under the License is distributed on an
012 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND,
013 * either express or implied.  See the License for the
014 * specific language governing permissions and limitations
015 * under the License.
016 */
017
018package com.scivicslab.actoriac.report.sections.basic;
019
020import com.scivicslab.actoriac.report.SectionBuilder;
021
022import java.sql.Connection;
023import java.sql.PreparedStatement;
024import java.sql.ResultSet;
025import java.sql.SQLException;
026import java.util.ArrayList;
027import java.util.LinkedHashSet;
028import java.util.List;
029import java.util.logging.Logger;
030
031/**
032 * POJO section builder that collects and outputs messages with % prefix.
033 *
034 * <p>Pure business logic - no {@code CallableByActionName}.
035 * Use {@link CheckResultsSectionIIAR} to expose as an actor.</p>
036 *
037 * <p>The % prefix is used in workflows to mark lines that should be
038 * collected and displayed in the final report. This is useful for
039 * check/status reporting:</p>
040 *
041 * <h2>Usage in workflows:</h2>
042 * <pre>
043 * - actor: this
044 *   method: executeCommand
045 *   arguments:
046 *     - |
047 *       if command -v node > /dev/null; then
048 *         echo "%[OK] Node.js: $(node --version)"
049 *       else
050 *         echo "%[ERROR] Node.js: not found"
051 *       fi
052 * </pre>
053 *
054 * <h2>Output example:</h2>
055 * <pre>
056 * [Check Results]
057 * [OK] Node.js: v18.0.0
058 * [OK] yarn: 1.22.19
059 * [ERROR] Maven: not found
060 * </pre>
061 *
062 * @author devteam@scivicslab.com
063 * @since 2.16.0
064 */
065public class CheckResultsSection implements SectionBuilder {
066
067    private static final Logger logger = Logger.getLogger(CheckResultsSection.class.getName());
068
069    /** Prefix for messages to be included in the report. */
070    private static final String REPORT_PREFIX = "%";
071
072    private Connection connection;
073    private long sessionId = -1;
074
075    /**
076     * Sets the database connection for log queries.
077     *
078     * @param connection the JDBC connection to the H2 log database
079     */
080    public void setConnection(Connection connection) {
081        this.connection = connection;
082    }
083
084    /**
085     * Sets the session ID to query logs from.
086     *
087     * @param sessionId the session ID
088     */
089    public void setSessionId(long sessionId) {
090        this.sessionId = sessionId;
091    }
092
093    @Override
094    public String generate() {
095        if (connection == null || sessionId < 0) {
096            logger.warning("CheckResultsSection: connection or sessionId not set");
097            return "";
098        }
099
100        try {
101            List<String> messages = getReportMessages();
102            if (messages.isEmpty()) {
103                return "";  // No check results, skip this section
104            }
105
106            StringBuilder sb = new StringBuilder();
107            sb.append("[Check Results]\n");
108            for (String msg : messages) {
109                sb.append(msg).append("\n");
110            }
111            return sb.toString();
112
113        } catch (SQLException e) {
114            logger.warning("CheckResultsSection: SQL error: " + e.getMessage());
115            return "";
116        }
117    }
118
119    /**
120     * Get messages with % prefix from logs.
121     *
122     * @return list of messages (without the % prefix)
123     * @throws SQLException if database query fails
124     */
125    private List<String> getReportMessages() throws SQLException {
126        List<String> messages = new ArrayList<>();
127
128        String sql = "SELECT message FROM logs WHERE session_id = ? ORDER BY timestamp";
129        try (PreparedStatement ps = connection.prepareStatement(sql)) {
130            ps.setLong(1, sessionId);
131            try (ResultSet rs = ps.executeQuery()) {
132                while (rs.next()) {
133                    String message = rs.getString("message");
134                    if (message != null) {
135                        // Extract lines starting with %
136                        for (String line : message.split("\n")) {
137                            String trimmed = line.trim();
138                            // Handle prefixes like [node-xxx] %message
139                            String cleaned = trimmed.replaceFirst("^\\[node-[^\\]]+\\]\\s*", "");
140                            if (cleaned.startsWith(REPORT_PREFIX)) {
141                                messages.add(cleaned.substring(1).trim());
142                            }
143                        }
144                    }
145                }
146            }
147        }
148
149        // Remove duplicates while preserving order, then sort
150        List<String> uniqueMessages = new ArrayList<>(new LinkedHashSet<>(messages));
151        uniqueMessages.sort(null);
152        return uniqueMessages;
153    }
154
155    @Override
156    public String getTitle() {
157        return null;  // Title is embedded in content
158    }
159}