package de.gecheckt.pdf.umbenenner.bootstrap;

import static org.junit.jupiter.api.Assertions.assertInstanceOf;
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 static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;

import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.mockito.junit.jupiter.MockitoExtension;

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.InvalidStartConfigurationException;
import de.gecheckt.pdf.umbenenner.application.config.provider.AiProviderFamily;
import de.gecheckt.pdf.umbenenner.application.config.provider.ProviderConfiguration;
import de.gecheckt.pdf.umbenenner.application.port.out.AiInvocationPort;

/**
 * Unit tests for {@link AiProviderSelector}.
 * <p>
 * Covers selection of the OpenAI-compatible adapter, selection of the Claude adapter,
 * hard failure for unregistered provider families, and null-safety.
 */
@ExtendWith(MockitoExtension.class)
class AiProviderSelectorTest {

    private final AiProviderSelector selector = new AiProviderSelector();

    private static ProviderConfiguration validOpenAiConfig() {
        return new ProviderConfiguration("gpt-4", 30, "https://api.example.com", "test-key");
    }

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

    // =========================================================================
    // Mandatory test case: bootstrapWiresOpenAiCompatibleAdapterWhenActive
    // =========================================================================

    /**
     * When the active provider family is OPENAI_COMPATIBLE, the selector must return
     * an {@link OpenAiHttpAdapter} instance.
     */
    @Test
    void bootstrapWiresOpenAiCompatibleAdapterWhenActive() {
        AiInvocationPort port = selector.select(AiProviderFamily.OPENAI_COMPATIBLE, validOpenAiConfig());

        assertNotNull(port, "Selector must return a non-null AiInvocationPort");
        assertInstanceOf(OpenAiHttpAdapter.class, port,
                "OPENAI_COMPATIBLE must be wired to OpenAiHttpAdapter");
    }

    // =========================================================================
    // Mandatory test case: bootstrapSelectsClaudeWhenActive (AP-005)
    // =========================================================================

    /**
     * When the active provider family is CLAUDE, the selector must return an
     * {@link AnthropicClaudeHttpAdapter} instance.
     */
    @Test
    void bootstrapSelectsClaudeWhenActive() {
        AiInvocationPort port = selector.select(AiProviderFamily.CLAUDE, validClaudeConfig());

        assertNotNull(port, "Selector must return a non-null AiInvocationPort for Claude");
        assertInstanceOf(AnthropicClaudeHttpAdapter.class, port,
                "CLAUDE must be wired to AnthropicClaudeHttpAdapter");
    }

    // =========================================================================
    // Mandatory test case: bootstrapFailsHardWhenActiveProviderUnknown
    // =========================================================================

    /**
     * A null provider family must result in a NullPointerException.
     * This guards against uninitialised / null active-provider state that
     * should be caught by the validator before reaching the selector.
     */
    @Test
    void bootstrapFailsHardWhenActiveProviderUnknown() {
        assertThrows(NullPointerException.class,
                () -> selector.select(null, validOpenAiConfig()),
                "Null provider family must throw NullPointerException");
    }

    // =========================================================================
    // Mandatory test case: bootstrapFailsHardWhenSelectedProviderHasNoImplementation
    // =========================================================================

    /**
     * A provider family with no registered adapter implementation must throw
     * {@link InvalidStartConfigurationException} immediately, preventing the
     * application from starting.
     * <p>
     * Both known families (OPENAI_COMPATIBLE and CLAUDE) are now registered.
     * This test uses a Mockito mock of {@link AiProviderFamily} to represent a
     * hypothetical future or unknown provider, confirming that the selector's
     * fallback guard remains in place for any unregistered family.
     */
    @Test
    void bootstrapFailsHardWhenSelectedProviderHasNoImplementation() {
        // Create a mock AiProviderFamily that does not equal any registered constant
        AiProviderFamily unknownFamily = mock(AiProviderFamily.class);
        when(unknownFamily.getIdentifier()).thenReturn("unknown-future-provider");

        ProviderConfiguration anyConfig = new ProviderConfiguration(
                "some-model", 30, "https://unknown.example.com", "some-key");

        InvalidStartConfigurationException ex = assertThrows(
                InvalidStartConfigurationException.class,
                () -> selector.select(unknownFamily, anyConfig),
                "A provider family with no registered adapter must throw InvalidStartConfigurationException");

        assertTrue(ex.getMessage().contains("unknown-future-provider")
                        || ex.getMessage().toLowerCase().contains("no ai adapter"),
                "Error message must reference the unregistered provider or indicate missing registration");
    }

    // =========================================================================
    // Additional safety: null ProviderConfiguration
    // =========================================================================

    @Test
    void selectThrowsWhenProviderConfigurationIsNull() {
        assertThrows(NullPointerException.class,
                () -> selector.select(AiProviderFamily.OPENAI_COMPATIBLE, null),
                "Null ProviderConfiguration must throw NullPointerException");
    }
}
