package de.gecheckt.pdf.umbenenner.adapter.in.gui.batchrun;

import static org.junit.jupiter.api.Assertions.assertAll;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertFalse;
import static org.junit.jupiter.api.Assertions.assertNotNull;
import static org.junit.jupiter.api.Assertions.assertThrows;

import java.util.HashSet;
import java.util.Set;

import org.junit.jupiter.api.Test;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.EnumSource;

import de.gecheckt.pdf.umbenenner.application.port.in.DocumentCompletionStatus;
import de.gecheckt.pdf.umbenenner.adapter.in.gui.batchrun.ProcessingStatusPresentation.StatusVisuals;

/**
 * Unit-Tests für {@link ProcessingStatusPresentation}.
 * <p>
 * Prüft, dass alle {@link DocumentCompletionStatus}-Werte korrekte Icons, Farben,
 * Tooltip-Texte und Summary-Kategorielabels liefern und dass keine zwei Status
 * dasselbe Icon teilen.
 */
class ProcessingStatusPresentationTest {

    // -------------------------------------------------------------------------
    // iconFor
    // -------------------------------------------------------------------------

    @Test
    void iconFor_success_isCheckMark() {
        assertEquals(ProcessingStatusPresentation.ICON_SUCCESS,
                ProcessingStatusPresentation.iconFor(DocumentCompletionStatus.SUCCESS));
    }

    @Test
    void iconFor_failedRetryable_isClockwiseArrow() {
        assertEquals(ProcessingStatusPresentation.ICON_FAILED_RETRYABLE,
                ProcessingStatusPresentation.iconFor(DocumentCompletionStatus.FAILED_RETRYABLE));
    }

    @Test
    void iconFor_failedPermanent_isMultiplicationSign() {
        assertEquals(ProcessingStatusPresentation.ICON_FAILED_PERMANENT,
                ProcessingStatusPresentation.iconFor(DocumentCompletionStatus.FAILED_PERMANENT));
    }

    @Test
    void iconFor_skippedAlreadyProcessed_isIdenticalTo() {
        assertEquals(ProcessingStatusPresentation.ICON_SKIPPED_ALREADY_PROCESSED,
                ProcessingStatusPresentation.iconFor(DocumentCompletionStatus.SKIPPED_ALREADY_PROCESSED));
    }

    @Test
    void iconFor_skippedFinalFailure_isCircledDivisionSlash() {
        assertEquals(ProcessingStatusPresentation.ICON_SKIPPED_FINAL_FAILURE,
                ProcessingStatusPresentation.iconFor(DocumentCompletionStatus.SKIPPED_FINAL_FAILURE));
    }

    @Test
    void iconFor_null_throws() {
        assertThrows(NullPointerException.class,
                () -> ProcessingStatusPresentation.iconFor(null));
    }

    @Test
    void icons_areAllDistinct() {
        Set<String> icons = new HashSet<>();
        for (DocumentCompletionStatus status : DocumentCompletionStatus.values()) {
            icons.add(ProcessingStatusPresentation.iconFor(status));
        }
        assertEquals(DocumentCompletionStatus.values().length, icons.size(),
                "Jeder Status muss ein eindeutiges Icon haben");
    }

    // -------------------------------------------------------------------------
    // cssColorFor
    // -------------------------------------------------------------------------

    @ParameterizedTest
    @EnumSource(DocumentCompletionStatus.class)
    void cssColorFor_allStatuses_returnsNonBlankHexColor(DocumentCompletionStatus status) {
        String color = ProcessingStatusPresentation.cssColorFor(status);
        assertAll(
                () -> assertNotNull(color, "Farbe darf nicht null sein für " + status),
                () -> assertFalse(color.isBlank(), "Farbe darf nicht leer sein für " + status),
                () -> assertFalse(color.isEmpty(), "Farbe darf nicht leer sein für " + status)
        );
        // Farbe muss im CSS-Hex-Format beginnen (#)
        assertFalse(color.isBlank());
        assertEquals('#', color.charAt(0), "CSS-Farbe muss mit # beginnen für " + status);
    }

    @Test
    void cssColorFor_null_throws() {
        assertThrows(NullPointerException.class,
                () -> ProcessingStatusPresentation.cssColorFor(null));
    }

    @Test
    void failedRetryable_and_failedPermanent_haveDifferentColors() {
        String orangeColor = ProcessingStatusPresentation.cssColorFor(DocumentCompletionStatus.FAILED_RETRYABLE);
        String redColor = ProcessingStatusPresentation.cssColorFor(DocumentCompletionStatus.FAILED_PERMANENT);
        assertFalse(orangeColor.equals(redColor),
                "FAILED_RETRYABLE (Orange) und FAILED_PERMANENT (Rot) müssen unterschiedliche Farben haben");
    }

    // -------------------------------------------------------------------------
    // tooltipFor
    // -------------------------------------------------------------------------

    @ParameterizedTest
    @EnumSource(DocumentCompletionStatus.class)
    void tooltipFor_allStatuses_returnsNonBlankText(DocumentCompletionStatus status) {
        String tooltip = ProcessingStatusPresentation.tooltipFor(status);
        assertNotNull(tooltip, "Tooltip darf nicht null sein für " + status);
        assertFalse(tooltip.isBlank(), "Tooltip darf nicht leer sein für " + status);
    }

    @Test
    void tooltipFor_null_throws() {
        assertThrows(NullPointerException.class,
                () -> ProcessingStatusPresentation.tooltipFor(null));
    }

    @Test
    void tooltipFor_failedRetryable_containsWiederholung() {
        String tooltip = ProcessingStatusPresentation.tooltipFor(DocumentCompletionStatus.FAILED_RETRYABLE);
        assertFalse(tooltip.isBlank());
        // Tooltip muss die Retry-Semantik kommunizieren
        assertFalse(tooltip.equals(ProcessingStatusPresentation.tooltipFor(DocumentCompletionStatus.FAILED_PERMANENT)),
                "FAILED_RETRYABLE und FAILED_PERMANENT müssen unterschiedliche Tooltips haben");
    }

    @Test
    void tooltipFor_failedPermanent_containsKeinWeitererVersuch() {
        String tooltip = ProcessingStatusPresentation.tooltipFor(DocumentCompletionStatus.FAILED_PERMANENT);
        // Tooltip für FAILED_PERMANENT muss kommunizieren, dass kein weiterer automatischer Versuch folgt
        assertFalse(tooltip.isBlank());
    }

    @Test
    void tooltips_areAllDistinct() {
        Set<String> tooltips = new HashSet<>();
        for (DocumentCompletionStatus status : DocumentCompletionStatus.values()) {
            tooltips.add(ProcessingStatusPresentation.tooltipFor(status));
        }
        assertEquals(DocumentCompletionStatus.values().length, tooltips.size(),
                "Jeder Status muss einen eindeutigen Tooltip haben");
    }

    // -------------------------------------------------------------------------
    // summaryCategoryFor
    // -------------------------------------------------------------------------

    @ParameterizedTest
    @EnumSource(DocumentCompletionStatus.class)
    void summaryCategoryFor_allStatuses_returnsNonBlankLabel(DocumentCompletionStatus status) {
        String category = ProcessingStatusPresentation.summaryCategoryFor(status);
        assertNotNull(category, "Summary-Kategorie darf nicht null sein für " + status);
        assertFalse(category.isBlank(), "Summary-Kategorie darf nicht leer sein für " + status);
    }

    @Test
    void summaryCategoryFor_null_throws() {
        assertThrows(NullPointerException.class,
                () -> ProcessingStatusPresentation.summaryCategoryFor(null));
    }

    // -------------------------------------------------------------------------
    // visualsFor (gebündelt)
    // -------------------------------------------------------------------------

    @ParameterizedTest
    @EnumSource(DocumentCompletionStatus.class)
    void visualsFor_allStatuses_returnsConsistentRecord(DocumentCompletionStatus status) {
        StatusVisuals visuals = ProcessingStatusPresentation.visualsFor(status);
        assertAll(
                () -> assertNotNull(visuals, "StatusVisuals darf nicht null sein für " + status),
                () -> assertEquals(ProcessingStatusPresentation.iconFor(status), visuals.icon()),
                () -> assertEquals(ProcessingStatusPresentation.cssColorFor(status), visuals.cssColor()),
                () -> assertEquals(ProcessingStatusPresentation.tooltipFor(status), visuals.tooltipText()),
                () -> assertEquals(ProcessingStatusPresentation.summaryCategoryFor(status),
                        visuals.summaryCategoryLabel())
        );
    }

    @Test
    void visualsFor_null_throws() {
        assertThrows(NullPointerException.class,
                () -> ProcessingStatusPresentation.visualsFor(null));
    }

    // -------------------------------------------------------------------------
    // Spezifische Status-Mapping-Werte (gemäß Spezifikation)
    // -------------------------------------------------------------------------

    @Test
    void success_mapping_correctValues() {
        assertAll(
                () -> assertEquals("✓", ProcessingStatusPresentation.iconFor(DocumentCompletionStatus.SUCCESS)),
                () -> assertEquals("#2e7d32", ProcessingStatusPresentation.cssColorFor(DocumentCompletionStatus.SUCCESS)),
                () -> assertEquals("erfolgreich",
                        ProcessingStatusPresentation.summaryCategoryFor(DocumentCompletionStatus.SUCCESS))
        );
    }

    @Test
    void failedRetryable_mapping_correctValues() {
        assertAll(
                () -> assertEquals("↻", ProcessingStatusPresentation.iconFor(DocumentCompletionStatus.FAILED_RETRYABLE)),
                () -> assertEquals("#d98200",
                        ProcessingStatusPresentation.cssColorFor(DocumentCompletionStatus.FAILED_RETRYABLE)),
                () -> assertEquals("wird wiederholt",
                        ProcessingStatusPresentation.summaryCategoryFor(DocumentCompletionStatus.FAILED_RETRYABLE))
        );
    }

    @Test
    void failedPermanent_mapping_correctValues() {
        assertAll(
                () -> assertEquals("×", ProcessingStatusPresentation.iconFor(DocumentCompletionStatus.FAILED_PERMANENT)),
                () -> assertEquals("#c62828",
                        ProcessingStatusPresentation.cssColorFor(DocumentCompletionStatus.FAILED_PERMANENT)),
                () -> assertEquals("fehlgeschlagen",
                        ProcessingStatusPresentation.summaryCategoryFor(DocumentCompletionStatus.FAILED_PERMANENT))
        );
    }

    @Test
    void skippedAlreadyProcessed_mapping_correctValues() {
        assertAll(
                () -> assertEquals("≡",
                        ProcessingStatusPresentation.iconFor(DocumentCompletionStatus.SKIPPED_ALREADY_PROCESSED)),
                () -> assertEquals("übersprungen",
                        ProcessingStatusPresentation.summaryCategoryFor(
                                DocumentCompletionStatus.SKIPPED_ALREADY_PROCESSED))
        );
    }

    @Test
    void skippedFinalFailure_mapping_correctValues() {
        assertAll(
                () -> assertEquals("⊘",
                        ProcessingStatusPresentation.iconFor(DocumentCompletionStatus.SKIPPED_FINAL_FAILURE)),
                () -> assertEquals("endgültig übersprungen",
                        ProcessingStatusPresentation.summaryCategoryFor(
                                DocumentCompletionStatus.SKIPPED_FINAL_FAILURE))
        );
    }

    // -------------------------------------------------------------------------
    // Farbe ist NICHT einziges Unterscheidungsmerkmal
    // -------------------------------------------------------------------------

    @Test
    void failedRetryable_and_failedPermanent_distinctByIconAndTooltip() {
        String iconRetryable = ProcessingStatusPresentation.iconFor(DocumentCompletionStatus.FAILED_RETRYABLE);
        String iconPermanent = ProcessingStatusPresentation.iconFor(DocumentCompletionStatus.FAILED_PERMANENT);
        String tooltipRetryable = ProcessingStatusPresentation.tooltipFor(DocumentCompletionStatus.FAILED_RETRYABLE);
        String tooltipPermanent = ProcessingStatusPresentation.tooltipFor(DocumentCompletionStatus.FAILED_PERMANENT);
        assertAll(
                () -> assertFalse(iconRetryable.equals(iconPermanent),
                        "Icons müssen sich unterscheiden"),
                () -> assertFalse(tooltipRetryable.equals(tooltipPermanent),
                        "Tooltips müssen sich unterscheiden")
        );
    }
}
