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

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.assertTrue;

import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.List;
import java.util.Optional;
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.AfterAll;
import org.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.api.Test;

import de.gecheckt.pdf.umbenenner.adapter.in.gui.history.GuiDeleteDocumentHistoryPort;
import de.gecheckt.pdf.umbenenner.adapter.in.gui.history.GuiHistoryDetailsPort;
import de.gecheckt.pdf.umbenenner.adapter.in.gui.history.GuiHistoryOverviewPort;
import de.gecheckt.pdf.umbenenner.adapter.in.gui.history.GuiHistoryResetDocumentStatusPort;
import de.gecheckt.pdf.umbenenner.adapter.in.gui.history.GuiHistoryTab;
import de.gecheckt.pdf.umbenenner.application.usecase.DefaultHistoryOverviewUseCase.HistoryOverviewResult;
import javafx.application.Platform;
import javafx.scene.control.Tab;

/**
 * Monocle-basierte Headless-Smoke-Tests für {@link GuiHistoryTab}.
 * <p>
 * Geprüfte Szenarien:
 * <ul>
 *   <li>Tab wird mit Titel „Verlauf" erstellt.</li>
 *   <li>Tab ist nicht schließbar.</li>
 *   <li>Ohne geladene Konfiguration bleibt die Übersicht leer (null-configPath).</li>
 *   <li>Mit leerem Übersichts-Port bleibt die Tabelle leer.</li>
 * </ul>
 */
class GuiHistoryTabSmokeTest {

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

    @BeforeAll
    static void setUpJavaFxPlatform() throws InterruptedException {
        Platform.setImplicitExit(false);
        CountDownLatch latch = new CountDownLatch(1);
        try {
            Platform.startup(() -> {
                PLATFORM_STARTED.set(true);
                latch.countDown();
            });
            assertTrue(latch.await(FX_TIMEOUT_SECONDS, TimeUnit.SECONDS),
                    "JavaFX Platform muss innerhalb des Timeouts starten");
        } catch (IllegalStateException alreadyStarted) {
            CountDownLatch verifyLatch = new CountDownLatch(1);
            Platform.runLater(() -> {
                PLATFORM_STARTED.set(true);
                verifyLatch.countDown();
            });
            assertTrue(verifyLatch.await(FX_TIMEOUT_SECONDS, TimeUnit.SECONDS),
                    "Vorhandene JavaFX-Platform muss innerhalb des Timeouts erreichbar sein");
        }
    }

    @AfterAll
    static void tearDownJavaFxPlatform() {
        // Gemeinsame Platform – kein Platform.exit().
    }

    // =========================================================================
    // Stubs
    // =========================================================================

    private static GuiHistoryOverviewPort emptyOverviewPort() {
        return (configFilePath, query) ->
                new HistoryOverviewResult(List.of(), false);
    }

    private static GuiHistoryDetailsPort emptyDetailsPort() {
        return (configFilePath, fingerprint) -> Optional.empty();
    }

    private static GuiHistoryResetDocumentStatusPort noOpResetPort() {
        return (configFilePath, fingerprint) -> { /* no-op */ };
    }

    private static GuiDeleteDocumentHistoryPort noOpDeletePort() {
        return (configFilePath, fingerprint) -> { /* no-op */ };
    }

    private static GuiHistoryTab buildTab(Path configPath) {
        return new GuiHistoryTab(
                emptyOverviewPort(),
                emptyDetailsPort(),
                noOpResetPort(),
                noOpDeletePort(),
                () -> false,
                () -> configPath);
    }

    // =========================================================================
    // Tests
    // =========================================================================

    @Test
    void tab_shouldHaveTitleVerlauf() throws Exception {
        CountDownLatch latch = new CountDownLatch(1);
        AtomicReference<Throwable> fxError = new AtomicReference<>();
        AtomicReference<Tab> tabRef = new AtomicReference<>();

        Platform.runLater(() -> {
            try {
                GuiHistoryTab historyTab = buildTab(null);
                tabRef.set(historyTab.tab());
            } catch (Throwable t) {
                fxError.set(t);
            } finally {
                latch.countDown();
            }
        });

        assertTrue(latch.await(FX_TIMEOUT_SECONDS, TimeUnit.SECONDS));
        if (fxError.get() != null) {
            throw new AssertionError("FX-Thread hat eine Ausnahme geworfen", fxError.get());
        }
        assertNotNull(tabRef.get(), "Tab darf nicht null sein");
        assertEquals("Verlauf", tabRef.get().getText(), "Tab-Titel muss 'Verlauf' sein");
    }

    @Test
    void tab_shouldNotBeClosable() throws Exception {
        CountDownLatch latch = new CountDownLatch(1);
        AtomicReference<Throwable> fxError = new AtomicReference<>();
        AtomicBoolean closableRef = new AtomicBoolean(true);

        Platform.runLater(() -> {
            try {
                GuiHistoryTab historyTab = buildTab(null);
                closableRef.set(historyTab.tab().isClosable());
            } catch (Throwable t) {
                fxError.set(t);
            } finally {
                latch.countDown();
            }
        });

        assertTrue(latch.await(FX_TIMEOUT_SECONDS, TimeUnit.SECONDS));
        if (fxError.get() != null) {
            throw new AssertionError("FX-Thread hat eine Ausnahme geworfen", fxError.get());
        }
        assertFalse(closableRef.get(), "Tab darf nicht schließbar sein");
    }

    @Test
    void construction_withNullConfigPath_doesNotThrow() throws Exception {
        CountDownLatch latch = new CountDownLatch(1);
        AtomicReference<Throwable> fxError = new AtomicReference<>();

        Platform.runLater(() -> {
            try {
                // Konstruktion mit null-configPath-Supplier muss möglich sein
                GuiHistoryTab historyTab = buildTab(null);
                assertNotNull(historyTab.tab());
            } catch (Throwable t) {
                fxError.set(t);
            } finally {
                latch.countDown();
            }
        });

        assertTrue(latch.await(FX_TIMEOUT_SECONDS, TimeUnit.SECONDS));
        if (fxError.get() != null) {
            throw new AssertionError("FX-Thread hat eine Ausnahme geworfen", fxError.get());
        }
    }

    @Test
    void construction_withConfigPath_doesNotThrow() throws Exception {
        CountDownLatch latch = new CountDownLatch(1);
        AtomicReference<Throwable> fxError = new AtomicReference<>();

        Platform.runLater(() -> {
            try {
                Path dummyPath = Paths.get("config/application.properties");
                GuiHistoryTab historyTab = buildTab(dummyPath);
                assertNotNull(historyTab.tab());
            } catch (Throwable t) {
                fxError.set(t);
            } finally {
                latch.countDown();
            }
        });

        assertTrue(latch.await(FX_TIMEOUT_SECONDS, TimeUnit.SECONDS));
        if (fxError.get() != null) {
            throw new AssertionError("FX-Thread hat eine Ausnahme geworfen", fxError.get());
        }
    }
}
