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.log;
019
020import java.time.Duration;
021import java.time.LocalDateTime;
022import java.time.ZoneId;
023import java.time.format.DateTimeFormatter;
024import java.util.List;
025
026/**
027 * Summary of a workflow execution session.
028 *
029 * @author devteam@scivicslab.com
030 */
031public class SessionSummary {
032    private static final DateTimeFormatter ISO_FORMATTER =
033            DateTimeFormatter.ofPattern("yyyy-MM-dd'T'HH:mm:ssXXX");
034    private static final ZoneId SYSTEM_ZONE = ZoneId.systemDefault();
035
036    private final long sessionId;
037    private final String workflowName;
038    private final String overlayName;
039    private final String inventoryName;
040    private final LocalDateTime startedAt;
041    private final LocalDateTime endedAt;
042    private final int nodeCount;
043    private final SessionStatus status;
044    private final int successCount;
045    private final int failedCount;
046    private final List<String> failedNodes;
047    private final int totalLogEntries;
048    private final int errorCount;
049
050    // Execution context for reproducibility
051    private final String cwd;
052    private final String gitCommit;
053    private final String gitBranch;
054    private final String commandLine;
055    private final String actorIacVersion;
056    private final String actorIacCommit;
057
058    /**
059     * Legacy constructor for backward compatibility.
060     */
061    public SessionSummary(long sessionId, String workflowName, String overlayName,
062                          String inventoryName, LocalDateTime startedAt,
063                          LocalDateTime endedAt, int nodeCount, SessionStatus status,
064                          int successCount, int failedCount, List<String> failedNodes,
065                          int totalLogEntries, int errorCount) {
066        this(sessionId, workflowName, overlayName, inventoryName, startedAt, endedAt,
067             nodeCount, status, successCount, failedCount, failedNodes, totalLogEntries, errorCount,
068             null, null, null, null, null, null);
069    }
070
071    /**
072     * Full constructor with execution context.
073     */
074    public SessionSummary(long sessionId, String workflowName, String overlayName,
075                          String inventoryName, LocalDateTime startedAt,
076                          LocalDateTime endedAt, int nodeCount, SessionStatus status,
077                          int successCount, int failedCount, List<String> failedNodes,
078                          int totalLogEntries, int errorCount,
079                          String cwd, String gitCommit, String gitBranch,
080                          String commandLine, String actorIacVersion, String actorIacCommit) {
081        this.sessionId = sessionId;
082        this.workflowName = workflowName;
083        this.overlayName = overlayName;
084        this.inventoryName = inventoryName;
085        this.startedAt = startedAt;
086        this.endedAt = endedAt;
087        this.nodeCount = nodeCount;
088        this.status = status;
089        this.successCount = successCount;
090        this.failedCount = failedCount;
091        this.failedNodes = failedNodes;
092        this.totalLogEntries = totalLogEntries;
093        this.errorCount = errorCount;
094        this.cwd = cwd;
095        this.gitCommit = gitCommit;
096        this.gitBranch = gitBranch;
097        this.commandLine = commandLine;
098        this.actorIacVersion = actorIacVersion;
099        this.actorIacCommit = actorIacCommit;
100    }
101
102    public long getSessionId() { return sessionId; }
103    public String getWorkflowName() { return workflowName; }
104    public String getOverlayName() { return overlayName; }
105    public String getInventoryName() { return inventoryName; }
106    public LocalDateTime getStartedAt() { return startedAt; }
107    public LocalDateTime getEndedAt() { return endedAt; }
108    public int getNodeCount() { return nodeCount; }
109    public SessionStatus getStatus() { return status; }
110    public int getSuccessCount() { return successCount; }
111    public int getFailedCount() { return failedCount; }
112    public List<String> getFailedNodes() { return failedNodes; }
113    public int getTotalLogEntries() { return totalLogEntries; }
114    public int getErrorCount() { return errorCount; }
115
116    // Execution context getters
117    public String getCwd() { return cwd; }
118    public String getGitCommit() { return gitCommit; }
119    public String getGitBranch() { return gitBranch; }
120    public String getCommandLine() { return commandLine; }
121    public String getActorIacVersion() { return actorIacVersion; }
122    public String getActorIacCommit() { return actorIacCommit; }
123
124    public Duration getDuration() {
125        if (startedAt == null || endedAt == null) {
126            return Duration.ZERO;
127        }
128        return Duration.between(startedAt, endedAt);
129    }
130
131    @Override
132    public String toString() {
133        StringBuilder sb = new StringBuilder();
134        sb.append("Session #").append(sessionId).append(": ").append(workflowName).append("\n");
135        if (overlayName != null) {
136            sb.append("  Overlay:  ").append(overlayName).append("\n");
137        }
138        if (inventoryName != null) {
139            sb.append("  Inventory: ").append(inventoryName).append("\n");
140        }
141        sb.append("  Started:  ").append(formatTimestamp(startedAt)).append("\n");
142        sb.append("  Ended:    ").append(formatTimestamp(endedAt)).append("\n");
143
144        Duration d = getDuration();
145        if (!d.isZero()) {
146            long minutes = d.toMinutes();
147            long seconds = d.toSecondsPart();
148            sb.append("  Duration: ").append(minutes).append("m ").append(seconds).append("s\n");
149        }
150
151        sb.append("  Nodes:    ").append(nodeCount).append("\n");
152        sb.append("  Status:   ").append(status).append("\n");
153        sb.append("\n");
154        sb.append("  Results:\n");
155        sb.append("    SUCCESS: ").append(successCount).append(" nodes\n");
156        sb.append("    FAILED:  ").append(failedCount).append(" nodes");
157        if (failedNodes != null && !failedNodes.isEmpty()) {
158            sb.append(" (").append(String.join(", ", failedNodes)).append(")");
159        }
160        sb.append("\n");
161        sb.append("\n");
162        sb.append("  Log lines: ").append(totalLogEntries).append(" (").append(errorCount).append(" errors)");
163        return sb.toString();
164    }
165
166    /**
167     * Formats a timestamp in ISO 8601 format with timezone.
168     */
169    private String formatTimestamp(LocalDateTime timestamp) {
170        if (timestamp == null) {
171            return "N/A";
172        }
173        return timestamp.atZone(SYSTEM_ZONE).format(ISO_FORMATTER);
174    }
175}