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

import java.util.Objects;
import java.util.Optional;

/**
 * Versiegeltes Ergebnis eines einzelnen Prüfpunkts des technischen Gesamttests.
 * <p>
 * Jeder Prüfpunkt liefert genau einen der drei möglichen Zustände:
 * <ul>
 *   <li>{@link Success} – der Prüfpunkt wurde bestanden.</li>
 *   <li>{@link Failure} – der Prüfpunkt wurde nicht bestanden (Fehler oder Warnung),
 *       optional mit einem Korrekturvorschlag.</li>
 *   <li>{@link NotApplicable} – der Prüfpunkt konnte wegen fehlender Voraussetzungen
 *       nicht ausgeführt werden (z. B. API-Key-Test ohne vorhandenen API-Key).
 *       Dies ist kein Fehler, sondern ein eigenständiger Zustand.</li>
 * </ul>
 * <p>
 * Alle Implementierungen sind immutable und enthalten keine JavaFX-Typen. Sie können
 * auf beliebigen Threads erzeugt und sicher an den JavaFX Application Thread übergeben werden.
 */
public sealed interface CheckpointResult
        permits CheckpointResult.Success,
                CheckpointResult.Failure,
                CheckpointResult.NotApplicable {

    /**
     * Gibt den Bezeichner des Prüfpunkts zurück, zu dem dieses Ergebnis gehört.
     *
     * @return Prüfpunkt-Bezeichner; nie {@code null}
     */
    CheckpointId checkpointId();

    /**
     * Der Prüfpunkt wurde bestanden.
     *
     * @param checkpointId  Bezeichner des bestandenen Prüfpunkts; nie {@code null}
     * @param message       deutsche Bestätigungsmeldung; nie {@code null}
     */
    record Success(
            CheckpointId checkpointId,
            String message) implements CheckpointResult {

        /**
         * Erstellt ein Erfolgs-Ergebnis.
         *
         * @param checkpointId  Prüfpunkt-Bezeichner; darf nicht {@code null} sein
         * @param message       deutsche Bestätigungsmeldung; darf nicht {@code null} sein
         * @throws NullPointerException wenn {@code checkpointId} oder {@code message} {@code null} sind
         */
        public Success {
            Objects.requireNonNull(checkpointId, "checkpointId must not be null");
            Objects.requireNonNull(message, "message must not be null");
        }
    }

    /**
     * Der Prüfpunkt wurde nicht bestanden.
     * <p>
     * Ein gescheiterter Prüfpunkt hat immer einen Schweregrad ({@link CheckpointSeverity})
     * und eine deutsche Fehlermeldung. Optional ist ein {@link CorrectionSuggestion}
     * beigefügt, wenn eine sichere technische Korrektur möglich ist.
     * <p>
     * Ein Failure mit Schweregrad {@link CheckpointSeverity#WARNING} markiert eine
     * riskante, aber formal zulässige Einstellung. Ein Failure mit
     * {@link CheckpointSeverity#ERROR} zeigt an, dass der Gesamtstand nicht lauffähig ist.
     *
     * @param checkpointId         Bezeichner des nicht bestandenen Prüfpunkts; nie {@code null}
     * @param severity             Schweregrad; nie {@code null}
     * @param message              deutsche Fehlermeldung; nie {@code null}
     * @param correctionSuggestion optionaler Korrekturvorschlag; leer wenn keine Korrektur angeboten wird
     */
    record Failure(
            CheckpointId checkpointId,
            CheckpointSeverity severity,
            String message,
            Optional<CorrectionSuggestion> correctionSuggestion) implements CheckpointResult {

        /**
         * Erstellt ein Fehler-Ergebnis mit optionalem Korrekturvorschlag.
         *
         * @param checkpointId         Prüfpunkt-Bezeichner; darf nicht {@code null} sein
         * @param severity             Schweregrad; darf nicht {@code null} sein
         * @param message              deutsche Fehlermeldung; darf nicht {@code null} sein
         * @param correctionSuggestion optionaler Vorschlag; {@code null} wird zu leerem Optional
         * @throws NullPointerException wenn {@code checkpointId}, {@code severity} oder {@code message} {@code null} sind
         */
        public Failure {
            Objects.requireNonNull(checkpointId, "checkpointId must not be null");
            Objects.requireNonNull(severity, "severity must not be null");
            Objects.requireNonNull(message, "message must not be null");
            correctionSuggestion = Objects.requireNonNullElse(correctionSuggestion, Optional.empty());
        }

        /**
         * Erstellt ein Fehler-Ergebnis ohne Korrekturvorschlag.
         *
         * @param checkpointId  Prüfpunkt-Bezeichner; darf nicht {@code null} sein
         * @param severity      Schweregrad; darf nicht {@code null} sein
         * @param message       deutsche Fehlermeldung; darf nicht {@code null} sein
         * @return ein neues Failure-Ergebnis ohne Korrekturvorschlag
         */
        public static Failure of(CheckpointId checkpointId, CheckpointSeverity severity, String message) {
            return new Failure(checkpointId, severity, message, Optional.empty());
        }

        /**
         * Erstellt ein Fehler-Ergebnis mit einem Korrekturvorschlag.
         *
         * @param checkpointId  Prüfpunkt-Bezeichner; darf nicht {@code null} sein
         * @param severity      Schweregrad; darf nicht {@code null} sein
         * @param message       deutsche Fehlermeldung; darf nicht {@code null} sein
         * @param suggestion    Korrekturvorschlag; darf nicht {@code null} sein
         * @return ein neues Failure-Ergebnis mit Korrekturvorschlag
         */
        public static Failure withCorrection(CheckpointId checkpointId, CheckpointSeverity severity,
                String message, CorrectionSuggestion suggestion) {
            Objects.requireNonNull(suggestion, "suggestion must not be null");
            return new Failure(checkpointId, severity, message, Optional.of(suggestion));
        }

        /**
         * Gibt an, ob zu diesem Befund ein Korrekturvorschlag vorliegt.
         *
         * @return {@code true} wenn ein Korrekturvorschlag vorhanden ist
         */
        public boolean hasCorrectionSuggestion() {
            return correctionSuggestion.isPresent();
        }
    }

    /**
     * Der Prüfpunkt konnte wegen fehlender Voraussetzungen nicht ausgeführt werden.
     * <p>
     * Beispiel: Der Prüfpunkt {@link CheckpointId#API_KEY_ACCEPTED} ist nicht ausführbar,
     * wenn {@link CheckpointId#API_KEY_PRESENT} zuvor als Fehler bewertet wurde.
     * <p>
     * {@code NotApplicable} ist kein Fehler; er wird im Meldungsbereich neutral dargestellt
     * und wird nicht als Korrekturanlass behandelt.
     *
     * @param checkpointId  Bezeichner des nicht ausgeführten Prüfpunkts; nie {@code null}
     * @param reason        deutsche Begründung, warum der Prüfpunkt übersprungen wurde; nie {@code null}
     */
    record NotApplicable(
            CheckpointId checkpointId,
            String reason) implements CheckpointResult {

        /**
         * Erstellt ein Nicht-Anwendbar-Ergebnis.
         *
         * @param checkpointId  Prüfpunkt-Bezeichner; darf nicht {@code null} sein
         * @param reason        deutsche Begründung; darf nicht {@code null} sein
         * @throws NullPointerException wenn {@code checkpointId} oder {@code reason} {@code null} sind
         */
        public NotApplicable {
            Objects.requireNonNull(checkpointId, "checkpointId must not be null");
            Objects.requireNonNull(reason, "reason must not be null");
        }
    }
}
