package de.gecheckt.pdf.umbenenner.bootstrap;

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.Files;
import java.nio.file.Path;
import java.util.Optional;
import java.util.concurrent.atomic.AtomicReference;

import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.io.TempDir;

import de.gecheckt.pdf.umbenenner.adapter.in.gui.GuiAdapter;
import de.gecheckt.pdf.umbenenner.adapter.in.gui.GuiStartupContext;
import de.gecheckt.pdf.umbenenner.adapter.out.bootstrap.validation.InvalidStartConfigurationException;
import de.gecheckt.pdf.umbenenner.adapter.out.bootstrap.validation.StartConfigurationValidator;
import de.gecheckt.pdf.umbenenner.application.config.provider.AiProviderFamily;
import de.gecheckt.pdf.umbenenner.application.config.provider.MultiProviderConfiguration;
import de.gecheckt.pdf.umbenenner.application.config.provider.ProviderConfiguration;
import de.gecheckt.pdf.umbenenner.application.config.startup.StartConfiguration;
import de.gecheckt.pdf.umbenenner.application.port.out.ConfigurationPort;
import de.gecheckt.pdf.umbenenner.application.port.out.DocumentPersistenceException;
import de.gecheckt.pdf.umbenenner.bootstrap.startup.StartupArguments;
import de.gecheckt.pdf.umbenenner.bootstrap.startup.StartupMode;

/**
 * Tests, die die einzelnen {@code catch}-Zweige von
 * {@code BootstrapRunner.initializeApplicationRunContext()} abdecken.
 * <p>
 * Der Pfad für {@code ConfigurationLoadingException} ist bereits durch
 * {@link BootstrapRunnerConfigPathSemanticsTest} abgedeckt; dieser Test
 * fokussiert auf die verbleibenden Fehlerklassen
 * ({@link InvalidStartConfigurationException},
 * {@link DocumentPersistenceException} und {@link RuntimeException}),
 * damit auch deren Zustandsübergänge auf
 * {@code guiApplicationRunContext} ausgeführt werden.
 */
class BootstrapRunnerGuiContextInitFailureTest {

    @TempDir
    Path tempDir;

    @Test
    void guiStart_validatorThrowsInvalidStartConfiguration_contextErrorIsForwarded() throws Exception {
        Path configFile = tempDir.resolve("invalid-validation.properties");
        Files.createFile(configFile);

        AtomicReference<GuiStartupContext> capturedContext = new AtomicReference<>();
        BootstrapRunner runner = guiRunner(
                buildValidConfigPort(),
                () -> new StartConfigurationValidator() {
                    @Override
                    public void validate(StartConfiguration config) {
                        throw new InvalidStartConfigurationException("Validierung fehlgeschlagen (Test)");
                    }
                },
                jdbcUrl -> () -> { /* no-op schema init */ },
                ctx -> capturedContext.set(ctx));

        int exitCode = runner.run(new StartupArguments(StartupMode.GUI,
                Optional.of(configFile.toString())));

        assertEquals(0, exitCode, "GUI-Modus muss bei Validierungsfehler trotzdem regulär enden");
        GuiStartupContext context = capturedContext.get();
        assertNotNull(context, "GuiStartupContext muss an den GuiAdapter übergeben werden");
        assertTrue(context.applicationContextError().isPresent(),
                "applicationContextError muss bei Validierungsfehler gesetzt sein");
        assertTrue(context.applicationContextError().get().contains("Konfiguration nicht lauffähig"),
                "Fehlermeldung muss den Validierungspräfix enthalten");
    }

    @Test
    void guiStart_schemaInitThrowsDocumentPersistence_contextErrorIsForwarded() throws Exception {
        Path configFile = tempDir.resolve("invalid-schema.properties");
        Files.createFile(configFile);

        AtomicReference<GuiStartupContext> capturedContext = new AtomicReference<>();
        BootstrapRunner runner = guiRunner(
                buildValidConfigPort(),
                () -> new StartConfigurationValidator() {
                    @Override
                    public void validate(StartConfiguration config) { /* no-op valid validator */ }
                },
                jdbcUrl -> () -> {
                    throw new DocumentPersistenceException("Schema-Init fehlgeschlagen (Test)");
                },
                ctx -> capturedContext.set(ctx));

        int exitCode = runner.run(new StartupArguments(StartupMode.GUI,
                Optional.of(configFile.toString())));

        assertEquals(0, exitCode, "GUI-Modus muss bei SQLite-Fehler trotzdem regulär enden");
        GuiStartupContext context = capturedContext.get();
        assertNotNull(context);
        assertTrue(context.applicationContextError().isPresent(),
                "applicationContextError muss bei SQLite-Init-Fehler gesetzt sein");
        assertTrue(context.applicationContextError().get().contains("SQLite konnte nicht initialisiert werden"),
                "Fehlermeldung muss den SQLite-Präfix enthalten");
    }

    @Test
    void guiStart_unexpectedRuntimeException_contextErrorIsForwarded() throws Exception {
        Path configFile = tempDir.resolve("invalid-runtime.properties");
        Files.createFile(configFile);

        AtomicReference<GuiStartupContext> capturedContext = new AtomicReference<>();
        BootstrapRunner runner = guiRunner(
                buildValidConfigPort(),
                () -> new StartConfigurationValidator() {
                    @Override
                    public void validate(StartConfiguration config) {
                        throw new IllegalStateException("Unerwarteter Initialisierungsfehler (Test)");
                    }
                },
                jdbcUrl -> () -> { /* unreachable */ },
                ctx -> capturedContext.set(ctx));

        int exitCode = runner.run(new StartupArguments(StartupMode.GUI,
                Optional.of(configFile.toString())));

        assertEquals(0, exitCode, "GUI-Modus muss bei unerwartetem Fehler trotzdem regulär enden");
        GuiStartupContext context = capturedContext.get();
        assertNotNull(context);
        assertTrue(context.applicationContextError().isPresent(),
                "applicationContextError muss bei unerwartetem Fehler gesetzt sein");
        assertTrue(context.applicationContextError().get()
                        .contains("Unerwarteter Fehler bei der Kontextinitialisierung"),
                "Fehlermeldung muss den allgemeinen Präfix enthalten");
    }

    // -------------------------------------------------------------------------
    // Helpers
    // -------------------------------------------------------------------------

    private BootstrapRunner guiRunner(
            BootstrapRunner.ConfigurationPortFactory configPortFactory,
            BootstrapRunner.ValidatorFactory validatorFactory,
            BootstrapRunner.SchemaInitializationPortFactory schemaInitFactory,
            java.util.function.Consumer<GuiStartupContext> contextSink) {
        return new BootstrapRunner(
                path -> { /* no-op migration */ },
                configPortFactory,
                lockFile -> { throw new AssertionError("RunLockPort must not be called in GUI mode"); },
                validatorFactory,
                schemaInitFactory,
                (config, lock) -> { throw new AssertionError("UseCaseFactory must not be called in GUI mode"); },
                useCase -> { throw new AssertionError("CommandFactory must not be called in GUI mode"); },
                () -> new GuiAdapter() {
                    @Override
                    public void start(GuiStartupContext startupContext) {
                        contextSink.accept(startupContext);
                    }
                }
        );
    }

    private BootstrapRunner.ConfigurationPortFactory buildValidConfigPort() {
        return path -> {
            try {
                Path source = tempDir.resolve("source");
                Path target = tempDir.resolve("target");
                Files.createDirectories(source);
                Files.createDirectories(target);
                Path sqliteFile = tempDir.resolve("db.sqlite");
                if (!Files.exists(sqliteFile)) {
                    Files.createFile(sqliteFile);
                }
                Path promptFile = tempDir.resolve("prompt.txt");
                if (!Files.exists(promptFile)) {
                    Files.createFile(promptFile);
                }
                ProviderConfiguration providerConfig = new ProviderConfiguration(
                        "gpt-4", 30, "https://api.example.com", "test-key");
                MultiProviderConfiguration multiConfig = new MultiProviderConfiguration(
                        AiProviderFamily.OPENAI_COMPATIBLE, providerConfig, null);
                StartConfiguration config = new StartConfiguration(
                        source,
                        target,
                        sqliteFile,
                        multiConfig,
                        3, 100, 50000, 60,
                        promptFile,
                        tempDir.resolve("lock.lock"),
                        tempDir.resolve("logs"),
                        "INFO",
                        false
                );
                return (ConfigurationPort) () -> config;
            } catch (Exception e) {
                throw new RuntimeException("Failed to build valid ConfigurationPort", e);
            }
        };
    }
}
