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

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 static org.junit.jupiter.api.Assertions.assertTrue;

import java.time.Duration;
import java.util.Optional;

import org.junit.jupiter.api.Test;

import de.gecheckt.pdf.umbenenner.application.port.in.DocumentCompletionStatus;
import de.gecheckt.pdf.umbenenner.domain.model.DocumentFingerprint;

/**
 * Unit tests for {@link GuiBatchRunResultRow}, including the {@code resetMarker} factory.
 */
class GuiBatchRunResultRowTest {

    private static final DocumentFingerprint FP =
            new DocumentFingerprint("a".repeat(64));
    

    // -------------------------------------------------------------------------
    // Basic construction
    // -------------------------------------------------------------------------

    @Test
    void construction_validArgs_succeeds() {
        GuiBatchRunResultRow row = new GuiBatchRunResultRow(
                "test.pdf", FP, DocumentCompletionStatus.SUCCESS,
                Optional.of("2026-01-01 - Titel.pdf"),
                Optional.empty(),
                Optional.empty(),
                Optional.empty(),
                Duration.ofMillis(100));
        assertEquals("test.pdf", row.originalFileName());
        assertEquals(FP, row.fingerprint());
        assertEquals(DocumentCompletionStatus.SUCCESS, row.status());
        assertFalse(row.resetPending());
    }

    @Test
    void construction_nullOriginalFileName_throws() {
        assertThrows(NullPointerException.class, () ->
                new GuiBatchRunResultRow(null, FP, DocumentCompletionStatus.SUCCESS,
                        null, null, null, null, Duration.ZERO));
    }

    @Test
    void construction_blankOriginalFileName_throws() {
        assertThrows(IllegalArgumentException.class, () ->
                new GuiBatchRunResultRow("  ", FP, DocumentCompletionStatus.SUCCESS,
                        null, null, null, null, Duration.ZERO));
    }

    @Test
    void construction_nullFingerprint_throws() {
        assertThrows(NullPointerException.class, () ->
                new GuiBatchRunResultRow("test.pdf", null, DocumentCompletionStatus.SUCCESS,
                        null, null, null, null, Duration.ZERO));
    }

    @Test
    void construction_nullStatus_throws() {
        assertThrows(NullPointerException.class, () ->
                new GuiBatchRunResultRow("test.pdf", FP, null,
                        null, null, null, null, Duration.ZERO));
    }

    @Test
    void construction_negativeDuration_throws() {
        assertThrows(IllegalArgumentException.class, () ->
                new GuiBatchRunResultRow("test.pdf", FP, DocumentCompletionStatus.SUCCESS,
                        null, null, null, null, Duration.ofSeconds(-1)));
    }

    @Test
    void construction_nullOptionals_normalisedToEmpty() {
        GuiBatchRunResultRow row = new GuiBatchRunResultRow(
                "test.pdf", FP, DocumentCompletionStatus.FAILED_PERMANENT,
                null, null, null, null, Duration.ZERO);
        assertTrue(row.finalFileName().isEmpty());
        assertTrue(row.resolvedDate().isEmpty());
        assertTrue(row.aiReasoning().isEmpty());
        assertTrue(row.aiFailureMessage().isEmpty());
    }

    // -------------------------------------------------------------------------
    // Status icons
    // -------------------------------------------------------------------------

    @Test
    void statusIcon_success_isCheckMark() {
        assertEquals("✓", row(DocumentCompletionStatus.SUCCESS).statusIcon());
    }

    @Test
    void statusIcon_failedRetryable_isWarning() {
        assertEquals("↻", row(DocumentCompletionStatus.FAILED_RETRYABLE).statusIcon());
    }

    @Test
    void statusIcon_failedPermanent_isBallotX() {
        assertEquals("×", row(DocumentCompletionStatus.FAILED_PERMANENT).statusIcon());
    }

    @Test
    void statusIcon_skippedAlreadyProcessed_isNextTrack() {
        assertEquals("≡", row(DocumentCompletionStatus.SKIPPED_ALREADY_PROCESSED).statusIcon());
    }

    @Test
    void statusIcon_skippedFinalFailure_isCircledDivisionSlash() {
        assertEquals("⊘", row(DocumentCompletionStatus.SKIPPED_FINAL_FAILURE).statusIcon());
    }

    @Test
    void statusIcon_skippedValues_areDifferentFromEachOther() {
        String alreadyProcessed = row(DocumentCompletionStatus.SKIPPED_ALREADY_PROCESSED).statusIcon();
        String finalFailure = row(DocumentCompletionStatus.SKIPPED_FINAL_FAILURE).statusIcon();
        assertFalse(alreadyProcessed.equals(finalFailure),
                "SKIPPED_ALREADY_PROCESSED und SKIPPED_FINAL_FAILURE müssen unterschiedliche Icons haben");
    }

    // -------------------------------------------------------------------------
    // resetMarker factory
    // -------------------------------------------------------------------------

    @Test
    void resetMarker_preservesFingerprintAndFileName() {
        GuiBatchRunResultRow original = row(DocumentCompletionStatus.SUCCESS);
        GuiBatchRunResultRow marker = GuiBatchRunResultRow.resetMarker(original);

        assertEquals(original.originalFileName(), marker.originalFileName());
        assertEquals(original.fingerprint(), marker.fingerprint());
        assertTrue(marker.resetPending());
    }

    @Test
    void resetMarker_statusIconIsResetIcon() {
        GuiBatchRunResultRow marker = GuiBatchRunResultRow.resetMarker(
                row(DocumentCompletionStatus.FAILED_PERMANENT));
        assertEquals(GuiBatchRunResultRow.RESET_PENDING_ICON, marker.statusIcon());
    }

    @Test
    void resetMarker_statusLabelIsResetLabel() {
        GuiBatchRunResultRow marker = GuiBatchRunResultRow.resetMarker(
                row(DocumentCompletionStatus.SUCCESS));
        assertEquals(GuiBatchRunResultRow.RESET_PENDING_LABEL, marker.statusLabel());
    }

    @Test
    void resetMarker_optionalsEmpty() {
        GuiBatchRunResultRow original = new GuiBatchRunResultRow(
                "test.pdf", FP, DocumentCompletionStatus.SUCCESS,
                Optional.of("2026-01-01 - Titel.pdf"), Optional.empty(), Optional.empty(),
                Optional.empty(), Duration.ofMillis(42));
        GuiBatchRunResultRow marker = GuiBatchRunResultRow.resetMarker(original);

        assertTrue(marker.finalFileName().isEmpty());
        assertTrue(marker.resolvedDate().isEmpty());
        assertTrue(marker.aiReasoning().isEmpty());
        assertTrue(marker.aiFailureMessage().isEmpty());
        assertEquals(Duration.ZERO, marker.processingDuration());
    }

    @Test
    void resetMarker_nullArg_throws() {
        assertThrows(NullPointerException.class, () ->
                GuiBatchRunResultRow.resetMarker(null));
    }

    // -------------------------------------------------------------------------
    // resetPending=false icon/label
    // -------------------------------------------------------------------------

    @Test
    void statusLabel_normalRow_notResetLabel() {
        for (DocumentCompletionStatus status : DocumentCompletionStatus.values()) {
            String label = row(status).statusLabel();
            assertNotNull(label);
            assertFalse(label.equals(GuiBatchRunResultRow.RESET_PENDING_LABEL),
                    "Non-reset row must not show reset label for status " + status);
        }
    }

    // -------------------------------------------------------------------------
    // statusTooltip
    // -------------------------------------------------------------------------

    @Test
    void statusTooltip_success_isNonBlank() {
        assertFalse(row(DocumentCompletionStatus.SUCCESS).statusTooltip().isBlank());
    }

    @Test
    void statusTooltip_failedRetryable_isNonBlank() {
        assertFalse(row(DocumentCompletionStatus.FAILED_RETRYABLE).statusTooltip().isBlank());
    }

    @Test
    void statusTooltip_failedPermanent_isNonBlank() {
        assertFalse(row(DocumentCompletionStatus.FAILED_PERMANENT).statusTooltip().isBlank());
    }

    @Test
    void statusTooltip_failedRetryable_and_failedPermanent_areDifferent() {
        String retryable = row(DocumentCompletionStatus.FAILED_RETRYABLE).statusTooltip();
        String permanent = row(DocumentCompletionStatus.FAILED_PERMANENT).statusTooltip();
        assertFalse(retryable.equals(permanent),
                "FAILED_RETRYABLE und FAILED_PERMANENT müssen unterschiedliche Tooltips haben");
    }

    @Test
    void statusTooltip_allStatuses_delegatesToProcessingStatusPresentation() {
        for (DocumentCompletionStatus status : DocumentCompletionStatus.values()) {
            String rowTooltip = row(status).statusTooltip();
            String expectedTooltip = ProcessingStatusPresentation.tooltipFor(status);
            assertEquals(expectedTooltip, rowTooltip,
                    "statusTooltip() soll Wert von ProcessingStatusPresentation liefern für " + status);
        }
    }

    @Test
    void statusTooltip_resetPending_returnsResetLabel() {
        GuiBatchRunResultRow marker = GuiBatchRunResultRow.resetMarker(
                row(DocumentCompletionStatus.SUCCESS));
        assertEquals(GuiBatchRunResultRow.RESET_PENDING_LABEL, marker.statusTooltip());
    }

    // -------------------------------------------------------------------------
    // aiFailureMessage
    // -------------------------------------------------------------------------

    @Test
    void construction_withFailureMessage_isPresent() {
        GuiBatchRunResultRow row = new GuiBatchRunResultRow(
                "test.pdf", FP, DocumentCompletionStatus.FAILED_RETRYABLE,
                null, null, null, Optional.of("KI-Timeout"), Duration.ofMillis(10));
        assertTrue(row.aiFailureMessage().isPresent());
        assertEquals("KI-Timeout", row.aiFailureMessage().get());
    }

    @Test
    void construction_withoutFailureMessage_isEmpty() {
        GuiBatchRunResultRow row = new GuiBatchRunResultRow(
                "test.pdf", FP, DocumentCompletionStatus.SUCCESS,
                null, null, null, Optional.empty(), Duration.ofMillis(10));
        assertTrue(row.aiFailureMessage().isEmpty());
    }

    // -------------------------------------------------------------------------
    // Helper
    // -------------------------------------------------------------------------

    private static GuiBatchRunResultRow row(DocumentCompletionStatus status) {
        return new GuiBatchRunResultRow(
                "file.pdf", FP, status, null, null, null, null, Duration.ofMillis(1));
    }
}
