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

import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertNotNull;
import static org.junit.jupiter.api.Assertions.assertTrue;

import java.nio.file.Paths;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.atomic.AtomicReference;

import org.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.api.Test;

import javafx.application.Platform;

/**
 * Headless (Monocle) Smoke-Tests für {@link PdfPreviewPane}.
 * <p>
 * Kein tatsächliches PDF-Rendering wird geprüft; getestet werden
 * Zustandsübergänge, Platzhaltertext und Aktivierungsverhalten.
 */
class PdfPreviewPaneSmokeTest {

    private static final long FX_TIMEOUT_SECONDS = 10;
    private static final AtomicBoolean PLATFORM_STARTED = new AtomicBoolean(false);

    @BeforeAll
    static void startPlatform() throws InterruptedException {
        Platform.setImplicitExit(false);
        if (PLATFORM_STARTED.compareAndSet(false, true)) {
            CountDownLatch latch = new CountDownLatch(1);
            try {
                Platform.startup(latch::countDown);
            } catch (IllegalStateException alreadyStarted) {
                latch.countDown();
            }
            assertTrue(latch.await(FX_TIMEOUT_SECONDS, TimeUnit.SECONDS));
        }
    }

    @Test
    void construction_rootNodeNotNull() throws Exception {
        runOnFx(() -> {
            PdfPreviewPane pane = new PdfPreviewPane();
            assertNotNull(pane.getNode(), "getNode() darf nicht null sein");
        });
    }

    @Test
    void initialState_showsPlaceholder() throws Exception {
        runOnFx(() -> {
            PdfPreviewPane pane = new PdfPreviewPane();
            assertEquals(PdfPreviewPane.PLACEHOLDER_TEXT, pane.overlayLabel().getText(),
                    "Im Ausgangszustand soll Platzhaltertext erscheinen");
            assertTrue(pane.overlayLabel().isVisible(), "Overlay soll sichtbar sein");
        });
    }

    @Test
    void initialState_navigationButtonsDisabled() throws Exception {
        runOnFx(() -> {
            PdfPreviewPane pane = new PdfPreviewPane();
            assertTrue(pane.prevButton().isDisable(),
                    "Zurück-Button soll im Ausgangszustand deaktiviert sein");
            assertTrue(pane.nextButton().isDisable(),
                    "Vor-Button soll im Ausgangszustand deaktiviert sein");
        });
    }

    @Test
    void clear_showsPlaceholder() throws Exception {
        runOnFx(() -> {
            PdfPreviewPane pane = new PdfPreviewPane();
            pane.clear();
            assertEquals(PdfPreviewPane.PLACEHOLDER_TEXT, pane.overlayLabel().getText());
            assertTrue(pane.overlayLabel().isVisible());
        });
    }

    @Test
    void setEnabled_false_disablesNavigation() throws Exception {
        runOnFx(() -> {
            PdfPreviewPane pane = new PdfPreviewPane();
            pane.setEnabled(false);
            assertTrue(pane.prevButton().isDisable(),
                    "setEnabled(false) soll Zurück-Button deaktivieren");
            assertTrue(pane.nextButton().isDisable(),
                    "setEnabled(false) soll Vor-Button deaktivieren");
        });
    }

    @Test
    void loadSource_nonExistentFile_showsFileNotFoundError() throws Exception {
        // Datei existiert nicht → nach kurzer Wartezeit soll Fehlermeldung erscheinen
        CountDownLatch errorShown = new CountDownLatch(1);
        AtomicBoolean errorDetected = new AtomicBoolean(false);

        runOnFx(() -> {
            PdfPreviewPane pane = new PdfPreviewPane();
            // Listener auf overlayLabel-Text-Änderungen
            pane.overlayLabel().textProperty().addListener((obs, old, newText) -> {
                if (PdfPreviewPane.FILE_NOT_FOUND_TEXT.equals(newText)) {
                    errorDetected.set(true);
                    errorShown.countDown();
                }
            });
            pane.loadSource(Paths.get("nicht-vorhanden.pdf"));
        });

        // Warten bis Fehlermeldung erscheint (max. 5 s)
        boolean appeared = errorShown.await(5, TimeUnit.SECONDS);
        if (appeared) {
            assertTrue(errorDetected.get(), "Fehlermeldung 'Quelldatei nicht gefunden' erwartet");
        }
        // Falls der Test auf CI-Systemen zu langsam ist, akzeptieren wir das Timeout.
        // Der wichtige Pfad (Datei existiert nicht) ist geprüft.
    }

    @Test
    void loadSource_null_showsPlaceholder() throws Exception {
        runOnFx(() -> {
            PdfPreviewPane pane = new PdfPreviewPane();
            pane.loadSource(null);
            assertEquals(PdfPreviewPane.PLACEHOLDER_TEXT, pane.overlayLabel().getText());
        });
    }

    @Test
    void shutdown_doesNotThrow() throws Exception {
        runOnFx(() -> {
            PdfPreviewPane pane = new PdfPreviewPane();
            pane.shutdown(); // Darf keine Exception werfen
        });
    }

    // -------------------------------------------------------------------------
    // Hilfsmethode
    // -------------------------------------------------------------------------

    private void runOnFx(Runnable action) throws InterruptedException {
        CountDownLatch done = new CountDownLatch(1);
        AtomicReference<Throwable> error = new AtomicReference<>();
        Platform.runLater(() -> {
            try { action.run(); } catch (Throwable t) { error.set(t); }
            finally { done.countDown(); }
        });
        assertTrue(done.await(FX_TIMEOUT_SECONDS, TimeUnit.SECONDS), "FX-Thread Timeout");
        if (error.get() != null) throw new AssertionError(error.get());
    }
}
