package de.gecheckt.pdf.umbenenner.bootstrap;

import static org.assertj.core.api.Assertions.assertThat;

import java.nio.file.Files;
import java.nio.file.Path;
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.cli.SchedulerBatchCommand;
import de.gecheckt.pdf.umbenenner.adapter.out.ai.AnthropicClaudeHttpAdapter;
import de.gecheckt.pdf.umbenenner.adapter.out.ai.OpenAiHttpAdapter;
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.in.BatchRunOutcome;
import de.gecheckt.pdf.umbenenner.application.port.out.AiInvocationPort;
import de.gecheckt.pdf.umbenenner.application.port.out.ConfigurationPort;
import de.gecheckt.pdf.umbenenner.application.port.out.PersistenceSchemaInitializationPort;
import de.gecheckt.pdf.umbenenner.application.port.out.RunLockPort;

/**
 * Smoke tests for the complete bootstrap wiring of the active AI provider.
 * <p>
 * Each test drives the full {@link BootstrapRunner} startup sequence — configuration loading,
 * validation, schema initialisation, and use-case factory — but replaces the real AI HTTP client
 * with a wiring probe inside the use-case factory. No real HTTP calls are made.
 *
 * <h2>What is verified</h2>
 * <ul>
 *   <li>When {@code ai.provider.active=openai-compatible}, the {@link AiProviderSelector}
 *       produces an {@link OpenAiHttpAdapter} instance.</li>
 *   <li>When {@code ai.provider.active=claude}, the {@link AiProviderSelector}
 *       produces an {@link AnthropicClaudeHttpAdapter} instance.</li>
 * </ul>
 *
 * <h2>Scope</h2>
 * <p>
 * These are regression smoke tests for the provider selection path. They do not exercise
 * real document processing; the use-case factory captures the selected port and immediately
 * returns a no-op use case.
 */
class BootstrapSmokeTest {

    // =========================================================================
    // Pflicht-Testfall: smokeBootstrapWithOpenAiCompatibleActive
    // =========================================================================

    /**
     * Verifies that the bootstrap path correctly wires {@link OpenAiHttpAdapter} when
     * {@code ai.provider.active=openai-compatible} is configured.
     * <p>
     * The {@link AiProviderSelector} is called inside the use-case factory with the
     * active provider configuration; the resulting {@link AiInvocationPort} instance
     * is captured and asserted to be an {@link OpenAiHttpAdapter}.
     */
    @Test
    void smokeBootstrapWithOpenAiCompatibleActive(@TempDir Path tempDir) throws Exception {
        AtomicReference<AiInvocationPort> capturedPort = new AtomicReference<>();

        BootstrapRunner runner = new BootstrapRunner(
                configPath -> buildConfigPort(tempDir, AiProviderFamily.OPENAI_COMPATIBLE,
                        openAiConfig(), null),
                lockFile -> new NoOpRunLockPort(),
                StartConfigurationValidator::new,
                jdbcUrl -> new NoOpSchemaInitializationPort(),
                (config, lock) -> {
                    AiProviderFamily family =
                            config.multiProviderConfiguration().activeProviderFamily();
                    ProviderConfiguration provConfig =
                            config.multiProviderConfiguration().activeProviderConfiguration();
                    capturedPort.set(new AiProviderSelector().select(family, provConfig));
                    return context -> BatchRunOutcome.SUCCESS;
                },
                SchedulerBatchCommand::new
        );

        int exitCode = runner.run();

        assertThat(exitCode).isEqualTo(0);
        assertThat(capturedPort.get())
                .as("OPENAI_COMPATIBLE must wire OpenAiHttpAdapter")
                .isInstanceOf(OpenAiHttpAdapter.class);
    }

    // =========================================================================
    // Pflicht-Testfall: smokeBootstrapWithClaudeActive
    // =========================================================================

    /**
     * Verifies that the bootstrap path correctly wires {@link AnthropicClaudeHttpAdapter}
     * when {@code ai.provider.active=claude} is configured.
     * <p>
     * The {@link AiProviderSelector} is called inside the use-case factory with the
     * active provider configuration; the resulting {@link AiInvocationPort} instance
     * is captured and asserted to be an {@link AnthropicClaudeHttpAdapter}.
     */
    @Test
    void smokeBootstrapWithClaudeActive(@TempDir Path tempDir) throws Exception {
        AtomicReference<AiInvocationPort> capturedPort = new AtomicReference<>();

        BootstrapRunner runner = new BootstrapRunner(
                configPath -> buildConfigPort(tempDir, AiProviderFamily.CLAUDE,
                        null, claudeConfig()),
                lockFile -> new NoOpRunLockPort(),
                StartConfigurationValidator::new,
                jdbcUrl -> new NoOpSchemaInitializationPort(),
                (config, lock) -> {
                    AiProviderFamily family =
                            config.multiProviderConfiguration().activeProviderFamily();
                    ProviderConfiguration provConfig =
                            config.multiProviderConfiguration().activeProviderConfiguration();
                    capturedPort.set(new AiProviderSelector().select(family, provConfig));
                    return context -> BatchRunOutcome.SUCCESS;
                },
                SchedulerBatchCommand::new
        );

        int exitCode = runner.run();

        assertThat(exitCode).isEqualTo(0);
        assertThat(capturedPort.get())
                .as("CLAUDE must wire AnthropicClaudeHttpAdapter")
                .isInstanceOf(AnthropicClaudeHttpAdapter.class);
    }

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

    private static ConfigurationPort buildConfigPort(
            Path tempDir,
            AiProviderFamily activeFamily,
            ProviderConfiguration openAiConfig,
            ProviderConfiguration claudeConfig) {
        try {
            Path sourceDir = Files.createDirectories(tempDir.resolve("source"));
            Path targetDir = Files.createDirectories(tempDir.resolve("target"));
            Path dbFile = tempDir.resolve("test.db");
            if (!Files.exists(dbFile)) {
                Files.createFile(dbFile);
            }
            Path promptFile = tempDir.resolve("prompt.txt");
            if (!Files.exists(promptFile)) {
                Files.writeString(promptFile, "Test prompt.");
            }

            MultiProviderConfiguration multiConfig =
                    new MultiProviderConfiguration(activeFamily, openAiConfig, claudeConfig);

            StartConfiguration config = new StartConfiguration(
                    sourceDir,
                    targetDir,
                    dbFile,
                    multiConfig,
                    3,
                    10,
                    5000,
                    60,
                    promptFile,
                    tempDir.resolve("run.lock"),
                    tempDir.resolve("logs"),
                    "INFO",
                    false
            );

            return () -> config;
        } catch (Exception e) {
            throw new RuntimeException("Failed to set up test configuration", e);
        }
    }

    private static ProviderConfiguration openAiConfig() {
        return new ProviderConfiguration(
                "gpt-4o-mini", 30, "https://api.openai.com/v1", "test-openai-key");
    }

    private static ProviderConfiguration claudeConfig() {
        return new ProviderConfiguration(
                "claude-3-5-sonnet-20241022", 60, "https://api.anthropic.com", "test-claude-key");
    }

    // -------------------------------------------------------------------------
    // Minimal test doubles
    // -------------------------------------------------------------------------

    private static class NoOpRunLockPort implements RunLockPort {
        @Override public void acquire() {
            // intentionally empty
        }
        @Override public void release() {
            // intentionally empty
        }
        @Override
        public java.util.Optional<de.gecheckt.pdf.umbenenner.application.port.out.RunLockHandle> tryAcquire() {
            return java.util.Optional.empty();
        }
    }

    private static class NoOpSchemaInitializationPort implements PersistenceSchemaInitializationPort {
        @Override public void initializeSchema() {
            // intentionally empty
        }
    }
}
