package de.gecheckt.pdf.umbenenner.application.validation.technicaltest;

import java.time.Instant;
import java.util.List;
import java.util.Objects;

/**
 * Ergebnis eines vollständigen technischen Gesamttests.
 * <p>
 * Enthält die {@link CheckpointResult}-Einträge aller durchlaufenen Prüfpunkte in
 * Ausführungsreihenfolge. Jeder definierte Prüfpunkt ist vertreten – entweder als
 * {@link CheckpointResult.Success}, {@link CheckpointResult.Failure} oder
 * {@link CheckpointResult.NotApplicable}.
 * <p>
 * Der Gesamttest bricht bei einem Fehler <em>nicht</em> ab; alle Prüfpunkte werden
 * vollständig durchlaufen.
 * <p>
 * Dieser Record ist immutable und enthält keine JavaFX-Typen.
 *
 * @param results      Prüfpunkt-Ergebnisse in Ausführungsreihenfolge; nie {@code null}
 * @param evaluatedAt  Zeitpunkt, zu dem der Gesamttest gestartet wurde; nie {@code null}
 */
public record TechnicalTestReport(
        List<CheckpointResult> results,
        Instant evaluatedAt) {

    /**
     * Erstellt einen neuen Gesamttestbericht.
     *
     * @param results      Ergebnisliste; darf nicht {@code null} sein
     * @param evaluatedAt  Startzeitpunkt des Tests; darf nicht {@code null} sein
     * @throws NullPointerException wenn ein Parameter {@code null} ist
     */
    public TechnicalTestReport {
        Objects.requireNonNull(results, "results must not be null");
        Objects.requireNonNull(evaluatedAt, "evaluatedAt must not be null");
        results = List.copyOf(results);
    }

    /**
     * Gibt an, ob mindestens ein Prüfpunkt mit Schweregrad {@link CheckpointSeverity#ERROR}
     * gescheitert ist.
     * <p>
     * Wenn {@code true}, gilt die Konfiguration im aktuellen Zustand als nicht lauffähig.
     *
     * @return {@code true} wenn mindestens ein Fehler-Prüfpunkt vorliegt
     */
    public boolean hasErrors() {
        return results.stream()
                .filter(r -> r instanceof CheckpointResult.Failure)
                .map(r -> (CheckpointResult.Failure) r)
                .anyMatch(f -> f.severity() == CheckpointSeverity.ERROR);
    }

    /**
     * Gibt an, ob mindestens ein Prüfpunkt mit Schweregrad {@link CheckpointSeverity#WARNING}
     * gescheitert ist.
     *
     * @return {@code true} wenn mindestens ein Warn-Prüfpunkt vorliegt
     */
    public boolean hasWarnings() {
        return results.stream()
                .filter(r -> r instanceof CheckpointResult.Failure)
                .map(r -> (CheckpointResult.Failure) r)
                .anyMatch(f -> f.severity() == CheckpointSeverity.WARNING);
    }

    /**
     * Gibt an, ob mindestens ein Prüfpunkt einen {@link CorrectionSuggestion} enthält.
     * <p>
     * Wenn {@code true}, kann aus diesem Bericht ein nicht leerer {@link CorrectionPlan}
     * abgeleitet werden.
     *
     * @return {@code true} wenn mindestens ein korrigierbarer Befund vorliegt
     */
    public boolean hasCorrectableFindings() {
        return results.stream()
                .filter(r -> r instanceof CheckpointResult.Failure)
                .map(r -> (CheckpointResult.Failure) r)
                .anyMatch(CheckpointResult.Failure::hasCorrectionSuggestion);
    }

    /**
     * Leitet einen {@link CorrectionPlan} aus den korrigierbaren Prüfpunkt-Fehlern ab.
     * <p>
     * Enthält alle {@link CorrectionSuggestion}-Einträge der gescheiterten Prüfpunkte
     * in Berichtsreihenfolge.
     *
     * @return abgeleiteter Korrekturplan; nie {@code null}; leer wenn keine Korrekturen möglich sind
     */
    public CorrectionPlan deriveCorrectionPlan() {
        List<CorrectionSuggestion> suggestions = results.stream()
                .filter(r -> r instanceof CheckpointResult.Failure)
                .map(r -> (CheckpointResult.Failure) r)
                .filter(CheckpointResult.Failure::hasCorrectionSuggestion)
                .map(f -> f.correctionSuggestion().orElseThrow())
                .toList();
        return new CorrectionPlan(suggestions);
    }

    /**
     * Gibt die Gesamtzahl der Prüfpunkt-Ergebnisse zurück.
     *
     * @return Anzahl der Ergebnisse; nie negativ
     */
    public int size() {
        return results.size();
    }
}
